2009-08-09 10 views
7

Mam tabelę o nazwie prices, która zawiera cenę zamknięcia zapasów, które śledzę codziennie.Jak mogę obliczyć najwyższe zmiany dziennych cen w% za pomocą MySQL?

Oto schemat:

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 ; 

Próbuję obliczyć spadek% Cena za wszystko, co ma wartość większą niż 0 cena na dziś i wczoraj. Z czasem ten stół będzie ogromny i martwię się o wydajność. Zakładam, że będzie to musiało być zrobione po stronie MySQL, a nie PHP, ponieważ potrzebne będzie tutaj LIMIT.

Jak wziąć 2 ostatnie daty i wykonać obliczenie spadku procentowego w MySQL?

Każda rada byłaby mile widziana.

Odpowiedz

4

Jeden problem widzę tuż nietoperza używa typ danych timestamp do tej pory, będzie to skomplikować zapytanie SQL dla dwóch powodów - trzeba będzie użyć zakres lub przekonwertować do rzeczywistej datę w gdzie klauzula, ale, co ważniejsze, ponieważ twierdzisz, że interesuje Cię dzisiejsza cena zamknięcia i wczorajsza cena zamknięcia, będziesz musiał śledzić dni, kiedy rynek jest otwarty - więc poniedziałkowe zapytanie jest inne niż wtorek, i Każdy dzień, w którym rynek jest zamknięty na wakacje, również będzie musiał zostać rozliczony.

Dodałbym kolumnę taką jak mktDay i zwiększałbym ją każdego dnia, gdy rynek jest otwarty dla biznesu. Inne podejście może obejmować kolumnę "previousClose", która sprawia, że ​​obliczenia są banalne. Rozumiem, że narusza to normalną formę, ale oszczędza kosztowne samo łączenie się z zapytaniem.

Jeśli nie możesz zmienić struktury, wykonasz samo łączenie, aby uzyskać wczorajsze zamknięcie i możesz obliczyć zmianę% i kolejność według tej zmiany%, jeśli chcesz.

Poniżej jest kod Erica, posprzątać trochę to wykonywane na moim serwerze z systemem 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 

Note back-kleszcze jak niektóre z nazw kolumn i aliasów Erica były zarezerwowane słowa.

Należy również pamiętać, że przy użyciu WHERE dla pierwszej tabeli byłaby tańsza zapytania - where realizowanej się pierwszy i ma tylko próba samodzielnego dołączyć w wierszach, które są większe od zera i mają dzisiejsza data

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: Dzięki za komentarz. Mogę zmienić znacznik czasu tylko na datę, aby ułatwić sobie pracę, a nie zajmować się zakresami. –

+0

@Knix Funkcja daty jest całkiem czysta, nie wiem, ile jest kosztowna, ale z pewnością twoja rozmowa. Nadal istnieje problem z zamykaniem rynku w weekendy i święta. previousZamknij kolumnę eliminuje samo łączenie, niechlujność zamkniętych dni targowych kosztem duplikowania danych i konieczności uprzedniego zamknięcia przy wstawianiu dzisiejszego zamknięcia. – Scott

+0

Dzięki ... Przyjmę twoją radę! –

2

Zasadniczo wystarczy dołączyć do stołu, aby znaleźć podaną zmianę%. Następnie należy złożyć zamówienie przez change, aby uzyskać największą liczbę zmieniaczy u góry. Możesz nawet zamówić przez abs(change), jeśli chcesz największych huśtawki.

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

Witaj Eric. Dzięki za rozwiązanie. Podczas wykonywania zapytania pojawia się błąd: # 1064 - Wystąpił błąd w składni SQL; sprawdź instrukcję, która odpowiada twojej wersji serwera MySQL dla właściwej składni do użycia w pobliżu 'change od ceny p_today ceny wewnętrznego łączenia p_yest na p_today.' w linii 6 –

+0

@Knix: Huh. Nie mam instancji MySQL, aby to wypróbować, ale co się stanie, jeśli usuniesz komentarz, a potem kolumnę 'change'? – Eric

+0

@Eric: Wyciągnąłem komentarz, a także 2 linie, które zmieniły się, ale nadal otrzymuję błąd. Myślę, że nie podoba mi się linia "i data (p_today.date) = CURRENT_DATE", ponieważ "data" jest w kolorze czerwonym w komunikacie o błędzie. –

3

Scott przedstawia świetny punkt dotyczący kolejnych dni targowych. Polecam obsługi to z tabeli złącza jak:

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 
; 

Ponieważ coraz więcej dni rynkowe upłynąć, zaledwie INSERT nowych date wartości w tabeli. market_day odpowiednio zwiększy.

Podczas wstawiania danych należy wyszukać wartość LAST_INSERT_ID() lub odpowiednią wartość dla danego parametru date dla poprzednich wartości.

chodzi o sam stół prices można uczynić przechowywanie, SELECT i INSERT operacje znacznie bardziej wydajne użytecznej PRIMARY KEY i bez kolumny AUTO_INCREMENT. W poniższym schemacie Twój PRIMARY KEY zawiera istotne informacje i nie jest zwykłą konwencją identyfikującą unikalne wiersze. Używanie MEDIUMINT (3 bajty) zamiast INT (4 bajty) zapisuje dodatkowy bajt na wiersz, a co ważniejsze 2 bajty na wiersz w PRIMARY KEY - a jednocześnie zapewnia ponad 16 milionów możliwych dat i symboli ticker (każdy).

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 
; 

W tym schemacie każdy rząd jest unikalne w każdej parze market_day i ticker_id. Tutaj ticker_id odpowiada liście symbol giełdowy w tickers stole z podobnego schematu do market_days tabeli:

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 
; 

Daje podobny zapytanie do innych proponowane, ale z dwoma istotnymi różnicami: 1) Nie ma transformacja funkcjonalny w kolumnie daty, która niszczy zdolność MySQL do używania kluczy w sprzężeniu; w poniższym zapytaniu MySQL użyje części PRIMARY KEY do dołączenia na market_day. 2) MySQL może używać tylko jednego klucza na klauzulę JOIN lub WHERE. W tym zapytaniu MySQL użyje pełnej szerokości PRIMARY KEY (market_day i ticker_id), podczas gdy w poprzednim zapytaniu może użyć tylko jednej (MySQL zwykle wybierze bardziej selektywny z dwóch).

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 
; 

Punkt drobniejsza jest potrzeba również dołączyć przeciwko tickers i market_days aby wyświetlić rzeczywiste ticker_symbol i date, ale te operacje są bardzo szybko, ponieważ wykorzystanie klawiszy.

+0

to poważnie o wiele lepszy schemat i zestaw zapytań – philfreo

Powiązane problemy