SQL - 多个多对多关系过滤SELECT

时间:2012-03-24 05:53:23

标签: mysql sql many-to-many inner-join

这些是我的表格:

Cadastros (id, nome)
Convenios (id, nome)
Especialidades (id, nome)
Facilidades (id, nome)

连接表:

cadastros_convenios
cadastros_especialidades
cadastros_facilidades

我要查询的表:Cadastros

我正在使用MySQL。

系统将允许用户选择多个“Convenios”,“Especialidades”和“Facilidades”。将每个表视为不同类型的“标签”。用户将能够选择每种类型的多个“标签”。

我想要的是只选择Cadastros表中与所提供的3个不同表中的所有“标签”相关的结果。请注意,这不是“OR”关系。它应该只返回Cadastros中的行,如果它具有匹配的链接表行,则提供每个“标记”。

这是我到目前为止所做的:

SELECT Cadastro.*, Convenio.* FROM Cadastros AS Cadastro
    INNER JOIN cadastros_convenios AS CadastrosConvenio ON(Cadastro.id = CadastrosConvenio.cadastro_id)
INNER JOIN Convenios AS Convenio ON (CadastrosConvenio.convenio_id = Convenio.id AND Convenio.id IN(2,3))
    INNER JOIN cadastros_especialidades AS CadastrosEspecialidade ON (Cadastro.id = CadastrosEspecialidade.cadastro_id)
INNER JOIN Especialidades AS Especialidade ON(CadastrosEspecialidade.especialidade_id = Especialidade.id AND Especialidade.id IN(1))
    INNER JOIN cadastros_facilidades AS CadastrosFacilidade ON (Cadastro.id = CadastrosFacilidade.cadastro_id)
INNER JOIN Facilidades AS Facilidade ON(CadastrosFacilidade.facilidade_id = Facilidade.id AND Facilidade.id IN(1,2))
GROUP BY Cadastro.id
HAVING COUNT(*) = 5;

我正在使用HAVING子句尝试根据显示的次数过滤结果(意味着它已成功“INNER JOINED”的次数)。所以在每种情况下,计数应该等于我添加的不同过滤器的数量。因此,如果我添加3个不同的“标签”,则计数应为3.如果我添加5个不同的标签,则计数应为5,依此类推。它适用于单个关系(一对内连接)。当我添加其他2个关系时,它开始失去控制。

修改

以下是我认为有效的方法(感谢 @Tomalak 指出带有子查询的解决方案):

    SELECT Cadastro.*, Convenio.*, Especialidade.*, Facilidade.* FROM Cadastros AS Cadastro

    INNER JOIN cadastros_convenios AS CadastrosConvenio ON(Cadastro.id = CadastrosConvenio.cadastro_id)
INNER JOIN Convenios AS Convenio ON (CadastrosConvenio.convenio_id = Convenio.id)

    INNER JOIN cadastros_especialidades AS CadastrosEspecialidade ON (Cadastro.id = CadastrosEspecialidade.cadastro_id)
INNER JOIN Especialidades AS Especialidade ON(CadastrosEspecialidade.especialidade_id = Especialidade.id)

    INNER JOIN cadastros_facilidades AS CadastrosFacilidade ON (Cadastro.id = CadastrosFacilidade.cadastro_id)
INNER JOIN Facilidades AS Facilidade ON(CadastrosFacilidade.facilidade_id = Facilidade.id)

WHERE
(SELECT COUNT(*) FROM cadastros_convenios WHERE cadastro_id = Cadastro.id AND convenio_id IN(1, 2, 3)) = 3
AND
(SELECT COUNT(*) FROM cadastros_especialidades WHERE cadastro_id = Cadastro.id AND especialidade_id IN(3)) = 1
AND
(SELECT COUNT(*) FROM cadastros_facilidades WHERE cadastro_id = Cadastro.id AND facilidade_id IN(2, 3)) = 2

GROUP BY Cadastro.id

但我很担心表现。看起来WHERE子句中的这3个子查询将被过度执行......

另一种解决方案

只有在以前的连接成功时才加入后续表(如果没有行与其中一个连接匹配,则下一个连接将加入空结果集)(感谢 @DRapp 这一个)

