Как я могу рассчитать верхний% ежедневных изменений цен с помощью MySQL? - PullRequest
8 голосов
/ 09 августа 2009

У меня есть таблица с именем prices, которая включает цену закрытия акций, которые я отслеживаю ежедневно.

Вот схема:

CREATE TABLE `prices` (
  `id` int(21) NOT NULL auto_increment,
  `ticker` varchar(21) NOT NULL,
  `price` decimal(7,2) NOT NULL,
  `date` timestamp NOT NULL default CURRENT_TIMESTAMP,
  PRIMARY KEY  (`id`),
  KEY `ticker` (`ticker`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2200 ;

Я пытаюсь рассчитать процентное падение цены для всего, что имеет значение цены больше 0 на сегодня и вчера. Со временем эта таблица будет огромной, и я беспокоюсь о производительности. Я предполагаю, что это должно быть сделано на стороне MySQL, а не на PHP, потому что здесь потребуется LIMIT.

Как мне взять последние 2 даты и рассчитать% падения в MySQL?

Любой совет будет принят с благодарностью.

Ответы [ 3 ]

4 голосов
/ 09 августа 2009

Одна проблема, которую я сразу вижу, заключается в использовании типа данных timestamp для даты, это усложнит ваш SQL-запрос по двум причинам - вам придется использовать диапазон или преобразовать его в фактическую дату в предложении where, но что еще более важно, поскольку вы заявляете, что заинтересованы в сегодняшней цене закрытия и вчерашней цене закрытия, вам придется отслеживать дни, когда рынок открыт, - поэтому запрос в понедельник отличается от вторника-пятницы и в любой день рынка закрыто на праздник, придется учитывать.

Я бы добавил столбец типа mktDay и увеличивал его каждый день, когда рынок открыт для бизнеса. Другой подход может заключаться в том, чтобы включить столбец «предыдущий закрыть», который делает ваши вычисления тривиальными. Я понимаю, что это нарушает нормальную форму, но это экономит дорогостоящее самостоятельное соединение в вашем запросе.

Если вы не можете изменить структуру, тогда вы сделаете самостоятельное объединение, чтобы получить вчерашнее закрытие, и вы можете рассчитать% изменения и порядок на это изменение%, если хотите.

Ниже приведен код Эрика, он был немного очищен и выполнен на моем сервере под управлением mysql 5.0.27

.
select
   p_today.`ticker`,
   p_today.`date`,
   p_yest.price as `open`,
   p_today.price as `close`,
   ((p_today.price - p_yest.price)/p_yest.price) as `change`
from
   prices p_today
   inner join prices p_yest on
       p_today.ticker = p_yest.ticker
       and date(p_today.`date`) = date(p_yest.`date`) + INTERVAL 1 DAY
       and p_today.price > 0
       and p_yest.price > 0
       and date(p_today.`date`) = CURRENT_DATE
order by `change` desc
limit 10

Обратите внимание на обратные галочки, поскольку некоторые имена ваших столбцов и псевдонимы Эрика были зарезервированными словами.

Также обратите внимание, что использование оператора where для первой таблицы будет менее дорогим запросом - метод get, который выполняется первым и должен только попытаться выполнить самостоятельное объединение в строках, которые больше нуля и имеют сегодняшнюю дату

select
   p_today.`ticker`,
   p_today.`date`,
   p_yest.price as `open`,
   p_today.price as `close`,
   ((p_today.price - p_yest.price)/p_yest.price) as `change`
from
   prices p_today
   inner join prices p_yest on
       p_today.ticker = p_yest.ticker
       and date(p_today.`date`) = date(p_yest.`date`) + INTERVAL 1 DAY

       and p_yest.price > 0
where p_today.price > 0
    and date(p_today.`date`) = CURRENT_DATE
order by `change` desc
limit 10
3 голосов
/ 09 августа 2009

Скотт поднимает замечательный вопрос о последовательных рыночных днях. Я рекомендую обрабатывать это с помощью таблицы соединителей, например:

CREATE TABLE `market_days` ( 
  `market_day` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
  `date` DATE NOT NULL DEFAULT '0000-00-00',
  PRIMARY KEY USING BTREE (`market_day`), 
  UNIQUE KEY USING BTREE (`date`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 
;

По мере того, как будет больше рыночных дней, просто INSERT новые date значения в таблице. market_day будет увеличиваться соответственно.

При вставке данных prices ищите LAST_INSERT_ID() или соответствующее значение для данного date для прошлых значений.

Что касается самой таблицы prices, вы можете сделать хранилище, операции SELECT и INSERT гораздо более эффективными с полезным столбцом PRIMARY KEY и без AUTO_INCREMENT. В приведенной ниже схеме ваш PRIMARY KEY содержит полезную информацию, а не просто соглашение по идентификации уникальных строк. Использование MEDIUMINT (3 байта) вместо INT (4 байта) экономит дополнительный байт на строку и, что еще важнее, 2 байта на строку в PRIMARY KEY - при этом все еще предоставляется более 16 миллионов возможных дат и символов тикера (каждый ).

CREATE TABLE `prices` ( 
  `market_day` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
  `ticker_id` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0', 
  `price` decimal (7,2) NOT NULL DEFAULT '00000.00', 
  PRIMARY KEY USING BTREE (`market_day`,`ticker_id`), 
  KEY `ticker_id` USING BTREE (`ticker_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1
;

В этой схеме каждая строка уникальна для каждой пары market_day и ticker_id. Здесь ticker_id соответствует списку символов тикера в таблице tickers со схемой, аналогичной таблице market_days:

CREATE TABLE `tickers` ( 
  `ticker_id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
  `ticker_symbol` VARCHAR(5),
  `company_name` VARCHAR(50), 
  /* etc */
  PRIMARY KEY USING BTREE (`ticker_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 
;

Это дает запрос, аналогичный другим предложенным, но с двумя важными отличиями: 1) В столбце даты нет функционального преобразования, которое разрушает способность MySQL использовать ключи в соединении; в приведенном ниже запросе MySQL будет использовать часть PRIMARY KEY, чтобы присоединиться к market_day. 2) MySQL может использовать только один ключ в предложении JOIN или WHERE. В этом запросе MySQL будет использовать полную ширину PRIMARY KEY (market_day и ticker_id), тогда как в предыдущем запросе он мог использовать только один (MySQL обычно выбирает более селективный из двух).

SELECT 
  `market_days`.`date`, 
  `tickers`.`ticker_symbol`, 
  `yesterday`.`price` AS `close_yesterday`, 
  `today`.`price` AS `close_today`, 
  (`today`.`price` - `yesterday`.`price`) / (`yesterday`.`price`)  AS `pct_change`
FROM 
  `prices` AS `today`
LEFT JOIN 
  `prices` AS `yesterday` 
  ON /* uses PRIMARY KEY */
    `yesterday`.`market_day` = `today`.`market_day` - 1 /* this will join NULL for `today`.`market_day` = 0 */
    AND
    `yesterday`.`ticker_id` = `today`.`ticker_id` 
INNER JOIN 
  `market_days` /* uses first 3 bytes of PRIMARY KEY */
  ON 
  `market_days`.`market_day` = `today`.`market_day`
INNER JOIN 
  `tickers` /* uses KEY (`ticker_id`) */
  ON 
  `tickers`.`ticker_id` = `today`.`ticker_id`
WHERE 
  `today`.`price` > 0 
  AND 
  `yesterday`.`price` > 0 
;

Более тонкий момент - это необходимость также объединиться с tickers и market_days, чтобы отобразить действительные ticker_symbol и date, но эти операции выполняются очень быстро, поскольку они используют ключи.

2 голосов
/ 09 августа 2009

По сути, вы можете просто присоединить таблицу к себе, чтобы найти данное изменение%. Затем сделайте заказ по убыванию change, чтобы получить самые большие чейнджеры сверху. Вы можете даже заказать на abs(change), если хотите самые большие колебания.

select
   p_today.ticker,
   p_today.date,
   p_yest.price as open,
   p_today.price as close,
   --Don't have to worry about 0 division here
   (p_today.price - p_yest.price)/p_yest.price as change
from
   prices p_today
   inner join prices p_yest on
       p_today.ticker = p_yest.ticker
       and date(p_today.date) = date(date_add(p_yest.date interval 1 day))
       and p_today.price > 0
       and p_yest.price > 0
       and date(p_today.date) = CURRENT_DATE
order by change desc
limit 10
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...