Uniones de tablas múltiples con relaciones de uno a muchos
Frecuentes
Visto 4,117 veces
1
Utilizando SQL Server 2008.
Tengo varias ubicaciones, cada una de las cuales contiene varios departamentos, cada uno de los cuales contiene varios elementos que pueden tener de cero a muchos escaneos. Cada Escaneo se relaciona con una Operación específica que puede o no tener un tiempo límite. Cada artículo también pertenece a un paquete específico que pertenece a un trabajo específico. Cada trabajo contiene uno o más paquetes que contienen uno o más elementos.
+=============+ +=============+
| Locations | | Jobs |
+=============+ +=============+
^ ^
| |
+=============+ +=============+ +=============+
| Departments | <-- | Items | --> | Packages |
+=============+ +=============+ +=============+
^
|
+=============+ +=============+
| Scans | --> | Operations |
+=============+ +=============+
Lo que intento hacer es obtener un recuento de escaneos de un trabajo agrupados por ubicación y fecha de escaneo. La parte complicada es que solo quiero contar el primer escaneo por fecha/hora por elemento donde el tiempo límite para la operación no es nulo. (NOTA: los escaneos definitivamente NO estarán en orden de fecha/hora en la tabla).
La consulta que tengo me está dando los resultados correctos, pero es terriblemente lenta cuando la cantidad de elementos para un trabajo alcanza los 75,000 más o menos. Estoy presionando por un nuevo servidor, sé que falta nuestro hardware, pero Me pregunto si hay algo que estoy haciendo en la consulta que la está atascando. también.
Por lo poco que puedo deducir del plan de ejecución, la mayor parte del costo de la consulta parece estar en la subconsulta para encontrar el primer escaneo de cada elemento. Realiza un escaneo de índice (0%) en un índice de tabla de Operaciones (ID, Cutoff) y luego un spool perezoso (19%). Realiza una búsqueda de índice (61%) en un índice de tabla de escaneos (ItemID, DateTime, OperationID, ID). Los bucles anidados subsiguientes (unión interna) son solo el 2 % y el operador superior es el 0 %. (No es que realmente entienda mucho de lo que acabo de escribir, pero estoy tratando de proporcionar la mayor cantidad de información posible...)
Aquí está la consulta:
SELECT
Departments.LocationID
, DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime))
, COUNT(Scans.ItemID) AS [COUNT]
FROM
Items
INNER JOIN Scans
ON Scans.ID =
(
SELECT TOP 1
Scans.ID
FROM
Scans
INNER JOIN Operations
ON Scans.OperationID = Operations.ID
WHERE
Operations.Cutoff IS NOT NULL
AND Scans.ItemID = Items.ID
ORDER BY
Scans.DateTime
)
INNER JOIN Operations
ON Scans.OperationID = Operations.ID
INNER JOIN Packages
ON Items.PackageID = Packages.ID
INNER JOIN Departments
ON Items.DepartmentID = Departments.ID
WHERE
Packages.JobID = @ID
GROUP BY
Departments.LocationID
, DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime));
Lo que devolverá una muestra de resultados así:
8 2012-06-08 00:00:00.000 11842
21 2012-06-07 00:00:00.000 502
11 2012-06-12 00:00:00.000 1841
15 2012-06-11 00:00:00.000 4314
16 2012-06-09 00:00:00.000 278
23 2012-06-12 00:00:00.000 1345
6 2012-06-06 00:00:00.000 2005
20 2012-06-08 00:00:00.000 352
14 2012-06-07 00:00:00.000 2408
8 2012-06-11 00:00:00.000 290
19 2012-06-10 00:00:00.000 85
20 2012-06-11 00:00:00.000 5484
7 2012-06-10 00:00:00.000 2389
16 2012-06-06 00:00:00.000 6762
18 2012-06-09 00:00:00.000 4473
14 2012-06-10 00:00:00.000 2364
1 2012-06-11 00:00:00.000 1531
22 2012-06-08 00:00:00.000 14534
5 2012-06-10 00:00:00.000 11908
9 2012-06-12 00:00:00.000 47
19 2012-06-07 00:00:00.000 559
7 2012-06-07 00:00:00.000 2576
Aquí está el plan de ejecución (no estoy seguro de lo que cambié desde la publicación original, pero el porcentaje de costo es ligeramente diferente. Sin embargo, el cuello de botella todavía parece estar en la misma área):
3 Respuestas
1
Soy un poco receloso de marcar esto como la respuesta, ya que estoy seguro de que aún podemos sacarle un poco de jugo a la consulta. Pero esto redujo mi ejecución de prueba de 22 segundos a 6 segundos (con un índice agregado en Scans: OperationID que incluye DateTime e ItemID):
WITH CTE AS
(
SELECT
Items.ItemID AS ID
, Scans.DateTime AS [DateTime]
, Operations.Cutoff AS Cutoff
, ROW_NUMBER() OVER (PARTITION BY Items.ID ORDER BY Scans.DateTime) AS RN
FROM
Items
INNER JOIN Scans
ON Items.ID = Scans.ItemID
INNER JOIN Operations
ON Scans.OperationID = Operations.ID
INNER JOIN Packages
ON Items.PackageID = Packages.ID
WHERE
Operations.Cutoff IS NOT NULL
AND Packages.JobID = @ID
)
SELECT
Departments.LocationID
, CTE.DateTime
, COUNT(Items.ID) AS COUNT
FROM
Items
INNER JOIN CTE
ON Items.ID = CTE.ID
AND CTE.RN = 1
INNER JOIN Packages
ON Items.PackageID = Packages.ID
INNER JOIN Departments
ON Items.DepartmentID = Departments.ID
WHERE
Packages.JobID = @ID
GROUP BY
Departments.LocationID
, CTE.DateTime
Respondido el 13 de junio de 12 a las 13:06
0
Es difícil decirlo con certeza, pero algo como esto puede comportarse mejor. Reemplacé su búsqueda anidada con una NUMERO DE FILA llamada. El problema en su consulta original es que la búsqueda anidada lo está matando.
Tenga en cuenta que no tengo SQL delante de mí, por lo que no puedo probarlo, pero pensar es lógicamente equivalente.
SELECT
Departments.LocationID
, DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime))
, COUNT(Scans.ItemID) AS [COUNT]
FROM
Items
INNER JOIN Scans
ON Scans.ItemID = Items.ID
INNER JOIN Operations
ON Scans.OperationID = Operations.ID
INNER JOIN Packages
ON Items.PackageID = Packages.ID
INNER JOIN Departments
ON Items.DepartmentID = Departments.ID
WHERE
Operations.Cutoff IS NOT NULL
AND
Packages.JobID = @ID
AND
ROW_NUMBER () OVER (PARTITION BY Items.ID ORDER BY Scans.DateTime) = 1
GROUP BY
Departments.LocationID
, DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime));
Respondido el 12 de junio de 12 a las 21:06
Desafortunadamente, SSMS me ladró cuando traté de usar una función de ventana fuera de una cláusula SELECT u ORDER BY. La buena noticia es que comencé a "buscar en Google" ROW_NUMBER, encontré CTE y reduje mi consulta de prueba de 22 segundos a 7 segundos. Gracias por empujarme en la dirección correcta. - Frank
Genial. Debe agregar su solución como respuesta y aceptarla. - chris shain
No creo que puedas poner ROW_NUMBER()
en un parche de WHERE
cláusula. - ypercubeᵀᴹ
0
Tengo curiosidad, ¿podrías correr? APLICAR CRUZ ¿versión?
SELECT
Departments.LocationID
, DATEADD(dd, 0, DATEDIFF(dd, 0, CA_Scans.DateTime))
, COUNT(CA_Scans.ItemID) AS [COUNT]
FROM
Items
CROSS APPLY
(
SELECT TOP 1
Scans.ID,
Scans.OperationID,
Scans.DateTime
FROM
Scans
INNER JOIN Operations
ON Scans.OperationID = Operations.ID
WHERE
Operations.Cutoff IS NOT NULL
AND Scans.ItemID = Items.ID
ORDER BY
Scans.DateTime
) CA_Scans
INNER JOIN Operations
ON CA_Scans.OperationID = Operations.ID
INNER JOIN Packages
ON Items.PackageID = Packages.ID
INNER JOIN Departments
ON Items.DepartmentID = Departments.ID
WHERE
Packages.JobID = @ID
GROUP BY
Departments.LocationID
, DATEADD(dd, 0, DATEDIFF(dd, 0, CA_Scans.DateTime));
Respondido el 13 de junio de 12 a las 00:06
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas sql-server sql-server-2008 tsql join or haz tu propia pregunta.
¿Puede mostrarnos el plan de ejecución de la consulta? (como una imagen) - Chris Shain
¿Qué versión de SQL Server estás usando? - Justin Swartsel