2008-09-08 20 views
12

Tengo una tabla que contiene los precios de muchas "cosas" diferentes en una tabla MS SQL 2005. Hay cientos de registros por artículo por día y las diferentes cosas reciben actualizaciones de precios en diferentes momentos.Consulta SQL para obtener el último precio

ID uniqueidentifier not null, 
ThingID int NOT NULL, 
PriceDateTime datetime NOT NULL, 
Price decimal(18,4) NOT NULL 

Necesito obtener los últimos precios de hoy para un grupo de cosas. La consulta siguiente funciona, pero estoy recuperando cientos de filas y tengo que recorrerlas y extraer solo la última por ThingID. ¿Cómo puedo (por ejemplo, a través de un GRUPO POR) decir que quiero la última por ThingID? ¿O tendré que usar subconsultas?

SELECT * 
FROM Thing 
WHERE ThingID IN (1,2,3,4,5,6) 
    AND PriceDate > cast(convert(varchar(20), getdate(), 106) as DateTime) 

ACTUALIZACIÓN: En un intento de ocultar la complejidad puse la columna ID en un int. En la vida real es GUID (y no el tipo secuencial). He actualizado la tabla def arriba para usar uniqueidentifier.

+0

@BlaM: Desafortunadamente, el ID es un GUID y no un Int. (Que no podrías saber en ese momento). Lo siento. – Marius

Respuesta

20

creo que la única solución con la estructura de la tabla es trabajar con una subconsulta:

SELECT * 
    FROM Thing 
    WHERE ID IN (SELECT max(ID) FROM Thing 
        WHERE ThingID IN (1,2,3,4) 
        GROUP BY ThingID) 

(Dado el alto ID también significa el precio más reciente)

Sin embargo le sugiero que añadir una "IsCurrent "columna que es 0 si no es el último precio o 1 si es el último. Esto agregará el posible riesgo de datos inconsistentes, pero acelerará todo el proceso cuando la tabla aumente (si está en un índice). Entonces todo lo que tiene que hacer es ...

SELECT * 
    FROM Thing 
    WHERE ThingID IN (1,2,3,4) 
    AND IsCurrent = 1 

ACTUALIZACIÓN

bien, Markus actualizan la pregunta para demostrar que ID es un uniqueid, no un int. Eso hace que escribir la consulta sea aún más complejo.

SELECT T.* 
    FROM Thing T 
    JOIN (SELECT ThingID, max(PriceDateTime) 
      WHERE ThingID IN (1,2,3,4) 
      GROUP BY ThingID) X ON X.ThingID = T.ThingID 
           AND X.PriceDateTime = T.PriceDateTime 
    WHERE ThingID IN (1,2,3,4) 

Me gustaría sugerir el uso de una columna "IsCurrent" o ir con la otra sugerencia que se encuentra en las respuestas y el uso de la tabla "precio actual" y una tabla separada "historial de precios" (lo que en última instancia será el más rápido, porque mantiene la tabla de precios en sí misma pequeña).

(sé que el ThingID en la parte inferior es redundante. Haga la prueba, si es más rápido con o sin que "WHERE". No está seguro de qué versión será más rápido después de que el optimizador hizo su trabajo.)

+0

La consulta 'join' es mucho, mucho más rápida que la que tiene 'select' incrustado en where ('join' hace una selección adicional, 'where' hace una para cada entrada!). ¿Podrías editar tu carta para indicar esto? – skolima

+0

@skolima: ¿Cómo sugeriría que cambie la subconsulta por una combinación? No creo que eso sea posible, porque necesito una función agregada "en el medio". – BlaM

+0

Bueno, no sé cómo deshacerme por completo de la subconsulta. Sin embargo, en su "respuesta compleja de actualización", la subconsulta se ejecuta una vez. En su primera consulta, la subconsulta se invoca tantas veces como haya elementos en 'Thing'. Al menos en MySQL. – skolima

