lunes, 29 de febrero de 2016

Combinación de Conjuntos – Usando JOINS

T-SQL proporciona un número de formas diferentes de combinar datos de varias tablas. Este post cubre los joins.
Antes de ejecutar las consultas en este post, añada un nuevo proveedor a la tabla Production.Suppliers ejecutando el siguiente código.
USE TSQL2012;
INSERT INTO Production.Suppliers
(companyname,contactname,contacttitle,address,city,postalcode,country,phone)
VALUES(N'Supplier XYZ',N'Jiru',N'Head of Security',N'42 Sekimai Musashino-shi', N'Tokyo', N'01759', N'Japan', N'(02) 4311-2609');
Este proveedor no tiene ningún producto relacionado en la tabla Production.Products y es utilizado en los ejemplos demostrando las no coincidencias.
A menudo, los datos que necesita consultar son propagados a través de múltiples tablas. Cuanto más normalizado sea el entorno, más tablas tiene usualmente. Las tablas están usualmente relacionadas a través de llaves, como una llave foránea en un lado y una llave primaria en el otro. Entonces, puede usar joins para consultar los datos de diferentes tablas y coincidir las filas que necesita que estén relacionadas. Este post cubre los diferentes tipos de joins que T-SQL soporta: cross, inner y outer.

Cross Joins

Un cross join es el tipo más simple de join, aunque no es el más comúnmente utilizado. Este join realiza lo que es conocido como un Producto Cartesiano de las dos tablas de entrada. En otras palabras, realiza una multiplicación entre las tablas, produciendo una fila por cada combinación de filas de ambos lados. Si tiene m filas en la tabla T1 y n filas en la tabla T2, el resultado de un cross join entre T1 y T2 es una tabla virtual con m×n filas. La figura proporciona una ilustración de un cross join.
  
La tabla izquierda tiene tres filas con los valores llave A, B y C. La tabla derecha tiene cuatro filas con los valores llave B1, C1, C2 y D1. El resultado es una tabla con 12 filas conteniendo todas las combinaciones posibles de filas de las dos tablas de entrada.
Considere un ejemplo de la base de datos de ejemplo TSQL2012. Esta base de datos contiene una tabla llamada dbo.Nums que tiene una columna llamada n con una secuencia de enteros desde 1. Su tarea es utilizar la tabla Nums para generar un resultado con una fila por cada día de la semana (1 a 7) y el número de turno (1 a 3), asumiendo que hay tres turnos al día. El resultado puede ser utilizado más adelante como la base para construir información sobre las actividades en los diferentes turnos en los diferentes días. Con siete días en la semana y tres turnos cada día, el resultado debería tener 21 filas.
Aquí hay una consulta que logra la tarea realizando un cross join entre dos instancias de la tabla Nums, uno representando el día (con alias D), y el otro representando los turnos (con alias S).
SELECT D.n AS eldia, S.n AS numturno
FROM dbo.Nums AS D
  CROSS JOIN dbo.Nums AS S
WHERE D.n <= 7
  AND S.n <= 3