SELECT STRAIGHT_JOIN
  Cadastro.*
   FROM 
  ( SELECT Qualify1.cadastro_id
       from 
             ( SELECT cc1.cadastro_id
                  FROM cadastros_convenios cc1
                  WHERE cc1.convenio_id IN (1, 2, 3)
                  GROUP by cc1.cadastro_id 
                  having COUNT(*) = 3 ) Qualify1

             JOIN
             ( SELECT ce1.cadastro_id
                  FROM cadastros_especialidades ce1
                  WHERE ce1.especialidade_id IN( 3 )
                  GROUP by ce1.cadastro_id
                  having COUNT(*) = 1 ) Qualify2
                ON (Qualify1.cadastro_id = Qualify2.cadastro_id)

                  JOIN 
                  ( SELECT cf1.cadastro_id
                       FROM cadastros_facilidades cf1
                       WHERE cf1.facilidade_id IN (2, 3)
                       GROUP BY cf1.cadastro_id 
                       having COUNT(*) = 2 ) Qualify3
                  ON (Qualify2.cadastro_id = Qualify3.cadastro_id) ) FullSet
  JOIN Cadastros AS Cadastro
     ON FullSet.cadastro_id = Cadastro.id

     INNER JOIN cadastros_convenios AS CC 
        ON (Cadastro.id = CC.cadastro_id)
        INNER JOIN Convenios AS Convenio
           ON (CC.convenio_id = Convenio.id)

     INNER JOIN cadastros_especialidades AS CE 
        ON (Cadastro.id = CE.cadastro_id)
        INNER JOIN Especialidades AS Especialidade
           ON (CE.especialidade_id = Especialidade.id)

     INNER JOIN cadastros_facilidades AS CF
        ON (Cadastro.id = CF.cadastro_id)
        INNER JOIN Facilidades AS Facilidade
           ON (CF.facilidade_id = Facilidade.id)
GROUP BY Cadastro.id

2 个答案:

答案 0 :(得分:1)

强调我的

  

“它应该只返回来自Cadastros 的行,如果它有一个匹配的行,则提供所有”标记“。

“哪里有匹配的行”-problems可以通过EXISTS轻松解决。

编辑经过一番澄清,我发现使用EXISTS是不够的。比较实际的行数是必要的:

SELECT 
  *
FROM
  Cadastros c
WHERE
  (SELECT COUNT(*) FROM cadastros_facilidades WHERE cadastro_id = c.id AND id IN (2,3)) = 2 
  AND
  (SELECT COUNT(*) FROM cadastros_especialidades WHERE cadastro_id = c.id AND id IN (1)) = 1
  AND
  (SELECT COUNT(*) FROM cadastros_facilidades WHERE cadastro_id = c.id AND id IN (1,2)) = 2

此查询的链接表上的索引应为(cadastro_id, id)

答案 1 :(得分:1)

根据表(记录)的大小,基于WHERE的子查询,在每一行上运行测试都会显着地影响性能。我重组了它可能更好帮助,但只有你能够确认。这里的前提是让第一个表基于获得满足条件的不同ID,将THAT设置为下一个限定符标准...加入FINAL集。确定之后,使用THAT加入主表及其后续链接以获取您期望的详细信息。您还有一个ID的整体分组,它将消除支持详细信息表中找到的所有其他嵌套条目。

所有这些,让我们来看看这个场景。从预期具有最低结果集的表开始加入下一个和下一个。如果cadastros_convenios的ID符合所有条件,包括ID为1-100,那么,我们在MOST知道,我们将有100个ID。

现在,这100个条目立即加入到第二个合格标准......其中,说它只匹配其他...为简单起见,我们现在匹配100个中的50个。

最后,根据合格的50来加入第3个限定符并获得30个条目。因此,在这3个查询中,您现在可以过滤到30个条目,并预先处理所有符合条件的条件。现在,加入Cadastros,然后加入后续表格,了解详细信息,仅限于30名合格人员。

由于您的原始查询最终会为标准尝试每个“ID”,为什么不预先使用一个查询对其进行预先鉴定并获得那些命中,然后继续。

SELECT STRAIGHT_JOIN
      Cadastro.*, 
      Convenio.*, 
      Especialidade.*, 
      Facilidade.* 
   FROM 
      ( SELECT Qualify1.cadastro_id
           from 
                 ( SELECT cc1.cadastro_id
                      FROM cadastros_convenios cc1
                      WHERE cc1.convenio_id IN (1, 2, 3)
                      GROUP by cc1.cadastro_id 
                      having COUNT(*) = 3 ) Qualify1

                 JOIN
                 ( SELECT ce1.cadastro_id
                      FROM cadastros_especialidades ce1
                      WHERE ce1.especialidade_id IN( 3 )
                      GROUP by ce1.cadastro_id
                      having COUNT(*) = 1 ) Qualify2
                    ON Qualify1.cadastro_id = Qualify2.cadastro_id

                      JOIN 
                      ( SELECT cf1.cadastro_id
                           FROM cadastros_facilidades cf1
                           WHERE cf1.facilidade_id IN (2, 3)
                           GROUP BY cf1.cadastro_id 
                           having COUNT(*) = 2 ) Qualify3
                      ON Qualify2.cadastro_id = Qualify3.cadastro_id ) FullSet
      JOIN Cadastros AS Cadastro
         ON FullSet.Cadastro_id = Cadastro.Cadastro_id

         INNER JOIN cadastros_convenios AS CC 
            ON Cadastro.id = CC.cadastro_id
            INNER JOIN Convenios AS C
               ON CC.convenio_id = C.id

         INNER JOIN cadastros_especialidades AS CE 
            ON Cadastro.id = CE.cadastro_id
            INNER JOIN Especialidades AS E
               ON CE.especialidade_id = E.id

         INNER JOIN cadastros_facilidades AS CF
            ON Cadastro.id = CF.cadastro_id
            INNER JOIN Facilidades AS F 
               ON CF.facilidade_id = F.id