SQL - фильтрация множественных отношений «многие ко многим» SELECT - PullRequest
1 голос
/ 24 марта 2012

Это мои таблицы:

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

И таблицы объединения:

cadastros_convenios
cadastros_especialidades
cadastros_facilidades

Таблица, к которой я обращаюсь: Кадастр

Я используюMySQL.

Система позволит пользователю выбрать несколько «Convenios», «Especialidades» и «Facilidades».Думайте о каждой из этих таблиц как о различном типе «тега».Пользователь сможет выбрать несколько «тегов» каждого типа.

Я хочу выбрать только те результаты в таблице Кадастров, которые связаны со ВСЕМИ «тегами» из 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, чтобы попытаться отфильтровать результаты в зависимости от того, сколько раз он показывает (то есть, сколько раз он был успешно «ВНУТРЕННИЙ»).Таким образом, в каждом случае количество должно быть равно количеству различных фильтров, которые я добавил.Так что, если я добавлю 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

Но я беспокоюсь о производительности.Похоже, что эти 3 подзапроса в предложении WHERE будут перевыполнены ...

Другое решение

Он объединяет последующие таблицы, только если предыдущие присоединениябыли успешными (если ни одна строка не соответствует одному из объединений, следующие объединения будут объединяться с пустым набором результатов) (спасибо @ 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 ]

1 голос
/ 24 марта 2012

В зависимости от размера таблиц (записей), основанных на WHERE подзапросов, выполнение теста для каждой строки МОЖЕТ ЗНАЧИТЕЛЬНО повлиять на производительность. Я реструктурировал это, что МОЖЕТ помочь лучше, но только вы сможете подтвердить. Предполагается, что первая таблица должна основываться на получении различных идентификаторов, соответствующих критериям, соединить набор THAT со следующими критериями квалификатора ... присоединиться к набору FINAL. Как только это будет определено, используйте THAT, чтобы присоединиться к вашей главной таблице и ее последующим ссылкам, чтобы получить детали, которые вы ожидаете. У вас также была общая группа по идентификатору, которая исключит все другие вложенные записи, как указано в таблице сведений о поддержке.

Все, что сказал, давайте посмотрим на этот сценарий. Начните с таблицы, которая, как ожидается, будет иметь самый низкий набор результатов, чтобы присоединиться к следующему и следующему. если cadastros_convenios имеет идентификаторы, которые соответствуют всем критериям, включают идентификаторы 1-100, отлично, мы знаем в МОСТе, у нас будет 100 идентификаторов.

Теперь эти 100 записей немедленно присоединяются ко 2-му квалификационному критерию ... из которых, скажем, он соответствует только другим ... для простоты, мы теперь подобраны по 50 из 100.

Наконец, ПРИСОЕДИНЯЙТЕСЬ к 3-му квалификатору на основе 50 квалификаций, и вы получите 30 записей. Итак, в рамках этих 3 запросов вы теперь отфильтрованы до 30 записей со всеми квалификационными критериями, обработанными заранее. ТЕПЕРЬ, присоединитесь к Кадастру и последующим таблицам для деталей, основанных ТОЛЬКО на 30, которые квалифицировались.

Поскольку ваш исходный запрос в конечном итоге будет проверять КАЖДЫЙ «идентификатор» критериев, почему бы не предварительно квалифицировать его с ОДНЫМ запросом и получить только те, которые попали, а затем двигаться дальше.

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
1 голос
/ 24 марта 2012

Акцент мой

"Он должен возвращать строку из Cadastros только в том случае, если в ней есть соответствующая строка для КАЖДОГО предоставленного« тега »."

«где есть совпадающая строка» - проблемы легко решаются с помощью 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) для этого запроса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...