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

标签 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 个不同表中的所有“标签”相关的结果。请注意,这不是“或”关系。如果提供的每个“标签”都有匹配的链接表行,它应该只从 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

最佳答案

强调我的

"It should only return the row from Cadastros if it has a matching row for EVERY "tag" provided."

“哪里有匹配行”- 使用 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)

关于mysql - SQL - 多个多对多关系过滤 SELECT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9849510/

相关文章:

mysql - sql : finding how many objects were stolen by a player?

具有逗号分隔值的两组的Mysql交集

mysql - 如果 ID 在另一个表中不匹配,则返回 NULL

c# - Entity Framework GroupBy 使用 mySQL 取最老的

mysql - 为什么我看到 "COLLATION ' xxx' is not valid for CHARACTER SET 'yyy' "

sql - 如何按天分组并仍然返回日期时间?

vb.net - 如何为多对多关系插入实体? ( Entity Framework )

python-2.7 - 将关系设置为多对多关系

MySQL : error in your SQL syntax | LIMIT 0, 25

mysql - mysql如何处理不存在的索引值?