ORDER BY eldia, numturno;
Aquí está la salida de esta consulta.
eldia       numturno
----------- -----------
1            1
1            2
1            3
2            1
2            2
2            3
3            1
3            2
3            3
4            1
4            2
4            3
5            1
5            2
5            3
6            1
6            2
6            3
7            1
7            2
7            3
La tabla Nums tiene 100,000 filas. De acuerdo con el procesamiento de consulta lógico, el primer paso en el procesamiento de la consulta es evaluar la cláusula FROM. La cross join opera en la cláusula FROM, realizando un producto Cartesiano entre las dos instancias de Nums, produciendo una tabla con 10,000’000,000 filas (no se preocupe, que esto es sólo conceptualmente). Entonces la cláusula WHERE filtra sólo las filas donde la columna D.n es menor o igual a 7, y la columna S.n es menor o igual a 3. Después de aplicar el filtro, el resultado tiene 21 filas calificadas. La cláusula SELECT entonces retorna D.n nombrándolo eldia, y S.n nombrándolo numturno.
Afortunadamente, SQL Server no tiene que seguir el procesamiento de consulta lógico, literalmente, mientras pueda retornar el resultado correcto. Es lo que tiene que ver con la optimización, retornando el resultado tan rápido como sea posible. SQL Server sabe que con un cross join seguido por un filtrado puede evaluar los filtros primero (que es especialmente eficiente cuando hay índices para soportar los filtros), y luego coincide las filas restantes.
Note la importancia de dar alias a las tablas en el join. Por un lado, es conveniente referirse a una tabla utilizando un nombre más corto. Pero en una autocombinación como la nuestra, el alias de tabla es obligatorio. Si no asigna alias diferentes a las dos instancias de la tabla, termina con un resultado no válido porque hay nombres de columna duplicados incluso cuando incluye el nombre de la tabla como un prefijo. Por dar alias diferentes a las tablas, puede referirse a las columnas de forma inequívoca utilizando la forma alias_tabla.nombre_columna, como en D.n vs. S.n.
También note, que además de soportar la sintaxis para cross joins con la palabra clave CROSS JOIN, tanto el SQL estándar y el T-SQL soportan una sintaxis más antigua, donde se especifica una coma entre los nombres de tabla, como en FROM T1, T2. Sin embargo, por un número de razones, se recomienda apegarse a la nueva sintaxis; es menos propensa a errores y permite un código más consistente.

Inner Joins

Con un inner join, puede coincidir filas de dos tablas basadas en un predicado, generalmente uno que compara un valor de llave primaria en un lado a un valor de llave foránea en otro lado. La figura ilustra un inner join.
  
Las letras representan valores de llave primaria en la tabla izquierda y valores de llave foránea en la tabla derecha. Suponiendo que el join es un equijoin (utilizando un predicado con un operador de igualdad como tablaizquierda.llavecol = tabladerecha.llavecol), el inner join retorna solamente las filas coincidentes para el cual el predicado se evalúa a Verdadero. Las filas para el cual el predicado se evalúa a Falso o Desconocido son descartadas.
A modo de ejemplo, la siguiente consulta retorna los proveedores de Japón y los productos que suministra.
SELECT
  S.companyname AS proveedor, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  INNER JOIN Production.Products AS P
   ON S.supplierid = P.supplierid
