2011-03-25 20 views
6

Por favor considere las 2 declaraciones siguientes en SQL Server:servidor SQL - OUTER APPLY frente Sub-consultas

Ésta está utilizando anidados sub-consultas:

WITH cte AS 
(
    SELECT TOP 100 PERCENT * 
    FROM Segments 
    ORDER BY InvoiceDetailID, SegmentID 
) 
SELECT *, ReturnDate = 
       (SELECT TOP 1 cte.DepartureInfo 
        FROM cte 
        WHERE seg.InvoiceDetailID = cte.InvoiceDetailID 
         AND cte.SegmentID > seg.SegmentID), 
      DepartureCityCode = 
       (SELECT TOP 1 cte.DepartureCityCode 
        FROM cte 
        WHERE seg.InvoiceDetailID = cte.InvoiceDetailID 
         AND cte.SegmentID > seg.SegmentID) 
FROM Segments seg 

Y esto utiliza un OUTER APPLY operador:

WITH cte AS 
(
    SELECT TOP 100 PERCENT * 
    FROM Segments 
    ORDER BY InvoiceDetailID, SegmentID 
) 
SELECT seg.*, t.DepartureInfo AS ReturnDate, t.DepartureCityCode 
FROM Segments seg OUTER APPLY (
       SELECT TOP 1 cte.DepartureInfo, cte.DepartureCityCode 
       FROM cte 
       WHERE seg.InvoiceDetailID = cte.InvoiceDetailID 
         AND cte.SegmentID > seg.SegmentID 
      ) t 

¿Cuál de estos 2 potencialmente podría realizar mejor teniendo en cuenta que tanto la tabla de segmentos pueden tener potencialmente millones de filas?

Mi intuición es APLICACIÓN EXTERIOR funcionaría mejor.

Un par de preguntas más:

  1. Casi estoy bastante seguro de esto, pero todavía quería confirmar que en la primera solución, el CTE efectivamente sería ejecutado dos veces (debido a que su referencia a dos veces y CTE es ampliado en línea como una macro).
  2. ¿Se podría ejecutar CTE una vez por cada fila cuando se utiliza en el operador APLICACIÓN EXTERNA? ¿También se ejecutará para cada fila cuando se usa en consultas anidadas en la primera declaración?
+8

plazo, inspeccionar planes de consulta –

+1

'TOP 100 PERCENT ... ORDER BY' está optimizado por cierto y no tiene ningún efecto. Estoy de acuerdo en que el segundo debería funcionar mejor. También puede ver 'ROW_NUMBER' y' PARTITION BY' para obtener el 'TOP 1' por grupo. –

Respuesta

4

Primero, deshágase del Top 100 Percent en el CTE. No está utilizando TOP aquí y si desea ordenar los resultados, debe agregar un Pedido por hasta el final de la declaración completa. En segundo lugar, para abordar su pregunta sobre el rendimiento, y si se lo obliga a adivinar, mi apuesta estaría en el segundo formulario solo porque tiene una única subconsulta en lugar de dos. En tercer lugar, otra forma que se puede tratar sería:

With RankedSegments As 
    (
    Select S1.SegmentId, ... 
     , Row_Number() Over(Partition By S1.SegmentId Order By S2.SegmentId) As Num 
    From Segments As S1 
     Left Join Segments As S2 
      On S2.InvoiceDetailId = S1.InvoiceDetailId 
       And S2.SegmentId > S1.SegmentID 
    ) 
Select ... 
From RankedSegments 
Where Num = 1 

Otra posibilidad

With MinSegments As 
    (
    Select S1.SegmentId, Min(S2.SegmentId) As MinSegmentId 
    From Segments As S1 
     Join Segments As S2 
      On S2.InvoiceDetailId = S1.InvoiceDetailId 
       And S2.SegmentId > S1.SegmentID 
    Group By S1.SegmentId 
    ) 
Select ... 
From Segments As S1 
    Left Join (MinSegments As MS1 
     Join Segments As S2 
      On S2.SegmentId = MS1.MinSegmentId) 
     On MS1.SegmentId = S1.SegmentId 
+0

@Thomas: la ORDEN BY está allí porque las solicitudes APLICAR EXTERNA/Anidadas deben ejecutarse contra la tabla ordenada correcta, verá que necesito la fila TOP 1 y que tiene que ser de una tabla Clasificada, es por eso que un 100% POR CIENTO con PEDIDO POR AQUÍ. Hmmm ... Creo que ROW_NUMBER también sería una buena opción, me pregunto cómo me lo perdí :(Lo comprobaré y volveré ... –

+0

@Thomas: Falta CTE de la segunda consulta 'GROUP BY '. –

+0

@Andriy M - Doah. Reparado. Gracias. – Thomas

1

tal vez voy a utilizar esta variación de Thomas consulta:

WITH cte AS 
(
SELECT *, Row_Number() Over(Partition By SegmentId Order By InvoiceDetailID, SegmentId) As Num 
FROM Segments) 
SELECT seg.*, t.DepartureInfo AS ReturnDate, t.DepartureCityCode 
FROM Segments seg LEFT JOIN cte t ON seg.InvoiceDetailID = t.InvoiceDetailID AND t.SegmentID > seg.SegmentID AND t.Num = 1 
+0

Si SegmentId es el PK, num será 1 para cada fila. – Thomas

+0

Hmmm ... Gracias por señalar esto ... –

+0

@Thomas: Sí SegmentId es el PK. Creo que cualquier solución basada en la cláusula PARTITION BY u OVER no sería factible en este caso, incluida la que publicaste. –