2011-01-12 7 views
5

Estoy buscando una manera de filtrar las filas de un SELECT de una tabla basada en ciertos valores en filas de otra tabla.eliminando filas de un SELECT basado en columnas en una tabla diferente

Estoy experimentando con la siguiente estructura de ejemplo. Tengo una tabla de contenido de publicación de blog (una fila por publicación de blog) y otra tabla de metadatos sobre las publicaciones (una fila por par de clave-valor; cada fila con una columna que la asocia con una publicación de blog; muchas filas por entrada en el blog). Quiero sacar una fila de posts solo si no hay filas en metadata donde metadata.pid=posts.pid AND metadata.k='optout'. Es decir, para la siguiente estructura de ejemplo, solo quiero recuperar la fila posts.id=1.

(Basado en lo que he tratado) JOIN s no terminan quitando los puestos que tienen algunos metadatos donde metadata.k='optout', porque la otra fila de metadatos para que pid significa que lo hace en los resultados.

mysql> select * from posts; 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 1 | Foo | Some content | 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
3 rows in set (0.00 sec) 

mysql> select * from metadata; 
+------+-----+--------+-----------+ 
| mdid | pid | k  | v   | 
+------+-----+--------+-----------+ 
| 1 | 1 | date | yesterday | 
| 2 | 1 | thumb | img.jpg | 
| 3 | 2 | date | today  | 
| 4 | 2 | optout | true  | 
| 5 | 3 | date | tomorrow | 
| 6 | 3 | optout | true  | 
+------+-----+--------+-----------+ 
6 rows in set (0.00 sec) 

Una subconsulta me puede dar la inversa de lo que quiero:

mysql> select posts.* from posts where pid = any (select pid from metadata where k = 'optout'); 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
2 rows in set (0.00 sec) 

... pero utilizando pid != any (...) me da todos los 3 de las filas de mensajes, hacer que todos y cada uno pid tiene una fila de metadatos donde k!='optout'.

Respuesta

8

Suena como que quiere hacer un LEFT JOIN y luego verifica los resultados en los que el valor de la tabla unida es NULL, lo que indica que no existe tal registro unido.

Por ejemplo:

SELECT * FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

Esto seleccionará cualquier fila de la tabla posts para el que no existe metadata fila correspondiente con un valor de k = 'optout'.

corregir: Vale la pena señalar que esta es una propiedad clave de una combinación de la izquierda y no funcionaría con una unión regular; una combinación a la izquierda siempre devolverá los valores de la primera tabla, incluso si no existen valores coincidentes en las tablas combinadas, lo que le permite realizar selecciones en función de la ausencia de esas filas.

editar 2: Vamos a aclarar lo que está pasando aquí con respecto a la LEFT JOIN frente al JOIN (que me refiero como un INNER JOIN para mayor claridad, pero es intercambiable en MySQL).

suponga que ejecuta cualquiera de estas dos preguntas:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON posts.pid = metadata.pid; 

o

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON posts.pid = metadata.pid; 

Ambas consultas producir el siguiente conjunto de resultados:

+-----+-------+--------------+------+-------+-----------+ 
| pid | title | content  | mdid | k  | v   | 
+-----+-------+--------------+------+-------+-----------+ 
| 1 | Foo | Some content | 1 | date | yesterday | 
| 1 | Foo | Some content | 2 | thumb | img.jpg | 
+-----+-------+--------------+------+-------+-----------+ 

Ahora, supongamos que modificamos el consulta para agregar los criterios adicionales para "optout" que se mencionó.En primer lugar, la INNER JOIN:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

Como era de esperar, esto no devolvió resultados:

Empty set (0.00 sec) 

Ahora, el cambio de eso a un LEFT JOIN:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

esto producir un conjunto de resultados:

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

La diferencia entre una y una INNER JOINLEFT JOIN es que un INNER JOIN sólo devolverá un resultado si se unió a las filas de ambas tablas partido. En una LEFT JOIN, las filas coincidentes de la primera tabla SIEMPRE será devuelto, independientemente de si se encuentra cualquier cosa para unirse a. En muchos casos, no importa cuál use, pero es importante elegir el correcto para no obtener resultados inesperados en el futuro.

Así que en este caso, la consulta sugerido de:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

devolverá el mismo conjunto de resultados que el anterior:

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

Esperemos que lo aclara! Las uniones son una gran cosa para aprender, tener una comprensión completa de cuándo usar cuál es algo muy bueno.

+0

Así que vamos a ver si me sale esto ... para un post con optout, la subconsulta coincide con una fila de metadatos, por lo que el metadata.mdid no es nulo, por lo no se selecciona. Pero una publicación sin optout, la subconsulta no coincide con una fila, por lo que el lado derecho se rellena con nulos, por lo que la cláusula where es verdadera. – alxndr

+1

He agregado otra sección a la respuesta relacionada con el funcionamiento de la unión, que debería borrar cualquier área gris con ella. ¡Espero que ayude! – futureal

3

Puede intentar algo así como

select p.* 
from posts p 
where NOT EXISTS (
         select pid 
         from metadata 
         where k = 'optout' 
         and  pid = p.pid 
        ) 
+0

Guau, gracias. Voy a leer sobre NO EXISTE. – alxndr

+0

FYI dar la marca de verificación a la otra causa 36000 respuesta en las filas de la izquierda se unen es 0,1 segundos más rápido ... – alxndr

+1

En un pequeño conjunto de resultados de las dos consultas deben realizar sobre la misma. Sin embargo, cuando se utiliza '' NO EXISTS' o EXISTS' con una subconsulta, será necesario para calcular la tabla de subconsulta y copiarlo en una tabla temporal. Así que a medida que crece su conjunto de resultados, puede convertirse en un cuello de botella de alto rendimiento. No lo evitaría del todo, a veces hace las cosas mucho más fáciles de leer y entender que una combinación complicada; solo hay que tener en cuenta el tipo de conjunto de resultados que eventualmente puede producir. – futureal

Cuestiones relacionadas