WHERE S.country = N'Japan';
Aquí está la salida de esta consulta.
proveedor       country  productid   productname    unitprice
--------------- -------- ----------- -------------- ----------
Supplier QOVFD  Japan      9           Product AOZBW  97.00
Supplier QOVFD  Japan      10          Product YHXGE  31.00
Supplier QOVFD  Japan      74          Product BKAZJ  10.00
Supplier QWUSF  Japan      13          Product POXFU  6.00
Supplier QWUSF  Japan      14          Product PWCJB  23.25
Supplier QWUSF  Japan      15          Product KSZOI  15.50
Observe que el predicado de coincidencia del join es especificado en la cláusula ON. Coinciden proveedores y productos que comparten el mismo supplierid. Las filas de un lado que no encuentran coincidencia en la otra son descartadas. Por ejemplo, los proveedores de Japón con productos no relacionados no son retornados.
Tema Clave
A menudo, cuando combina tablas, las combina basadas en una relación de llave foránea-llave unique. Por ejemplo, hay una llave foránea definida en la columna supplierid en la tabla Production.Products (la tabla que referencia), referenciando a la columna supplierid de llave primaria de la tabla Production.Suppliers (la tabla referenciada). También es importante notar que cuando se define una llave primaria o una restricción unique, SQL Server crea un índice unique en las columnas de restricción para forzar la propiedad de unicidad de la restricción. Pero cuando se define una llave foránea, SQL Server no crea índices sobre las columnas de llave foránea. Estos índices podrían mejorar el rendimiento de los joins basados en esas relaciones. Dado que SQL Server no crea tales índices automáticamente, es su responsabilidad identificar los casos donde pueden serle útiles y crearlos. Así que cuando se trabaja con afinación de índices, un área interesante para examinar, son las columnas de llave foránea, y la evaluación de los beneficios de crearles índices.
En cuanto a la última consulta, de nuevo, observe la conveniencia de utilizar alias de tabla cortos, cuando necesita referirse a nombres de columna ambiguos como supplierid. Observe que la consulta utiliza alias de tabla para prefijar incluso nombres de columna no ambiguos como S.country. Esta práctica no es obligatoria, mientras que el nombre de la columna no sea ambiguo, pero aun así es considerada una buena práctica por motivos de claridad.
Una pregunta muy común es: "¿Cuál es la diferencia entre las cláusulas
ON y WHERE, e importa si especifica su predicado en uno o en otro?" La respuesta es que para los inner joins no importa. Ambas cláusulas realizan el mismo propósito de filtrado. Ambas filtran sólo las filas para el cual el predicado se evalúa a Verdadero y descartan las filas para el cual el predicado se evalúa a Falso o Desconocido. En términos del procesamiento de consulta lógico, WHERE es evaluado justo después de FROM, por lo que conceptualmente es equivalente a concatenar los predicados con un operador AND. SQL Server lo sabe, y por lo tanto puede reorganizar internamente el orden en el cual evalúa los predicados en la práctica, y lo hace basado en estimaciones de costos.
Por estas razones, si quisiera, podría reorganizar la colocación de los predicados de la consulta anterior, especificando ambas en la cláusula ON, y aun conservando el significado original, de la siguiente manera.
SELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  INNER JOIN Production.Products AS P
   ON S.supplierid = P.supplierid
   AND S.country = N'Japan';
Para muchas personas, sin embargo, es intuitivo especificar el predicado que coincide las columnas de ambos lados en la cláusula ON, y el predicado que filtra las columnas de un solo lado en la cláusula WHERE. Pero, de nuevo, con inner joins eso no importa. En la discusión de los outer joins en la siguiente sección, verá que ellos, ON y WHERE juegan diferentes roles; necesita averiguar, de acuerdo a sus necesidades, cual es la cláusula apropiada para cada uno de sus predicados.
Como otro ejemplo para un inner join, la siguiente consulta combina dos instancias de la tabla HR.Employees para coincidir empleados con sus jefes. (Un jefe es también un empleado, de ahí la autocombinación).
SELECT E.empid,
  E.firstname + N' ' + E.lastname AS emp,
  M.firstname + N' ' + M.lastname AS mgr
FROM HR.Employees AS E
  INNER JOIN HR.Employees AS M
   ON E.mgrid = M.empid;
Aquí está la salida de esta consulta.
empid  emp                mgr
------ ------------------ -----------
2       Don Funk            Sara Davis
3       Judy Lew            Don Funk
4       Yael Peled          Judy Lew
5       Sven Buck           Don Funk
6       Paul Suurs          Sven Buck
7       Russell King        Sven Buck
8       Maria Cameron       Judy Lew
9       Zoya Dolgopyatova  Sven Buck
Observe el predicado de join: ON E.mgrid = M.empid. A la instancia empleado se le da un alias como E y a la instancia del jefe como M. Para encontrar las coincidencias correctas, el mgrid del empleado debe ser igual al empid del jefe.
Note que sólo ocho filas fueron retornadas a pesar de que hay nueve filas en la tabla. La razón es que el CEO (Sara Davis, empid de 1) no tiene jefe, y por lo tanto, su columna mgrid es NULL. Recuerde que un inner join no retorna filas que no encuentra coincidentes.
Así como con los cross joins, tanto el SQL estándar y el T-SQL soportan una sintaxis más antigua para inner joins donde se especifica una coma entre los nombres de tabla, y luego todos los predicados en la cláusula WHERE. Pero como fue mencionado, es considerada una buena práctica atenerse a la nueva sintaxis con la palabra clave JOIN. Cuando utiliza la sintaxis antigua, si se olvida de indicar el predicado join, termina con un cross join no intencional. Cuando utiliza la nueva sintaxis, un inner join no es válido sintácticamente sin una cláusula ON, así que si se olvida de indicar el predicado join, el analizador generará un error.
Debido a que un inner join es el tipo de join más comúnmente utilizado, el estándar decidió convertirlo en el valor por defecto, en caso de que especifique sólo la palabra clave JOIN. Por tanto, T1 JOIN T2 es equivalente a T1 INNER JOIN T2.

