2009-09-10 16 views
12

que tienen una tabla de referencia cruzada que tiene este aspecto:filas SQL seleccionar donde el valor de una columna es común a través de otra columna criterios

id document_id subject_id 
1 8   21 
2 5   17 
3 5   76 
4 7   88 
5 9   17 
6 9   76 
7 2   76 

Coincide con los documentos a los sujetos. Los documentos pueden ser miembros de más de un tema. Quiero devolver las filas de esta tabla donde un documento dado coincide con todos los temas en un conjunto dado. Por ejemplo, dado el conjunto de temas:

(17,76)

quiero devolver sólo las filas de los documentos que se ajustan a todos los temas en los que el conjunto (al menos) en algún lugar de la tabla de referencias cruzadas. El conjunto de salida deseada dado el conjunto anterior sería:

id document_id subject_id 
2 5   17 
3 5   76 
5 9   17 
6 9   76 

en cuenta que la última fila de la tabla no se devuelve porque ese documento sólo coincide con una de las materias requeridas.

¿Cuál es la forma más simple y eficiente de consultar esto en SQL?

+0

Sería genial saber cómo está proporcionando los parámetros a la consulta. Veo una respuesta, aunque perfectamente, solo funcionará para exactamente 2 valores en el conjunto de parámetros. Si puede limitar el número de parámetros, por ejemplo, 10 max, entonces se trata de una conversación. Si necesita que la aplicación sea flexible, las sugerencias serán diferentes. – Eugene

+0

Gracias, la entrada es básicamente "elegir cualquier número de temas" para que el conjunto de identificaciones de sujeto pueda crecer tan grande como el número de sujetos (en teoría). – Maciek

Respuesta

27

que asumen que la clave natrual de esta tabla es id_documento + subject_id, y que la identificación es un sustituto; IOW, document_id y subject_id son únicos. Como tal, voy a pretender que no existe y que una restricción única está en la clave natural.

Comencemos con lo obvio.

SELECT document_id, subject_id 
    FROM document_subjects 
WHERE subject_id IN (17,76) 

que le consigue todo lo que quiere más cosas que no quiere. Entonces todo lo que tenemos que hacer es filtrar las otras cosas. Las "otras cosas" son grupos de filas que tienen un recuento que no es igual al recuento de los temas deseados.

SELECT document_id 
    FROM document_subjects 
WHERE subject_id IN (17,76) 
GROUP BY document_id 
HAVING COUNT(*) = 2 

Tenga en cuenta que subject_id se elimina porque no participa en la agrupación. Dando un paso más, voy a agregar una tabla imaginaria llamada subjects_i_want que contiene N filas de temas que deseas.

SELECT document_id 
    FROM document_subjects 
WHERE subject_id IN (SELECT subject_id FROM subjects_i_want) 
GROUP BY document_id 
HAVING COUNT(*) = (SELECT COUNT(*) FROM subjects_i_want) 

Obviamente subjects_i_want podrían ser intercambiado por otro sub consulta, tabla temporal, o lo que sea. Pero, una vez que tenga esta lista de document_id, puede usarla en una subselección de una consulta más grande.

SELECT document_id, subject_id, ... 
    FROM document_subjects 
WHERE document_id IN(
     SELECT document_id 
      FROM document_subjects 
      WHERE subject_id IN (SELECT subject_id FROM subjects_i_want) 
      GROUP BY document_id 
     HAVING COUNT(*) = (SELECT COUNT(*) FROM subjects_i_want)) 

O lo que sea.

+0

Impresionante, gracias. – Maciek

+1

+1 muy agradable, Alex. Últimamente noté algunas variaciones de esta pregunta, y esta es la solución general más claramente presentada que he visto hasta ahora. – Matt

+0

+1, muy bien y me ayudó, sería mejor si el recuento (*) realizado i Tener estaría en entradas distintas, ya que eliminaría la posibilidad de que también se consideren datos duplicados; preferiblemente COUNT (DISTINCT subject_id) en lugar de COUNT (*) –

1

Esa es una pregunta muy interesante.

Asumo que le gustaría una consulta más generalizada, pero esto es lo que yo haría en el caso en el que siempre tiene el mismo número de sujetos (por ejemplo dos):

SELECT T.id, T.document_id, T.subject_id 
    FROM table T 
     INNER JOIN table T1 ON T.document_id = T1.document_id AND T1.subject_ID = 17 
     INNER JOIN table T2 ON T.document_id = T2.document_id AND T2.subject_ID = 76    

Por supuesto, podrías agregar otra UNIÓN INTERNA para agregar otra identificación de sujeto. Pero admito que no es una muy buena solución general.

+0

D'oh, estoy buscando una solución que pueda coincidir con un número arbitrario de temas. – Maciek

0
select document_id from table1 
where subject_id in (17, 76) 
group by document_id 
having count(distinct subject_id) = 2 
2

Usando Oracle (o cualquier base de datos que permita la cláusula with). Esto permite la definición de los valores subject_id exactamente una vez.

with t as (select distinct document_id from table1 where subject_id in (17,76)) 
select document_id from table1 where subject_id in (select subject_id from t) 
group by document_id 
having count(*) = (select count (*) from t); 
+0

Encontré que esta respuesta es la más útil ya que también se aplica a PostgreSQL. – ramhiser

Cuestiones relacionadas