2

I Intentaría algo así como la siguiente subconsulta y se olvidaría de cambiar sus estructuras de datos.

SELECT 
* 
FROM 
Thing 
WHERE 
(ThingID, PriceDateTime) IN 
(SELECT 
    ThingID, 
    max(PriceDateTime) 
    FROM 
    Thing 
    WHERE 
    ThingID IN (1,2,3,4) 
    GROUP BY 
    ThingID 
) 

Editar lo anterior es ANSI SQL y ahora estoy adivinando que tiene más de una columna en una obra subconsulta duerma para T SQL. Marius, no puedo probar lo siguiente, pero inténtalo;

SELECT 
p.* 
FROM 
Thing p, 
(SELECT ThingID, max(PriceDateTime) FROM Thing WHERE ThingID IN (1,2,3,4) GROUP BY ThingID) m 
WHERE 
p.ThingId = m.ThingId 
and p.PriceDateTime = m.PriceDateTime 

otra opción podría ser la de cambiar la fecha en una cadena y concatenar con el id por lo que tiene una sola columna. Sin embargo, esto sería un poco desagradable.

+0

Mark, probé esto pero SQL Sercver se queja de la cláusula where. No sabía que podía hacer "WHERE (ThingID, PriceDateTime) IN ..."? – Marius

1

Depende de la naturaleza de cómo se usarán sus datos, pero si los datos de precios anteriores no se usarán casi tan regularmente como los datos de precios actuales, puede haber un argumento aquí para una tabla de historial de precios. De esta forma, los datos no actuales pueden archivarse en la tabla del historial de precios (probablemente por factores desencadenantes) a medida que ingresan los nuevos precios.

Como digo, dependiendo de su modelo de acceso, esta podría ser una opción.

2

Si la ruta sub consulta era demasiado lento Me gustaría ver el tratamiento de sus actualizaciones de precios como un registro de auditoría y el mantenimiento de una mesa ThingPrice - tal vez como un activador en la tabla actualizaciones de precios:

ThingID int not null, 
UpdateID int not null, 
PriceDateTime datetime not null, 
Price decimal(18,4) not null 

La clave primaria se simplemente sea ThingID y "UpdateID" es la "ID" en su tabla original.

1

Estoy convirtiendo el uniqueidentifier en un binario para que pueda obtener un MAX de él. Esto debe asegurarse de que usted no conseguirá los duplicados de múltiples registros con ThingIDs y PriceDateTimes idénticos:

SELECT * FROM Thing WHERE CONVERT(BINARY(16),Thing.ID) IN 
(
SELECT MAX(CONVERT(BINARY(16),Thing.ID)) 
    FROM Thing 
    INNER JOIN 
    (SELECT ThingID, MAX(PriceDateTime) LatestPriceDateTime FROM Thing 
    WHERE PriceDateTime >= CAST(FLOOR(CAST(GETDATE() AS FLOAT)) AS DATETIME) 
    GROUP BY ThingID) LatestPrices 
    ON Thing.ThingID = LatestPrices.ThingID 
    AND Thing.PriceDateTime = LatestPrices.LatestPriceDateTime 
GROUP BY Thing.ThingID, Thing.PriceDateTime 
) AND Thing.ThingID IN (1,2,3,4,5,6) 
1

Desde identificación no es secuencial, que se supone que tiene un índice único en ThingID y PriceDateTime tan solo precio puede ser el más reciente para un artículo dado.

Esta consulta obtendrá todos los elementos de la lista SI fueron tasados ​​hoy. Si elimina la cláusula where de PriceDate obtendrá el último precio independientemente de la fecha.

SELECT * 
FROM Thing thi 
WHERE thi.ThingID IN (1,2,3,4,5,6) 
    AND thi.PriceDateTime = 
    (SELECT MAX(maxThi.PriceDateTime) 
     FROM Thing maxThi 
     WHERE maxThi.PriceDateTime >= CAST(CONVERT(varchar(20), GETDATE(), 106) AS DateTime) 
     AND maxThi.ThingID = thi.ThingID) 