Outer Joins

Con outer joins, puede solicitar preservar todas las filas de uno o ambos lados del join, sin importar si hay filas coincidentes en el otro lado basados en el predicado ON.
Al utilizar las palabras clave LEFT OUTER JOIN (o LEFT JOIN para abreviar), pregunta por preservar la tabla izquierda. El join retorna lo que un inner join normalmente haría, es decir, las coincidencias (llamar a esas filas internas). Además, el join también retorna las filas de la izquierda que no tienen coincidencias en la tabla derecha (llamar a esas filas externas), con NULLs utilizados como marcadores de posición en el lado derecho. La figura muestra un ejemplo de un left outer join. 

A diferencia del inner join, la fila izquierda con la llave A es retornada a pesar de que no coincide en el lado derecho. Es retornado con NULLs como marcadores de posición en el lado derecho.
A modo de ejemplo, la siguiente consulta retorna los proveedores de Japón y los productos que suministran, incluyendo los proveedores de Japón que no tienen productos relacionados.
SELECT
  S.companyname AS proveedor, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  LEFT OUTER JOIN Production.Products AS P
   ON S.supplierid = P.supplierid
WHERE S.country = N'Japan';
Aquí está la salida de esta consulta.
proveedor       country  productid  productname    unitprice
--------------- -------- ---------- -------------- ----------
Supplier QOVFD  Japan      9          Product AOZBW   97.00
Supplier QOVFD  Japan      10         Product YHXGE   31.00
Supplier QOVFD  Japan      74         Product BKAZJ   10.00
Supplier QWUSF  Japan      13         Product POXFU   6.00
Supplier QWUSF  Japan      14         Product PWCJB   23.25
Supplier QWUSF  Japan      15         Product KSZOI   15.50
Supplier XYZ    Japan      NULL       NULL            NULL
Debido a que la tabla Production.Suppliers es el lado preservado del join, Supplier XYZ es retornado a pesar de que no tiene productos coincidentes. Como recordarán, un inner join no retorna este proveedor.
Es muy importante entender que, con los outer joins, las cláusulas ON y WHERE desempeñan roles muy diferentes, y por lo tanto, no son intercambiables. La cláusula WHERE aún desempeña un rol de filtrado sencillo, a saber, mantiene los casos Verdadero y descarta los casos Falso y Desconocido. En nuestra consulta, la cláusula WHERE filtra sólo los proveedores de Japón, así que los proveedores que no son de Japón, simplemente no aparecen en la salida.
Sin embargo, la cláusula ON no desempeña un rol de filtrado simple; más bien, es más un rol de coincidencia. En otras palabras, una fila en el lado preservado será retornada si el predicado ON encuentra una coincidencia o no lo hace. Así el predicado ON sólo determina qué filas del lado no preservado obtienen coincidencias con filas del lado preservado, no si retorna las filas del lado preservado. En nuestra consulta, la cláusula ON coincide con las filas de ambos lados por comparar sus valores de supplierid. Debido a que es un predicado coincidente (en contraposición a un filtro), el join no descartará proveedores; en cambio, sólo determina qué productos obtienen  coincidencia a cada proveedor. Pero incluso si un proveedor no tiene coincidencias basados en el predicado ON, el proveedor será aún retornado. En otras palabras, ON no es el final con respecto al lado preservado del join. WHERE es el final. Así que en caso de duda de donde especificar el predicado en la cláusula ON o WHERE, pregúntese: ¿Es el predicado utilizado para filtrar o para coincidir? ¿Se supone que es el final o no lo es?
¿Puede adivinar que sucede si especifica tanto el predicado que compara los supplierid de ambos lados y el que compara el país del proveedor a Japón en la cláusula ON? Lo probaremos.
SELECT
  S.companyname AS proveedor, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  LEFT OUTER JOIN Production.Products AS P
    ON S.supplierid = P.supplierid
   AND S.country = N'Japan';
