Сложный SQL-запрос соединения согласно заданной структуре - PullRequest
0 голосов
/ 12 марта 2019

У меня есть две таблицы posts и category_relationships. Мне нужно получить из этого немного сложные результаты в соответствии со следующей логикой.

таблица сообщений

id   |      post  
-----|------------------------------|
1000 | Lorem ipsum dolor sit amet   | 
1001 | consectetur adipiscing elit  | 
1002 | sed do eiusmod tempor  ut    | 
1004 | abore et dolore magna aliqua | 

таблица категорийных отношений

post_id    cat_id  
---------|---------|
   1000  |   201   | 
   1000  |   202   | 
   1000  |   211   | 
   1001  |   201   | 
   1001  |   211   | 
   1002  |   202   | 
   1002  |   212   | 

Сначала я попытаюсь объяснить структуру моей категории. У меня есть три уровня структуры, как показано ниже. (для простоты, я не включаю сюда ни одну таблицу категорий)

flight [level 1] [ID : 100]
    - class      [level 2] [ID : 200]
        -- economy  [level 3] [ID : 201]
        -- business [level 3] [ID : 202]
        -- first    [level 3] [ID : 203]
    - alliance   [level 2] [ID : 210]
        -- star     [level 3] [ID : 211]
        -- oneworld [level 3] [ID : 212]
        -- skyteam  [level 3] [ID : 213]

Теперь алгоритм:

Мне нужно, чтобы все сообщения были помечены как flight категория или любой ребенок / дети в соответствии со следующими правилами.

Мне нужно исключить сообщения, помеченные как economy (ID: 201);

  1. Тем не менее, он должен быть в наборе результатов, если один из его братьев и сестер (business или first) был помечен.
  2. Не следует учитывать посты, в которых alliance или его дочерние / дочерние элементы были помечены, ЕСЛИ economy также помечены в тех же сообщениях

Обратите внимание, что я могу получить идентификаторы категорий в соответствии со структурой и использовать их в запросе.

Мой подход пока:

SELECT posts.ID FROM posts  
LEFT JOIN category_relationships AS tt1 ON (posts.ID = tt1.post_id) 
WHERE tt1.cat_id IN (100,200,201,202,203,210,211,212,213) 
AND posts.ID NOT IN ( SELECT post_id FROM category_relationships WHERE cat_id IN (201) ) 

Но здесь проблема в том, что он удаляет все сообщения, помеченные как economy. Однако оно не соответствует правилу № 1.

Идеальный набор результатов будет как ниже;

1000 - rule number 1
1002 - anyway no `economy` tagged

Не включая:

1001 - rule number 2
1004 - no tagged

Надеюсь, у вас есть четкое представление о проблеме, и любая помощь будет очень благодарна.

Ответы [ 3 ]

1 голос
/ 12 марта 2019

Итак, одно из ваших условий: «У него нет категории 201 ИЛИ у него есть категория 202 или 203». Вы пропустили это условие ИЛИ OR tt1.cat_id IN (202, 203):

SELECT DISTINCT posts.ID FROM posts  
JOIN category_relationships AS tt1 ON (posts.ID = tt1.post_id) 
WHERE tt1.cat_id IN (100,200,201,202,203,210,211,212,213) 
AND (
    posts.ID NOT IN ( SELECT post_id FROM category_relationships WHERE cat_id IN (201) ) 
  OR
    tt1.cat_id IN (202, 203)
  )

Обратите внимание, что ваше левое соединение не имеет смысла и будет преобразовано движком в ВНУТРЕННЕЕ СОЕДИНЕНИЕ.

Однако - я бы написал запрос следующим образом:

SELECT posts.ID
FROM posts  
JOIN category_relationships AS tt1 ON posts.ID = tt1.post_id
WHERE tt1.cat_id IN (100,200,201,202,203,210,211,212,213) 
GROUP BY posts.ID
HAVING SUM(tt1.cat_id = 201) = 0
    OR SUM(tt1.cat_id = 202) > 0
    OR SUM(tt1.cat_id = 203) > 0
1 голос
/ 12 марта 2019

Это хороший кандидат для group by и having:

SELECT cr.post_id
FROM category_relationships cr
GROUP BY cr.post_id
HAVING SUM(cr.tag_id = 100) > 0 AND        -- flight
       (SUM(cr.tag_id = 201) > 0 OR        -- economy  
        SUM(cr.tag_id IN (202, 203)) > 0   -- business/first
       ) AND
       NOT (SUM(cr.tag_id = 201) > 0 OR    -- economy 
            SUM(cr.tag_id IN (210, 211, 212, 213) = 0  -- alliance
           );

Это может быть проще с флагами:

SELECT cr.post_id
FROM (SELECT cr.*,
             (cr.tag_id = 100) as is_flight,
             (cr.tag_id = 201) as is_economy,
             (cr.tag_id in (202, 203)) as is_first_business,
             (cr.tag_id IN (210, 211, 212, 213)) as is_alliance
      FROM category_relationships cr
     ) cr
GROUP BY cr.post_id
HAVING SUM(is_flight) > 0 AND
       (SUM(is_economy) > 0 OR  
        SUM(is_business_first) > 0
       ) AND
       NOT (SUM(is_economy) > 0 OR  
            SUM(is_alliance) > 0 
           );
0 голосов
/ 12 марта 2019

перенести ваше условие из пункта where в положение ON

SELECT posts.ID FROM posts  
LEFT JOIN category_relationships AS tt1 ON (posts.ID = tt1.post_id) 
and tt1.cat_id IN (100,200,201,202,203,210,211,212,213) 
AND posts.ID NOT IN ( SELECT post_id FROM category_relationships WHERE cat_id IN (201) ) 
...