Tenga en cuenta que he cambiado ">" para "> =" ya que podría tener un precio justo en el comienzo de un día

2

Puesto que usted está utilizando SQL Server 2005, puede utilizar la nueva (CROSS | OUTTER) APLICA la cláusula. La cláusula APPLY te permite unirte a una tabla con una función con valores de tabla.

Para resolver el problema, primero definir una tabla de función de valor para recuperar los mejores n filas de cosa para un ID específico, fecha ordenó:

CREATE FUNCTION dbo.fn_GetTopThings(@ThingID AS GUID, @n AS INT) 
    RETURNS TABLE 
AS 
RETURN 
    SELECT TOP(@n) * 
    FROM Things 
    WHERE ThingID= @ThingID 
    ORDER BY PriceDateTime DESC 
GO 

y luego utilizar la función de recuperar las 1 mejores registros en una consulta:

SELECT * 
    FROM Thing t 
CROSS APPLY dbo.fn_GetTopThings(t.ThingID, 1) 
WHERE t.ThingID IN (1,2,3,4,5,6) 

la magia aquí se hace por la cláusula de aplicar con la que aplica la función a cada fila de la izquierda resultado establecer luego se une con el conjunto de resultados devuelto por la función a continuación Retuns el conjunto de resultados finales .(Nota: para hacer una izquierda unirse como aplicar, usar Outter APLICA que devuelve todas las filas de la izquierda, mientras CROSS APPLY devuelve sólo las filas que tienen un partido en el lado derecho)

Blam: Porque puedo' t publicar comentarios todavía (debido a puntos de rept bajos) ni siquiera a mis propias respuestas ^^, responderé en el cuerpo del mensaje: -la cláusula APPLY incluso, si usa funciones con valores de tabla, está optimizado internamente por SQL Servidor de tal manera que no llama a la función para cada fila en el conjunto de resultados izquierdo, sino que toma el sql interno de la función y lo convierte en una cláusula de unión con el resto de la consulta, por lo que el rendimiento es equivalente o incluso mejor (si el plan se elige directamente por el servidor sql y se pueden hacer más optimizaciones) que la ejecución de una consulta usando subconsultas), y en mi experiencia personal cia SOLICITAR no tiene problemas de rendimiento cuando la base de datos está indexada correctamente y estadísticas están al día (al igual que una consulta normal con subconsultas se comporta en estas condiciones)

+0

He trabajado principalmente con SQL Server 2000, así que este es un concepto nuevo para mí. Técnicamente, ¿creará eso una tabla temporal con todas las filas y luego ejecutará la "función" sobre la tabla temporal? ¿Cómo se compara eso en velocidad con mi solución "JOINed"? – BlaM

0

Prueba esto (siempre y cuando sólo se necesita la última precio, no el identificador o de fecha y hora de ese precio)

SELECT ThingID, (SELECT TOP 1 Price FROM Thing WHERE ThingID = T.ThingID ORDER BY PriceDateTime DESC) Price 
FROM Thing T 
WHERE ThingID IN (1,2,3,4) AND DATEDIFF(D, PriceDateTime, GETDATE()) = 0 
GROUP BY ThingID 
-1

tal vez missunderstood el TAKS, pero ¿qué pasa con un:

SELECT ID, ThingID, max(PriceDateTime), Price FROM Thing GROUP BY ThingID

0

Se debe trabajar el ingenio hout utilizando una columna PK global (para claves primarias complejas, por ejemplo):

SELECT t1.*, t2.PriceDateTime AS bigger FROM Prices t1 
LEFT JOIN Prices t2 ON t1.ThingID = t2.ThingID AND t1.PriceDateTime < t2.PriceDateTime 
HAVING t2.PriceDateTime IS NULL 
Cuestiones relacionadas