Observe que es diferente en el resultado (mostrado aquí en forma abreviada) y vea si puede explicar en sus propias palabras lo que la consulta retorna ahora.
proveedor       country  productid  productname    unitprice
--------------- -------- ---------- -------------- ----------
Supplier SWRXU  UK         NULL       NULL            NULL
Supplier VHQZD  USA        NULL       NULL            NULL
Supplier STUAZ  USA        NULL       NULL            NULL
Supplier QOVFD  Japan      9          Product AOZBW   97.00
Supplier QOVFD  Japan      10         Product YHXGE   31.00
Supplier QOVFD  Japan      74         Product BKAZJ   10.00
Supplier EQPNC  Spain      NULL       NULL            NULL
...
Ahora que ambos predicados aparecen en la cláusula ON, ambos sirven a un propósito de coincidencia. Esto significa que todos los proveedores son retornados, incluso los que no son de Japón. Pero en función de coincidir un producto a un proveedor, los supplierid en ambos lados necesitan coincidir, y el país proveedor necesita ser Japón.
Regrese a la consulta que coincidió los empleados y sus jefes: Recuerde que el inner join eliminó la fila del CEO porque no encontró al jefe correspondiente. Si desea incluir la fila del CEO, necesita utilizar un outer join preservando el lado que representa a los empleados (E), de la siguiente manera.
SELECT E.empid,
  E.firstname + N' ' + E.lastname AS emp,
  M.firstname + N' ' + M.lastname AS mgr
FROM HR.Employees AS E
  LEFT OUTER JOIN HR.Employees AS M
   ON E.mgrid = M.empid;
Aquí está la salida de esta consulta, esta vez incluyendo la fila del CEO.
empid  emp                 mgr
------ ------------------ -----------
1       Sara Davis          NULL
2       Don Funk            Sara Davis
3       Judy Lew            Don Funk
4       Yael Peled          Judy Lew
5       Sven Buck           Don Funk
6       Paul Suurs          Sven Buck
7       Russell King        Sven Buck
8       Maria Cameron       Judy Lew
9       Zoya Dolgopyatova  Sven Buck
Al igual que puede utilizar un left outer join para preservar el lado izquierdo, puede utilizar un right outer join para preservar el lado derecho. Utilice las palabras clave RIGHT OUTER JOIN (o RIGHT JOIN más corta). La figura muestra un ejemplo de un right outer join. 

T-SQL también soporta un full outer join (FULL OUTER JOIN, o FULL JOIN más corto), que preserva ambos lados. La figura muestra un ejemplo de este tipo de join. 

Un full outer join retorna las filas interiores que son normalmente retornadas de un inner join; además de las filas de la izquierda que no tienen coincidencias en la derecha, con NULLs utilizados como marcadores de posición en el lado derecho; además de las filas de la derecha que no tienen coincidencias en la izquierda, con NULLs utilizados como marcadores de posición en el lado izquierdo.

Consultas Multi-Join

Es importante recordar que un join en T-SQL tiene lugar conceptualmente entre dos tablas a la vez. Una consulta multi-join evalúa las uniones conceptualmente de izquierda a derecha. Así, el resultado de un join es utilizado como la entrada izquierda al siguiente join. Si no entiende esto, puede terminar con errores lógicos, especialmente cuando los outer joins están involucrados. (Con inner y cross joins, el orden no afectará el significado).
A modo de ejemplo, suponga que quiere retornar todos los proveedores de Japón, y productos coincidentes relacionados. Para esto, necesita un outer join entre Production.Suppliers y Production.Products, preservando Proveedores. Pero también desea incluir información de categoría de producto, así que agrega un inner join a Production.Categories, de la siguiente manera.
SELECT
  S.companyname AS proveedor, S.country,
  P.productid, P.productname, P.unitprice,
  C.categoryname
