El optimizador de consultas de SQL Server es muy listo para resolver queries, pero algunas veces necesita un poco de ayuda.
Usemos la base de datos Northwind para mostrar un ejemplo. Supongamos que queremos una lista de las ordenes y su detalle de todos los id's que sean mayores o iguales a 11,000. Podríamos filtrar la tabla de ordenes o la tabla de detalles, lo cual sería lo mismo para extraer los registros correctos, pero si aplicamos el criterio para las dos tablas la búsqueda será mucho más eficiente ya que necesitaría de mucho menos I/O's.
Este sería el query que comúnmente escribiríamos para extraer la información
SELECT
*
FROM
Orders AS O
JOIN
[Order Details] AS OD ON OD.OrderID= O.OrderID
WHERE
O.OrderID >= 11000
Ahora si habilitamos las estadísticas de IO veríamos lo siguiente:
Table 'Order Details'. Scan count 78, logical reads 158, physical reads 0, read−
ahead reads 0. Table 'Orders'. Scan count 1, logical reads 3, physical reads 0, read−ahead reads 0.
Y el plan de ejecución:
|−−Nested Loops(Inner Join, OUTER REFERENCES:([O].[OrderID])) |−−Clustered Index Seek(OBJECT:([Northwind].[dbo].[Orders].[PK_Orders] AS
[O]), SEEK:([O].[OrderID] >= 11000) ORDERED FORWARD)
|−−Clustered Index Seek(OBJECT:([Northwind].[dbo].[Order
Details].[PK_Order_Details] AS [OD]), SEEK:([OD].[OrderID]=[O].[OrderID])
ORDERED FORWARD)
Ahora hagamos lo mismo pero anexando el filtro para ambas tablas.
SELECT
*
FROM
Orders AS O
JOIN
[Order Details] AS OD ON OD.OrderID = O.OrderID
WHERE
O.OrderID >= 11000
AND
OD.OrderID >= 11000
Revisando las estadísticas de IO, podemos ver como disminuye el número de IO's considerablemente.
Table 'Order Details'. Scan count 1, logical reads 3, physical reads 0, read−ahead
reads 0.
Table 'Orders'. Scan count 1, logical reads 3, physical reads 0, read−ahead reads 0.
Ahora veamos el plan de ejecución:
|−−Merge Join(Inner Join, MERGE:([O].[OrderID])=([OD].[OrderID]),
RESIDUAL:([O].[OrderID]=[OD].[OrderID]))
|−−Clustered Index Seek(OBJECT:([Northwind].[dbo].[Orders].[PK_Orders] AS
[O]), SEEK:([O].[OrderID] >= 11000) ORDERED FORWARD)
|−−Clustered Index Seek(OBJECT:([Northwind].[dbo].[Order
Details].[PK_Order_Details] AS [OD]), SEEK:([OD].[OrderID] >= 11000)
ORDERED FORWARD)
Noten como el primera query utiliza un nested−loops join mientras que el segundo utiliza una merge join el cuál es más eficiente.
Conculsión.
Siempre que escribamos un join en donde la columna filtrada exista en más de una tabla, debemos anexarla en nuestra cláusula WHERE. De esa manera podemos asegurar que el optimizador de consultas de SQL Server va a escoger el plan de ejecución más adecuado.