2009-08-09 10 views
7

Tengo una tabla llamada prices que incluye el precio de cierre de las existencias que estoy siguiendo todos los días.¿Cómo puedo calcular los mejores cambios de% de precio diario usando MySQL?

Aquí es el esquema:

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 ; 

Estoy tratando de calcular la caída% precio de cualquier cosa que tenga un valor mayor que 0 precio de hoy y de ayer. Con el tiempo, esta mesa será enorme y me preocupa el rendimiento. Supongo que esto tendrá que hacerse en el lado de MySQL en lugar de en PHP porque aquí se necesitará LIMIT.

¿Cómo tomo las 2 últimas fechas y hago el cálculo de% drop en MySQL?

Cualquier consejo sería muy apreciado.

Respuesta

4

Un problema que veo la derecha del palo es el uso de un tipo de datos de marca de tiempo para la fecha, esto complicará la consulta SQL por dos razones - que tendrá que utilizar una gama o convertir a una fecha real en su where clause, pero, más importante aún, ya que declara que está interesado en el precio de cierre de hoy y el precio de cierre de ayer, tendrá que hacer un seguimiento de los días en que el mercado está abierto, por lo que la consulta del lunes es diferente a tue - fri, y cualquier día que el mercado esté cerrado por vacaciones tendrá que ser contabilizado también.

Agregaría una columna como mktDay y la incrementaría cada día que el mercado esté abierto para los negocios. Otro enfoque podría ser incluir una columna 'previousClose' que hace que su cálculo sea trivial. Me doy cuenta de que esto viola la forma normal, pero ahorra una auto-unión costosa en su consulta.

Si no puede cambiar la estructura, se auto unirá para obtener el cierre de ayer y podrá calcular el% de cambio y ordenar el% de cambio si lo desea.

A continuación se muestra el código de Eric, limpiado un poco ejecutó en mi servidor ejecutando 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 

Nota las copias de las garrapatas como algunos de sus nombres de las columnas y alias de Eric estaban reservadas palabras.

También tenga en cuenta que el uso de una cláusula en la que por primera mesa sería una consulta menos costoso - el dónde conseguir de ejecutará en primer lugar y sólo tiene que intente auto unirse a las filas que son mayores que cero y tienen fecha de hoy

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: Gracias por tu comentario. Puedo cambiar la fecha y hora a la fecha para facilitar las cosas en lugar de tener que lidiar con los rangos. –

+0

@Knix La función de fecha es bastante limpia, no estoy seguro de lo caro que es, pero sin duda su llamada. Todavía hay un problema sobre el cierre del mercado los fines de semana y días festivos. columna previousClose elimina la unión automática, el desorden de los días de mercado cerrados a expensas de duplicar datos y tener que saber el cierre anterior al insertar el cierre de hoy. – Scott

+0

Gracias ... ¡Tomaré su consejo! –

2

Básicamente, puede unirse a la tabla para encontrar el% de cambio dado. Luego, ordene por change descendiendo para obtener los cambiadores más grandes en la parte superior. Incluso puede ordenar por abs(change) si desea los cambios más grandes.

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

Hola Eric. Gracias por la solución. Me aparece un error al ejecutar la consulta: # 1064 - Tiene un error en la sintaxis SQL; revise el manual que corresponde a su versión del servidor MySQL para la sintaxis correcta para usar cerca de 'cambiar de precios p_today precios de unión interna p_yest en p_today.' en la línea 6 –

+0

@Knix: Huh. No tengo una instancia de MySQL para probar esto, pero ¿qué sucede si saca el comentario y luego sale la columna 'change'? – Eric

+0

@Eric: He sacado el comentario y también las 2 líneas que han cambiado pero siguen recibiendo el error. Creo que no le gusta la línea 'y fecha (p_today.date) = CURRENT_DATE' porque 'fecha' está en rojo en el mensaje de error. –

3

Scott plantea un gran punto acerca de los días de mercado consecutivos. Yo recomiendo el manejo de este conector con una mesa como:

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 
; 

Como transcurren más días de mercado, sólo INSERT nuevos date valores de la tabla. market_day se incrementará en consecuencia.

Al insertar datos de prices, busque LAST_INSERT_ID() o el valor correspondiente a date para ver los valores pasados.

En cuanto a la tabla prices en sí, se puede hacer de almacenamiento, y SELECTINSERT operaciones mucho más eficientes con un útil PRIMARY KEY y ninguna columna AUTO_INCREMENT. En el esquema a continuación, su PRIMARY KEY contiene información intrínsecamente útil y no es solo una convención para identificar filas únicas. El uso de MEDIUMINT (3 bytes) en lugar de INT (4 bytes) guarda un byte adicional por fila y más importante aún 2 bytes por fila en el PRIMARY KEY, todo mientras que ofrece más de 16 millones de fechas posibles y símbolos de cotización (cada uno).

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 
; 

En este esquema cada fila es único a través de cada par de market_day y ticker_id. Aquí ticker_id corresponde a una lista de símbolos de cotización en una tabla tickers con un esquema similar a la tabla 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 
; 

Esto produce una consulta similar a otras propuestas, pero con dos diferencias importantes: 1) No hay ninguna transformación funcional en la columna de fecha, que destruye la capacidad de MySQL para usar claves en la unión; en la consulta a continuación, MySQL usará parte del PRIMARY KEY para unirse al market_day. 2) MySQL solo puede usar una clave por JOIN o WHERE cláusula. En esta consulta MySQL usará el ancho completo de PRIMARY KEY (market_day y ticker_id) mientras que en la consulta anterior solo podría usar uno (MySQL usualmente elegirá el más selectivo de los dos).

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 
; 

Un punto más fina es la necesidad de unirse también contra tickers y market_days con el fin de mostrar la real ticker_symbol y date, pero estas operaciones son muy rápidos ya que hacen uso de las teclas.

+0

esto es seriamente un esquema mucho mejor y un conjunto de consultas – philfreo

Cuestiones relacionadas