Uniones de tablas múltiples con relaciones de uno a muchos

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): Plan de ejecución

preguntado el 12 de junio de 12 a las 20:06

¿Puede mostrarnos el plan de ejecución de la consulta? (como una imagen) -

¿Qué versión de SQL Server estás usando? -

3 Respuestas

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

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ᵀᴹ

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 or haz tu propia pregunta.