Tengo un requisito para permitir el filtrado de artículos por ausencia de etiquetas.
Por ejemplo, tengo:
articles.id A B tags.id 1 2 3 4 articles_tags.article_id articles_tags.tag_id A 1 A 2 B 2 B 4 Ahora, tengo una lista de identificadores de etiquetas, por ejemplo, (3, 4) . Me gustaría una consulta que devuelva una lista de artículos a los que les falta alguna etiqueta de la lista. En este ejemplo, devolvería A y B porque ninguno tiene la etiqueta 3. Si envío (1) , debería devolver solo B porque A tiene la etiqueta 1.
Usaría el tipo de matriz PostgreSQL para manejar esto.
Ignorando las tablas de articles y tags para simplificar:
with arraystyle as ( select article_id, array_agg(tag_id) as tagarray from articles_tags group by article_id ) select * from arraystyle; article_id | tagarray ------------+----------- B | {2,4} A | {1,2} (2 rows) Tener el tagarray en este formato le permite usar las funciones y operadores de matriz . Uno de los operadores de contención, @> y <@ , es lo que necesita en su forma negada.
with arraystyle as ( select article_id, array_agg(tag_id) as tagarray from articles_tags group by article_id ) select * from arraystyle where not tagarray @> '{1,3}'; article_id | tagarray ------------+---------- B | {2,4} A | {1,2} (2 rows) with arraystyle as ( select article_id, array_agg(tag_id) as tagarray from articles_tags group by article_id ) select * from arraystyle where not tagarray @> '{1}'; article_id | tagarray ------------+---------- B | {2,4} (1 row)Puede usar not exists y una consulta agregada:
select a.* from articles a where ( select count(*) from article_tags at where at.article_id = a.id and at.tag_id in (3, 4) ) < 2 Esto supone que no hay duplicados en article_tags(article_id, tag_id) (como se muestra en sus datos de muestra).
También puede expresar esto con agregación externa y filtrado en una cláusula de having :
select a.* from articles a inner join article_tags at on at.article_id = a.id group by a.id having count(*) filter(where at.tag_id in (3, 4)) < 2Prueba esto:
select distinct article_id from ( select t1.id as "article_id", t2.id as "articles_tags" from articles t1 cross join tags t2 where t2.id in (3,4) except ( select article_id, tag_id from articles_tags where tag_id in (3,4) ) ) tabEsto también se manejará en caso de entradas duplicadas de combinaciones.