FROM Production.Suppliers AS S
  LEFT OUTER JOIN Production.Products AS P
   ON S.supplierid = P.supplierid
  INNER JOIN Production.Categories AS C
   ON C.categoryid = P.categoryid
WHERE S.country = N'Japan';
Mirar la salida de esta consulta.
proveedor       country  productid  productname    unitprice  categoryname
--------------- -------- ---------- -------------- ---------- --------------
Supplier QOVFD  Japan      9          Product AOZBW   97.00     Meat/Poultry
Supplier QOVFD  Japan      10         Product YHXGE   31.00     Seafood
Supplier QOVFD  Japan      74         Product BKAZJ   10.00     Produce
Supplier QWUSF  Japan      13         Product POXFU   6.00       Seafood
Supplier QWUSF  Japan      14         Product PWCJB   23.25      Produce
Supplier QWUSF  Japan      15         Product KSZOI   15.50     Condiments
El proveedor XYZ de Japón fue descartado. ¿Puede explicar por qué?
Conceptualmente, el primer join incluye las filas externas (proveedores sin productos) pero produce NULLs en el atributo producto en esas filas. Entonces el join a Production.Categories comparó los valores NULL de categoryid en las filas externas a los valores de categoryid en Production.Categories y descartó estas filas. En resumen, el inner join que siguió al outer join anuló la parte externa del join.
Hay un número de maneras de abordar este problema, pero probablemente el más natural consiste es utilizar una capacidad interesante en el lenguaje, separar algunos de los joins a su propia fase lógica independiente. Lo que esta después es un left outer join entre Production.Suppliers y el resultado del inner join entre Production.Products y Production.Categories. Se puede plantear la consulta exactamente así.
SELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice,
  C.categoryname
FROM Production.Suppliers AS S
  LEFT OUTER JOIN
    (Production.Products AS P
      INNER JOIN Production.Categories AS C
        ON C.categoryid = P.categoryid)
    ON S.supplierid = P.supplierid
WHERE S.country = N'Japan';
Ahora el resultado retiene los proveedores de Japón sin productos.
supplier        country  productid  productname    unitprice  categoryname
--------------- -------- ---------- -------------- ---------- -------------
Supplier QOVFD  Japan      9          Product AOZBW   97.00     Meat/Poultry
Supplier QOVFD  Japan      10         Product YHXGE   31.00     Seafood
Supplier QOVFD  Japan      74         Product BKAZJ   10.00     Produce
Supplier QWUSF  Japan      13         Product POXFU   6.00       Seafood
Supplier QWUSF  Japan      14         Product PWCJB   23.25     Produce
Supplier QWUSF  Japan      15         Product KSZOI   15.50     Condiments
Supplier XYZ    Japan      NULL       NULL            NULL       NULL
Este aspecto del lenguaje puede en efecto ser confuso, pero, afortunadamente, hay una corrección.
Curiosamente, los outer joins tienen solo una sintaxis estándar, basada en la palabra clave JOIN y la cláusula ON. De hecho, la introducción de outer joins al estándar es lo que conduce a cambiar la sintaxis, donde el estándar entiende la necesidad de separación entre las cláusulas donde se especifica el predicado coincidente (ON) y el predicado de filtro (WHERE). Entonces, probablemente, por motivos de consistencia, el estándar agregó soporte para sintaxis similares basado en la palabra clave JOIN para cross join y outer joins.

Ejercicio 1: Coincidir Clientes y Pedidos con Inner Joins

