如何使用MySQL计算每日最高%的价格变化?

如何使用MySQL计算每日最高%的价格变化?,sql,mysql,Sql,Mysql,我有一个名为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`), KE

我有一个名为
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

我如何在MySQL中计算最后两个日期和百分比下降


如果您有任何建议,我们将不胜感激。

基本上,您只需将表加入到表中,即可找到给定的%更改。然后,按
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

我马上看到的一个问题是使用日期的时间戳数据类型,这将使sql查询复杂化,原因有两个-您必须在where子句中使用范围或转换为实际日期,但更重要的是,由于您声明对今天的收盘价和昨天的收盘价感兴趣,你必须跟踪市场开放的日期——因此周一的查询与周二-周五的查询不同,任何一天市场因假期而关闭也必须被考虑在内

我会添加一个类似mktDay的专栏,并在市场开放的每一天增加它。另一种方法可能是包含一个“previousClose”列,这使您的计算变得微不足道。我知道这违反了正常形式,但它在您的查询中节省了昂贵的自连接

如果您无法更改结构,那么您将进行自连接以获得昨天的收盘价,如果您愿意,您可以根据该百分比计算百分比更改和订单

下面是Eric的代码,它在我运行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
请注意背面的记号,因为您的一些列名和Eric的别名是保留字

还请注意,对第一个表使用where子句将是一个成本较低的查询—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

斯科特提出了一个关于连续交易日的重要观点。我建议使用如下连接器表来处理此问题:

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 
;
随着市场天数的增加,只需在表中插入新的日期值即可<代码>市场日将相应增加

插入
prices
数据时,查找
LAST\u INSERT\u ID()
或给定
date
的对应值以查找过去的值

至于
prices
表本身,您可以使用一个有用的
主键
和无
自动增量
列,使存储、
SELECT
INSERT
操作更加高效。在下面的模式中,
主键
包含本质上有用的信息,而不仅仅是标识唯一行的约定。使用
MEDIUMINT
(3个字节)而不是
INT
(4个字节)可以在
主键中每行节省一个额外字节,更重要的是每行节省2个字节,同时仍然可以提供超过1600万个可能的日期和股票代码符号(每个)

在该模式中,每一行在每对
市场日
股票代码
中都是唯一的。此处,
ticker\u id
对应于
tickers
表中的股票代码列表,其模式与
market\u 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)date列上没有函数转换,这破坏了MySQL在连接上使用键的能力;在下面的查询中,MySQL将使用
主键的一部分
市场日
加入。2) MySQL只能在每个
JOIN
WHERE
子句中使用一个键。在这个查询中,MySQL将使用
主键的全宽(
market\u day
ticker\u id
),而在前面的查询中,MySQL只能使用一个(MySQL通常会从两个主键中选择更具选择性的一个)


更好的一点是,为了显示实际的
股票代码
日期
,还需要加入
股票代码
日期
,但是这些操作非常快,因为它们使用键。

Hi Eric。谢谢你的解决方案。但是,我在执行查询时遇到了一个错误:#1064-您的SQL语法有一个错误;查看与您的MySQL服务器版本对应的手册,以了解在第行“从今日价格更改到今日价格内部加入今日价格”附近使用的正确语法6@Knix:嗯。我没有一个MySQL实例来尝试这个,但是如果你把注释去掉,然后把
change
列去掉,会发生什么呢?@Eric:我去掉了注释,也去掉了其中有更改的两行,但是仍然得到了错误。我认为它不喜欢“and date(p_today.date)=CURRENT_date”行,因为“date”在错误消息中是红色的。我认为date函数只在MySQL(v4.1.1)中存在?在v4.1.1中添加了
date
,但它仍然是一个有效的函数。我想它在数学约会时犹豫了。我添加了日期添加功能。@Scott:谢谢你的评论。我可以将时间戳更改为日期,以使事情更简单,而不必处理范围。@Knix日期函数非常干净,不确定它有多贵,但肯定是您的调用。还有一个问题是周末和节假日市场关闭。previousClose列消除了自动加入、封闭市场日的混乱,代价是重复数据,并且在插入今天的收盘时必须知道previousClose。谢谢……我会接受你的建议!这是一个严重的问题
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 
;
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 
;