2009-08-09 54 views
7

我有一個名爲prices的表格,其中包括我每天跟蹤的股票收盤價。如何使用MySQL計算每日最高價格變化百分比?

這裏是架構:

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中進行%下降計算?

任何意見將不勝感激。

回答

4

一個問題,我看到了蝙蝠的使用日期時間戳數據類型,這將有兩個原因複雜的SQL查詢 - 你將不得不在使用範圍或轉換爲實際日期的但是,更重要的是,既然你聲明你對今天的收盤價格和昨天的收盤價格感興趣,你將不得不跟蹤市場開放的日子 - 所以週一的查詢不同於週二到週五,而且市場休市的任何一天都必須考慮在內。

我會添加一個像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子句第一個表將是一個更便宜的查詢 - 在哪裏得到的第一個執行,只有嘗試自聯接上是大於零的行和今天的日期

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 
+0

@Scott:感謝您的評論。我可以將時間戳更改爲日期以使事情更輕鬆,而不必處理範圍。 – 2009-08-09 05:50:57

+0

@Knix日期功能相當乾淨,不知道它有多昂貴,但肯定是你的電話。週末和假日市場關閉仍然存在問題。 previousClose列消除了自我加入,封閉市場日的混亂,以犧牲複製數據爲代價,並且在插入今日收盤時不得不知道prev close。 – Scott 2009-08-09 06:15:32

+0

謝謝...我會接受你的建議! – 2009-08-09 06:43:52

2

本質上,您可以將表加入自己以查找給定的%更改。然後,通過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 
+0

嗨,Eric。感謝您的解決方案。我在執行查詢時遇到錯誤: #1064 - 您的SQL語法錯誤;檢查對應於您的MySQL服務器版本的手冊,以便在'更改 從 價格p_today 內部加入價格p_yest上 p_today'使用附近使用正確的語法使用。在線6 – 2009-08-09 05:28:22

+0

@Knix:Huh。我沒有一個MySQL實例來試試這個,但是如果你把這個註釋掉出來,然後把'change'列改掉,會發生什麼? – Eric 2009-08-09 05:35:47

+0

@Eric:我已經刪除了評論,也刪除了兩行,但仍然出現錯誤。我認爲它不喜歡'and date(p_today.date)= CURRENT_DATE'這一行,因爲'date'在錯誤信息中是紅色的。 – 2009-08-09 05:40:23

3

斯科特提出了一個有關連續市場日的重要觀點。我建議用連接器表像處理這個:

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表本身,可以使存儲,SELECTINSERT操作更加高效與實用PRIMARY KEY並沒有AUTO_INCREMENT列。在下面的模式中,您的PRIMARY KEY包含內在有用的信息,而不僅僅是識別唯一行的慣例。使用MEDIUMINT(3字節)而不是INT(4字節)可以節省每行額外的一個字節,更重要的是在PRIMARY KEY中每行節省2個字節 - 同時仍然提供超過1600萬個可能的日期和標記符號(每個)。

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_dayticker_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每JOINWHERE子句只能使用一個密鑰。在這個查詢中,MySQL將使用PRIMARY KEYmarket_dayticker_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 
; 

更細的點是需要以顯示實際ticker_symboldate,但這些操作都非常快,因爲它們使用的鍵也加入反對tickersmarket_days

+0

這是一個更好的模式和一組查詢 – philfreo 2009-08-09 23:47:01