Quiero hacer una unión externa completa en MySQL. es posible? ¿MySQL admite una unión externa completa?
Quiero hacer una unión externa completa en MySQL. es posible? ¿MySQL admite una unión externa completa?
Respuestas:
No tiene FULL JOINS en MySQL, pero puede emularlos .
Para un código SAMPLE transcrito de esta pregunta SO , tiene:
con dos tablas t1, t2:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
La consulta anterior funciona para casos especiales en los que una operación FULL OUTER JOIN no produciría filas duplicadas. La consulta anterior depende del UNION
operador de conjunto para eliminar las filas duplicadas introducidas por el patrón de consulta. Podemos evitar la introducción de filas duplicadas mediante el uso de un patrón anti-unión para la segunda consulta, y luego usar un operador de conjunto UNION ALL para combinar los dos conjuntos. En el caso más general, donde una UNIÓN COMPLETA EXTERNA devolvería filas duplicadas, podemos hacer esto:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION ALL
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.id IS NULL
(SELECT ... FROM tbl1 LEFT JOIN tbl2 ...) UNION ALL (SELECT ... FROM tbl1 RIGHT JOIN tbl2 ... WHERE tbl1.col IS NULL)
t1
y t2
, la consulta en esta respuesta devuelve un conjunto de resultados que emula FULL OUTER JOIN. Pero en el caso más general, por ejemplo, la lista SELECT no contiene suficientes columnas / expresiones para hacer que las filas devueltas sean únicas, entonces este patrón de consulta es insuficiente para reproducir el conjunto que produciría a FULL OUTER JOIN
. Para obtener una emulación más fiel, necesitaríamos un UNION ALL
operador establecido, y una de las consultas necesitaría un patrón anti-unión . El comentario de Pavle Lekic (arriba) da el patrón de consulta correcto .
La respuesta que dio Pablo Santa Cruz es correcta; sin embargo, en caso de que alguien se tope con esta página y quiera más aclaraciones, aquí hay un desglose detallado.
Supongamos que tenemos las siguientes tablas:
-- t1
id name
1 Tim
2 Marta
-- t2
id name
1 Tim
3 Katarina
Una unión interna, como esta:
SELECT *
FROM `t1`
INNER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Solo obtendríamos registros que aparecen en ambas tablas, así:
1 Tim 1 Tim
Las uniones internas no tienen una dirección (como izquierda o derecha) porque son explícitamente bidireccionales; necesitamos una coincidencia en ambos lados.
Las uniones externas, por otro lado, son para encontrar registros que pueden no tener una coincidencia en la otra tabla. Como tal, debe especificar qué lado de la unión puede tener un registro faltante.
LEFT JOIN
y RIGHT JOIN
son taquigrafía para LEFT OUTER JOIN
y RIGHT OUTER JOIN
; Usaré sus nombres completos a continuación para reforzar el concepto de combinaciones externas frente a combinaciones internas.
Una unión externa izquierda, como esta:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... nos obtendría todos los registros de la tabla de la izquierda, independientemente de si tienen una coincidencia en la tabla de la derecha, de esta manera:
1 Tim 1 Tim
2 Marta NULL NULL
Una unión externa derecha, como esta:
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
... nos obtendría todos los registros de la tabla derecha, independientemente de si tienen una coincidencia en la tabla izquierda, como este:
1 Tim 1 Tim
NULL NULL 3 Katarina
Una combinación externa completa nos daría todos los registros de ambas tablas, independientemente de si tienen una coincidencia en la otra tabla, con NULL en ambos lados donde no hay coincidencia. El resultado se vería así:
1 Tim 1 Tim
2 Marta NULL NULL
NULL NULL 3 Katarina
Sin embargo, como señaló Pablo Santa Cruz, MySQL no es compatible con esto. Podemos emularlo haciendo una UNIÓN de una unión izquierda y una unión derecha, así:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
UNION
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`;
Puede pensar en a UNION
como que significa "ejecutar ambas consultas, luego apilar los resultados uno encima del otro"; algunas de las filas provendrán de la primera consulta y algunas de la segunda.
Cabe señalar que un UNION
en MySQL eliminará los duplicados exactos: Tim aparecería en ambas consultas aquí, pero el resultado de las UNION
únicas lo enumera una vez. El colega de mi gurú de bases de datos cree que no se debe confiar en este comportamiento. Entonces, para ser más explícitos al respecto, podríamos agregar una WHERE
cláusula a la segunda consulta:
SELECT *
FROM `t1`
LEFT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
UNION
SELECT *
FROM `t1`
RIGHT OUTER JOIN `t2` ON `t1`.`id` = `t2`.`id`
WHERE `t1`.`id` IS NULL;
Por otro lado, si quería ver duplicados por alguna razón, usted podría utilizar UNION ALL
.
FULL OUTER JOIN
. No hay nada de malo en hacer consultas de esa manera y usar UNION para eliminar esos duplicados. Pero para replicar realmente a FULL OUTER JOIN
, necesitamos una de las consultas para ser un anti-join.
UNION
operación eliminará esos duplicados; pero también elimina TODAS las filas duplicadas, incluidas las filas duplicadas que serían devueltas por una UNIÓN COMPLETA EXTERNA. Para emular a FULL JOIN b
, el patrón correcto es (a LEFT JOIN b) UNION ALL (b ANTI JOIN a)
.
El uso de una union
consulta eliminará duplicados, y esto es diferente al comportamiento de full outer join
que nunca elimina ningún duplicado:
[Table: t1] [Table: t2]
value value
------- -------
1 1
2 2
4 2
4 5
Este es el resultado esperado de full outer join
:
value | value
------+-------
1 | 1
2 | 2
2 | 2
Null | 5
4 | Null
4 | Null
Este es el resultado de usar left
y right Join
con union
:
value | value
------+-------
Null | 5
1 | 1
2 | 2
4 | Null
Mi consulta sugerida es:
select
t1.value, t2.value
from t1
left outer join t2
on t1.value = t2.value
union all -- Using `union all` instead of `union`
select
t1.value, t2.value
from t2
left outer join t1
on t1.value = t2.value
where
t1.value IS NULL
Resultado de la consulta anterior que es igual al resultado esperado:
value | value
------+-------
1 | 1
2 | 2
2 | 2
4 | NULL
4 | NULL
NULL | 5
@Steve Chambers : [De los comentarios, ¡muchas gracias!]
Nota: Esta puede ser la mejor solución, tanto para la eficiencia como para generar los mismos resultados que aFULL OUTER JOIN
. Esta publicación de blog también lo explica bien: para citar el Método 2: "Esto maneja las filas duplicadas correctamente y no incluye nada que no debería. Es necesario usarlo enUNION ALL
lugar de simpleUNION
, lo que eliminaría los duplicados que quiero conservar. Esto puede ser significativamente más eficiente en grandes conjuntos de resultados, ya que no hay necesidad de ordenar y eliminar duplicados ".
Decidí agregar otra solución que proviene de la full outer join
visualización y las matemáticas, no es mejor que la anterior pero es más legible:
La combinación externa completa significa
(t1 ∪ t2)
: todo dentrot1
o dentrot2
(t1 ∪ t2) = (t1 ∩ t2) + t1_only + t2_only
: todo en ambost1
yt2
más todos lost1
que no están dentrot2
y más todos lost2
que no están ent1
:
-- (t1 ∩ t2): all in both t1 and t2
select t1.value, t2.value
from t1 join t2 on t1.value = t2.value
union all -- And plus
-- all in t1 that not exists in t2
select t1.value, null
from t1
where not exists( select 1 from t2 where t2.value = t1.value)
union all -- and plus
-- all in t2 that not exists in t1
select null, t2.value
from t2
where not exists( select 1 from t1 where t2.value = t1.value)
FULL OUTER JOIN
. Esta publicación de blog también lo explica bien: para citar el Método 2: "Esto maneja las filas duplicadas correctamente y no incluye nada que no debería. Es necesario usar UNION ALL en lugar de UNION simple, lo que eliminaría los duplicados que quiero mantener. Esto puede ser significativamente más eficiente en grandes conjuntos de resultados, ya que no hay necesidad de ordenar y eliminar duplicados ".
MySql no tiene la sintaxis FULL-OUTER-JOIN. Tienes que emular haciendo LEFT JOIN y RIGHT JOIN de la siguiente manera:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Pero MySql tampoco tiene una sintaxis RIGHT JOIN. De acuerdo con la simplificación de combinación externa de MySql , la combinación derecha se convierte en la combinación izquierda equivalente cambiando t1 y t2 en la cláusula FROM
y ON
en la consulta. Por lo tanto, MySql Query Optimizer traduce la consulta original en lo siguiente:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
Ahora, no hay ningún daño en escribir la consulta original tal como está, pero si tiene predicados como la cláusula WHERE, que es un predicado anterior a la unión o un predicado AND en la ON
cláusula, que es un predicado durante la unión , entonces usted podría querer echar un vistazo al diablo; que está en detalles
MySql query optimizer verifica rutinariamente los predicados si son rechazados por nulo . Ahora, si ha realizado la UNIÓN DERECHA, pero con el predicado WHERE en la columna de t1, entonces podría correr el riesgo de encontrarse con un escenario de rechazo nulo .
Por ejemplo, la siguiente consulta:
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
se traduce a lo siguiente por el Optimizador de consultas
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
WHERE t1.col1 = 'someValue'
UNION
SELECT * FROM t2
LEFT JOIN t1 ON t2.id = t1.id
WHERE t1.col1 = 'someValue'
Por lo tanto, el orden de las tablas ha cambiado, pero el predicado todavía se aplica a t1, pero t1 ahora está en la cláusula 'ON'. Si t1.col1 se define como NOT NULL
columna, entonces esta consulta será rechazada por nulo .
MySql convierte cualquier unión externa (izquierda, derecha, completa) que sea rechazada por nulo en una unión interna.
Por lo tanto, los resultados que podría esperar podrían ser completamente diferentes de lo que está devolviendo MySql. Puede pensar que es un error con RIGHT JOIN de MySql, pero eso no está bien. Así es como funciona el optimizador de consultas MySql. Por lo tanto, el desarrollador a cargo debe prestar atención a estos matices cuando está construyendo la consulta.
En SQLite deberías hacer esto:
SELECT *
FROM leftTable lt
LEFT JOIN rightTable rt ON lt.id = rt.lrid
UNION
SELECT lt.*, rl.* -- To match column set
FROM rightTable rt
LEFT JOIN leftTable lt ON lt.id = rt.lrid
Ninguna de las respuestas anteriores es realmente correcta, porque no siguen la semántica cuando hay valores duplicados.
Para una consulta como (de este duplicado ):
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.Name = t2.Name;
El equivalente correcto es:
SELECT t1.*, t2.*
FROM (SELECT name FROM t1 UNION -- This is intentionally UNION to remove duplicates
SELECT name FROM t2
) n LEFT JOIN
t1
ON t1.name = n.name LEFT JOIN
t2
ON t2.name = n.name;
Si necesita que esto funcione con NULL
valores (que también pueden ser necesarios), utilice el NULL
operador de comparación seguro, en <=>
lugar de =
.
FULL OUTER JOIN
cuando la name
columna es nula. La union all
consulta con patrón anti-unión debería reproducir correctamente el comportamiento de la unión externa, pero la solución más apropiada depende del contexto y de las restricciones que están activas en las tablas.
union all
, pero esa respuesta pierde un patrón anti-unión en la primera o la segunda consulta que mantendrá duplicados existentes pero evita agregar nuevos. Dependiendo del contexto, otras soluciones (como esta) podrían ser más apropiadas.
Se modificó la consulta de shA.t para obtener más claridad:
-- t1 left join t2
SELECT t1.value, t2.value
FROM t1 LEFT JOIN t2 ON t1.value = t2.value
UNION ALL -- include duplicates
-- t1 right exclude join t2 (records found only in t2)
SELECT t1.value, t2.value
FROM t1 RIGHT JOIN t2 ON t1.value = t2.value
WHERE t2.value IS NULL
¿Qué dijiste sobre la solución Cross join ?
SELECT t1.*, t2.*
FROM table1 t1
INNER JOIN table2 t2
ON 1=1;
select (select count(*) from t1) * (select count(*) from t2))
filas en el conjunto de resultados.
SELECT
a.name,
b.title
FROM
author AS a
LEFT JOIN
book AS b
ON a.id = b.author_id
UNION
SELECT
a.name,
b.title
FROM
author AS a
RIGHT JOIN
book AS b
ON a.id = b.author_id
También es posible, pero debe mencionar los mismos nombres de campo en select.
SELECT t1.name, t2.name FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT t1.name, t2.name FROM t2
LEFT JOIN t1 ON t1.id = t2.id
Arreglo la respuesta, y las obras incluyen todas las filas (según la respuesta de Pavle Lekic)
(
SELECT a.* FROM tablea a
LEFT JOIN tableb b ON a.`key` = b.key
WHERE b.`key` is null
)
UNION ALL
(
SELECT a.* FROM tablea a
LEFT JOIN tableb b ON a.`key` = b.key
where a.`key` = b.`key`
)
UNION ALL
(
SELECT b.* FROM tablea a
right JOIN tableb b ON b.`key` = a.key
WHERE a.`key` is null
);
tablea
que no coinciden tableb
y viceversa. Intenta hacerlo UNION ALL
, lo que solo funcionaría si estas dos tablas tienen columnas ordenadas de manera equivalente, lo que no está garantizado.
Responder:
SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id;
Se puede recrear de la siguiente manera:
SELECT t1.*, t2.*
FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
LEFT JOIN t1 ON t1.id = tmp.id
LEFT JOIN t2 ON t2.id = tmp.id;
El uso de una respuesta UNION o UNION ALL no cubre el caso límite donde las tablas base tienen entradas duplicadas.
Explicación:
Hay un caso extremo que UNION o UNION ALL no pueden cubrir. No podemos probar esto en mysql ya que no admite FULL OUTER JOINs, pero podemos ilustrar esto en una base de datos que sí lo admite:
WITH cte_t1 AS
(
SELECT 1 AS id1
UNION ALL SELECT 2
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 6
),
cte_t2 AS
(
SELECT 3 AS id2
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 6
)
SELECT * FROM cte_t1 t1 FULL OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2;
This gives us this answer:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
La solución UNION:
SELECT * FROM cte_t1 t1 LEFT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
UNION
SELECT * FROM cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
Da una respuesta incorrecta:
id1 id2
NULL 3
NULL 4
1 NULL
2 NULL
5 5
6 6
La solución UNION ALL:
SELECT * FROM cte_t1 t1 LEFT OUTER join cte_t2 t2 ON t1.id1 = t2.id2
UNION ALL
SELECT * FROM cte_t1 t1 RIGHT OUTER JOIN cte_t2 t2 ON t1.id1 = t2.id2
También es incorrecto.
id1 id2
1 NULL
2 NULL
5 5
6 6
6 6
6 6
6 6
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
Mientras que esta consulta:
SELECT t1.*, t2.*
FROM (SELECT * FROM t1 UNION SELECT name FROM t2) tmp
LEFT JOIN t1 ON t1.id = tmp.id
LEFT JOIN t2 ON t2.id = tmp.id;
Da lo siguiente:
id1 id2
1 NULL
2 NULL
NULL 3
NULL 4
5 5
6 6
6 6
6 6
6 6
El orden es diferente, pero por lo demás coincide con la respuesta correcta.
UNION ALL
solución. Además, presenta una solución UNION
que sería más lenta en tablas de origen grandes debido a la desduplicación requerida. Finalmente, no se compilaría, porque el campo id
no existe en la subconsulta tmp
.
UNION ALL
solución: ... también es incorrecta". El código que presente deja de lado la exclusión de intersección de la unión derecha ( where t1.id1 is null
) que debe proporcionarse en UNION ALL
. Es decir, su solución supera a todas las demás, solo cuando una de esas otras soluciones se implementa incorrectamente. Sobre "ternura", punto tomado. Eso fue gratuito, mis disculpas.
El estándar SQL dice full join on
que las inner join on
filas de la union all
tabla izquierda no coinciden filas extendidas por nulos union all
filas de tabla derecha extendidas por nulos. Es decir, inner join on
filas union all
filas left join on
pero no inner join on
union all
filas right join on
pero no inner join on
.
Es decir, left join on
filas union all
right join on
filas no adentro inner join on
. O si sabe que su inner join on
resultado no puede tener un valor nulo en una columna de tabla derecha en particular, entonces las " right join on
filas no incluidas inner join on
" son filas right join on
con la on
condición extendida por and
esa columna is null
.
Es decir, filas igualmente right join on
union all
apropiadas left join on
.
De Cuál es la diferencia entre “INNER JOIN” y “OUTER JOIN”? :
(SQL Standard 2006 SQL / Foundation 7.7 Reglas de sintaxis 1, Reglas generales 1 b, 3 c & d, 5 b.)