Как отфильтровать данные через join таблицу?

Есть таблицы: groups, targets и join таблица groups_targets.

У групп есть таргеты, например:

  • у группы №1 есть таргет gender, таргет age, и таргет city.
  • у группы №2 есть таргет gender
  • у группы №3 есть таргет city и gender.
  • у группы №4 есть таргет age и city.

Мой запрос ничего не возвращает

SELECT DISTINCT groups.* 
FROM groups 
INNER JOIN groups_targets ON groups_targets.group_id = groups.id 
INNER JOIN targets ON targets.id = groups_targets.target_id 
WHERE groups.user_id = 1 
AND (targets.name = 'age' AND targets.name = 'gender')

Как найти группы у которых есть и gender и age, т.е. только группу #1.


Ответы (1 шт):

Автор решения: teran

В вашем запросе условие targets.name = 'age' AND targets.name = 'gender' никогда не может быть выполнено, т.к. применяется к одной строке. Значение этой строки не может быть одновременно равно одному и второму.

Вы можете выполнить свой запрос, объединив условия через OR. В таком случае вернутся группы, где есть либо одно, либо второе, либо оба вместе. После чего, можно сгруппировать по группам и посчитать количество строк. Если число строк равно двум, то это искомый случай - там есть и то и другое.

SELECT g.id
FROM groups AS g
INNER JOIN groups_targets AS gt ON (gt.group_id = g.id)
INNER JOIN targets AS t ON (t.id = gt.target_id )
WHERE g.user_id = 1 
   AND t.name IN ( 'age', 'gender')
GROUP BY g.id
HAVING COUNT(*) = 2

Данный запрос вернет вам ID групп, в которых оба условия выполняются одновременно.

Второй способ для частного случае, когда ищете 2 условия - два джойна на таблицу связи. Примерно следующим образом:

SELECT g.*
FROM groups AS g
INNER JOIN groups_targets AS gt1 ON (
     gt1.group_id = g.id
     AND gt1.target_id = (SELECT id FROM tagets WHERE name = 'age')
)
INNER JOIN groups_targets AS gt2 ON (
     gt2.group_id = g.id
     AND gt2.target_id = (SELECT id FROM tagets WHERE name = 'gender')
)
WHERE g.user_id = 1 

Можно и другие вариации придумать с EXISTS либо с джойном производной таблицы, в которую вынести подсчет на равенство двум.


Добавлю еще экзотики c INTERSECT, если вдруг у вас есть индекс в таблице связи на (target_id, group_id) то можно в обратную сторону решить задачу (не знаю по поводу поддержки версий СУБД):

SELECT g.*
FROM groups AS g 
INNER JOIN ( 
    SELECT group_id
    FROM groups_targets AS gt1 
    WHERE gt1.target_id = (SELECT id FROM targets WHERE name = 'age')
    INTERSECT 
    SELECT group_id
    FROM groups_targets AS gt2
    WHERE gt2.target_id = (SELECT id FROM targets WHERE name = 'gender')
) AS ids ON (g.id = ids.group_id)
→ Ссылка