En este ejercicio, practica la coincidencia entre clientes y pedidos utilizando inner joins.
1.    Abra el SSMS y conéctese a la base de datos de muestra TSQL2012.
2.    Escriba una consulta que coincida los clientes con sus respectivos pedidos, retornando sólo las coincidencias. No es necesario retornar clientes sin pedidos relacionados.
Emita la siguiente consulta utilizando un inner join.
USE TSQL2012;
SELECT C.custid, C.companyname, O.orderid, O.orderdate
FROM Sales.Customers AS C
  INNER JOIN Sales.Orders AS O
    ON C.custid = O.custid;
Esta consulta genera el siguiente resultado:
custid  companyname     orderid  orderdate
------- --------------- -------- -----------------------
85      Customer ENQZT  10248    2006-07-04 00:00:00.000
79      Customer FAPSM  10249    2006-07-05 00:00:00.000
34      Customer IBVRG  10250    2006-07-08 00:00:00.000
...
(830 filas afectadas)

Ejercicio 2: Coincidir Clientes y Pedidos con Outer Joins

En este ejercicio, practica la coincidencia de clientes y pedidos utilizando outer joins.
1.    Se comienza con la consulta que escribió en el paso 2 del ejercicio 1. Revise su consulta para también incluir a los clientes sin pedidos. Alterar el tipo de join a un left outer join, de la siguiente manera.
SELECT C.custid, C.companyname, O.orderid, O.orderdate
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid;
La salida ahora también incluye a los clientes sin pedidos, con NULLs en los atributos de pedido.
custid  companyname     orderid  orderdate
------- --------------- -------- -----------------------
85      Customer ENQZT  10248    2006-07-04 00:00:00.000
79      Customer FAPSM  10249    2006-07-05 00:00:00.000
34      Customer IBVRG  10250    2006-07-08 00:00:00.000
...
22      Customer DTDMN  NULL     NULL
57      Customer WVAXS  NULL     NULL
(832 filas afectadas)
2.    Retornar solo clientes sin pedidos. Para lograr esto, agregar a la consulta previa una cláusula WHERE que filtra sólo las filas con un NULL en la llave desde el lado no preservado (O.orderid), como sigue.
SELECT C.custid, C.companyname, O.orderid, O.orderdate
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid
WHERE O.orderid IS NULL;
El resultado muestra que hay dos clientes sin pedidos.
custid  companyname     orderid  orderdate
------- --------------- -------- -----------------------
22      Customer DTDMN  NULL NULL
57      Customer WVAXS  NULL NULL
3.    Escriba una consulta que retorna todos los clientes, pero coincide los pedidos sólo si fueron colocados en febrero del 2008. Debido a que tanto la comparación entre el custid del cliente y el custid del pedido, y el rango de fechas es considerado parte de la lógica de coincidencia, ambos deberían aparecer en la cláusula ON, de la siguiente manera.
SELECT C.custid, C.companyname, O.orderid, O.orderdate
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid
   AND O.orderdate >= '20080201'
   AND O.orderdate < '20080301';
Esta consulta retorna 110 filas; aquí está una parte de la salida.
custid  companyname     orderid  orderdate
------- --------------- -------- -----------------------
1      Customer NRZBB  NULL     NULL
2      Customer MLTDN  NULL     NULL
3      Customer KBUDE  NULL     NULL
4      Customer HFBZG  10864    2008-02-02 00:00:00.000
5      Customer HGVLZ  10866    2008-02-03 00:00:00.000
5      Customer HGVLZ  10875    2008-02-06 00:00:00.000
...

Si especifica el predicado de rango de fechas en la cláusula WHERE, los clientes que no hagan pedidos en ese mes serán filtrados, y eso no es lo que quiere.

2 comentarios:

  1. Este post se muestran la combinación de conjuntos - usando JOINS. Inner, Outer y Cross. Espero les sea de utilidad.

    ResponderBorrar
  2. que significa las iniciales CEO, entiendo el concepto, pero me gustaría saber el significado, por ejemplo en el siguiente párrafo:
    Recuerde que el inner join eliminó la fila del CEO porque no encontró al jefe correspondiente. Si desea incluir la fila del CEO, necesita utilizar un outer join preservando el lado que representa a los empleados (E), de la siguiente manera.

    ResponderBorrar