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.
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.
Este post se muestran la combinación de conjuntos - usando JOINS. Inner, Outer y Cross. Espero les sea de utilidad.
ResponderBorrarque significa las iniciales CEO, entiendo el concepto, pero me gustaría saber el significado, por ejemplo en el siguiente párrafo:
ResponderBorrarRecuerde 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.