使用SQL,如何在单个表中找到多对多的非匹配关系?

时间:2014-12-21 14:39:26

标签: sql many-to-many self-join

我有一个数据库,除其他外,记录成分之间的反应结果。它目前由以下三个表格构成:

| Material       |
|----------------|
| id : Integer   |
| name : Varchar |

| Reaction        |
|-----------------|
| id : Integer    |
| <other details> |

| Ingredient            |
|-----------------------|
| material_id : Integer |
| reaction_id : Integer |
| quantity : Real       |

这映射了材料和反应之间的多对多关系。

我想运行一个返回每对不会产生反应的材料的查询。 (即每对( x y ),使得没有任何反应完全使用 x y 没有其他材料。)在其他情况下,我会通过LEFT JOIN进入中间表,然后查找NULL reaction_id s。在这种情况下,我通过在材质表和它自身上进行CROSS JOIN来获得对,但是我不确定如何(或者是否)在两个材质上做两个LEFT JOIN可以起作用。

如何做到这一点?

我最感兴趣的是一种通用的SQL方法,但我目前正在使用SQLite3和SQLAlchemy。我可以选择将数据库移动到PostgreSQL,但SQLite是首选。

1 个答案:

答案 0 :(得分:2)

使用cross join生成列表,然后删除同一反应中的对。

select m.id, m2.id as id2
from materials m cross join
     materials m2
where not exists (select 1
                  from ingredient i join
                       ingredient i2
                       on i.reaction_id = i2.reaction_id and
                          i.material_id = m.id and
                          i2.material_id = m2.id
                 );

虽然此查询看起来很复杂,但它本质上是您问题的直接翻译。 where条款是说每种材料的相同反应没有两种成分。

为了提高性能,您需要ingredient(reaction_id, material_id)上的索引。

编辑:

如果您愿意,可以使用existsleft join where的情况下执行此操作:

select m.id, m2.id
from materials m cross join
     materials m2 left join
     ingredients i
     on i.material_id = m.id left join
     ingredients i2
     on i2.material_id = m2.id and
        i2.reaction_id = m2.reaction_id
where i2.reaction_id is null;