Найти единичное вхождение совпадающих и несовпадающих записей из множества через ассоциацию - PullRequest
1 голос
/ 09 июля 2020

У меня есть 3 Postgres таблица

  1. группы
  2. groups_favourites
  3. избранное

Структура этих 3 таблиц :

groups
--------------------------
| id |  name   | user_id |
--------------------------
| 1  | Group A |   100   |
| 2  | Group B |   100   |
| 3  | Group C |   100   |
| 4  | Group D |   100   |
--------------------------

favourites_groups
-------------------------------
| id | group_id | favorite_id |
-------------------------------
| 1  |  2       | 10          |
| 2  |  2       | 12          |
| 3  |  2       | 14          |
| 4  |  2       | 15          |
| 5  |  2       | 16          |
| 6  |  3       | 12          |
| 7  |  4       | 10          |
| 8  |  4       | 11          |
| 9  |  4       | 12          |
| 10 |  4       | 13          |
-------------------------------
UNIQUE INDEX ON [group_id, favorite_id]

favourites
-------------------------------
| id | product_id  | user_id  |
-------------------------------
| 10 |    1000     |   100    |
| 11 |    1001     |   100    |
| 12 |    1002     |   100    |
| 13 |    1003     |   100    |
| 14 |    1004     |   100    |
| 15 |    1005     |   100    |
| 16 |    1006     |   100    |
-------------------------------

Я использую Ruby на Rails на бэкэнде, и связь между этими тремя таблицами устанавливается через has_many :through ассоциацию

Результат, который я бы хотел бы выполнить sh: для данного продукта и пользователя я хотел бы найти все группы, которые имеют записи в таблице favorite_groups + все группы, которые не имеют записи в таблице favorites_groups.

Скажем, для user_id = 100 И product_id = 1000 результат будет:

| group_id | group_name | favorite_id | product_id |
----------------------------------------------------
|    1     |  Group A   |    NULL     |    NULL    |
|    2     |  Group B   |     10      |    1000    |
|    3     |  Group C   |    NULL     |    NULL    |
|    4     |  Group D   |     10      |    1000    |
----------------------------------------------------

Другой вариант использования - user_id = 100 И product_id = 1002, результат будет:

| group_id | group_name | favorite_id | product_id |
----------------------------------------------------
|    1     |  Group A   |    NULL     |    NULL    |
|    2     |  Group B   |     12      |    1002    |
|    3     |  Group C   |     12      |    1002    |
|    4     |  Group D   |     12      |    1002    |
----------------------------------------------------

Текущий SQL, который я использую, дает мне 1 строку NULL favorite_id и product_id и 1 строку сопоставленных favorite_id и product_id

Вот мой SQL :

SELECT DISTINCT ON (group_id, favorite_id, product_id) groups.id AS group_id, groups.name AS group_name, favorites.id AS favorite_id, favorites.product_id AS product_id
FROM groups
LEFT JOIN favorite_groups ON favorite_groups.group_id = groups.id
LEFT JOIN favorites ON favorites.id = favorite_groups.favorite_id AND favorites.product_id = 1002
WHERE groups.user_id = 100

Из-за результатов, возвращаемых этим запросом, мне приходится дополнительно поработать, чтобы перебрать каждую строку и подготовить требуемый набор результатов.

Есть ли как лучше сделать это?

Заранее спасибо за помощь!

1 Ответ

1 голос
/ 09 июля 2020

В вашей модели есть что-то странное. Связь между groups.name и user_id, которая отображается как в groups, так и в favourites, неясна. Ограничение unique для favourites_groups должно сделать user_id in favourites ненужным, поэтому я добавил закомментированное условие join.

Попробуйте этот запрос, чтобы узнать, возвращает ли он то, что вы нужно:

select g.id as group_id, g.name as group_name
  from groups g
  left join favourites_groups fg
    on fg.group_id = g.id
  left join favourites f
    on f.id = fg.favorite_id
 --  and f.user_id = g.user_id 
 where g.user_id = 100
   and f.product_id = 1002
;

Обновить Извините за это. Это должно вернуть то, что вы хотите:

select g.id as group_id, g.name as group_name,
       max(f.id) as favorite_id, 
       max(f.product_id) as product_id
  from groups g
  left join favourites_groups fg 
    on fg.group_id = g.id
  left join favourites f 
    on f.id = fg.favorite_id
   and f.product_id = 1000
 where g.user_id = 100
 group by g.id, g.name
 order by g.id;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...