Проверка двух разных списков с помощью операции AND в одном столбце

У меня есть следующий пример структуры данных клиента, который может быть частью нескольких групп, используя соединительную таблицу и данные:

CREATE TABLE customer (
    id INT NOT NULL
);

CREATE TABLE groups (
    id INT NOT NULL
);

CREATE TABLE customers_to_groups (
    id serial,
    group_id INT,
    customer_id INT
);

INSERT INTO customer(id) VALUES(0);
INSERT INTO customer(id) VALUES(1);
INSERT INTO customer(id) VALUES(2);
INSERT INTO customer(id) VALUES(3);
INSERT INTO groups(id) VALUES(1);
INSERT INTO groups(id) VALUES(3);
INSERT INTO groups(id) VALUES(5);
INSERT INTO groups(id) VALUES(6);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(0, 1);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(0, 5);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(1, 1);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(1, 90);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(2, 1);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(3, 3);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(3, 5);
INSERT INTO customers_to_groups(customer_id, group_id) VALUES(3, 90);

Мне нужно получить клиентов, у которых есть определенные группы, частью которых они являются, и мне нужно получить список всех клиентов, которые входят хотя бы в одну группу, в нескольких списках групп. Например, я хочу получить всех клиентов, которые находятся в группе [5 OR 6] AND [1 OR 3], поэтому, например, клиент из группы 5 и 1 будет возвращен, а кто-то из группы 1 и 90 или просто группы 1 — нет. С помощью предоставленных образцов данных мы получим клиентов с идентификаторами 0 и 3 только в том случае, если они соответствуют приведенным выше правилам.

Просто сделать WHERE group_id IN (5,6) AND group_id IN (1,3), кажется, не работает, поэтому я ищу альтернативу.

Я получил это до сих пор, что работает:

SELECT DISTINCT c.id
FROM customer c
INNER JOIN customers_to_groups at1 ON c.id = at1.customer_id
INNER JOIN customers_to_groups at2 ON c.id = at2.customer_id
WHERE at1.group_id IN (5, 6)
  AND at2.group_id IN (1, 3);

Ожидаемые результаты:

идентификатор 0 3

Есть ли способ сделать это более производительным?

Минимальный воспроизводимый пример — отличное начало при обращении за помощью по SQL.

jarlh 07.08.2024 11:43

Добавил больше контекста ^^

peti446 07.08.2024 11:56

Выборочные данные — это здорово, но можете ли вы также указать ожидаемый результат?

jarlh 07.08.2024 11:57

Обновил его там, поэтому с новым примером данных я получу 2 строки: одну с идентификатором клиента 0 и одну с клиентом 3, поскольку клиент 0 является частью группы 5 и 1, а клиент 3 является частью 3 и 5. Клиенты может быть дополнительная группа, мы просто игнорируем их здесь, как у клиента 3, имеющего дополнительную группу 7

peti446 07.08.2024 12:03

Можете ли вы отредактировать и добавить результат в виде правильно отформатированного текста? (Строки и столбцы, вы знаете.)

jarlh 07.08.2024 12:06

Готово @jarlh Спасибо, что терпели меня ^^

peti446 07.08.2024 12:19

Отличный пример кода, но я думаю, что вы используете group_id 6 и 7 взаимозаменяемо.

Rexy Gamaliel 07.08.2024 12:28

Итак, в примере я использовал группу 7 в предложении, чтобы показать, что ее нет ни в одном из двух списков. Я мог бы выбрать 20, 90 или 8. Я выбрал 7 наугад, возможно, не лучший из-за близости 7.

peti446 07.08.2024 12:31

В этом примере у нас есть клиент 3, входящий в группу 3, 5, 7, и он должен вернуть этого клиента, поскольку этот клиент находится в группе 3 или 1, а также является частью групп 5 или 6. Меня не волнуют дополнительные группы. в этом случае.

peti446 07.08.2024 12:33

Я могу обновить вопрос, указав другой номер, чтобы его было легче увидеть. Позвольте мне сделать это, обновлю до 90

peti446 07.08.2024 12:34
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
5
10
83
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Желаемого результата можно добиться с помощью более оптимизированного запроса, используя предложения GROUP BY и HAVING. Этот подход позволяет избежать необходимости многократного самосоединения.

SELECT customer_id
FROM customers_to_groups
WHERE group_id IN (5, 6, 1, 3)
GROUP BY customer_id
HAVING COUNT(DISTINCT CASE WHEN group_id IN (5, 6) THEN 1 END) > 0
   AND COUNT(DISTINCT CASE WHEN group_id IN (1, 3) THEN 1 END) > 0;

Рассмотрение индекса: Чтобы еще больше повысить производительность, убедитесь, что у вас есть индекс для столбцов customer_id и group_id в таблице customer_to_groups:

CREATE INDEX idx_customer_group ON customers_to_groups (customer_id, group_id);
Ответ принят как подходящий

Мы можем GROUP BY идентификатор клиента и использовать предложение HAVING. Там мы можем использовать CASE или FILTER, если ваша СУБД поддерживает это. Постгрес должен.

Там вам будут поставлены условия.

Запрос будет:

SELECT c.id 
FROM customer c
INNER JOIN customers_to_groups ctg
  ON c.id = ctg.customer_id
  AND ctg.group_id IN (1,3,5,6)
GROUP BY c.id
HAVING 
  COUNT(CASE WHEN ctg.group_id IN (1,3) THEN 1 END) > 0
  AND COUNT(CASE WHEN ctg.group_id IN (5,6) THEN 1 END) > 0;

или

SELECT c.id 
FROM customer c
INNER JOIN customers_to_groups ctg
  ON c.id = ctg.customer_id
  AND ctg.group_id IN (1,3,5,6)
GROUP BY c.id
HAVING 
  COUNT(*) FILTER(WHERE ctg.group_id IN (1,3)) > 0
  AND COUNT(*) FILTER(WHERE ctg.group_id IN (5,6)) > 0;

Примечание. Приведенные выше запросы предполагают, что вам действительно нужно включить таблицы customers и customers_to_groups и соединить их. Если вам не нужно включать таблицу customers, просто удалите ее и выберите из таблицы customers_to_groups только для улучшения производительности:

SELECT customer_id
FROM customers_to_groups ctg
WHERE ctg.group_id IN (1,3,5,6)
GROUP BY customer_id
HAVING 
  COUNT(*) FILTER(WHERE ctg.group_id IN (1,3)) > 0
  AND COUNT(*) FILTER(WHERE ctg.group_id IN (5,6)) > 0;

Эта демонстрация с примерами данных и множеством дополнительных строк показывает разницу в производительности.

Использование FILTER или CASE намного быстрее (поскольку позволяет избежать второго JOIN в таблице customer_to_groups). Точная разница в производительности зависит от реальных данных в ваших таблицах и от того, какие индексы вы используете.

Принимаю это как ответ, но всем спасибо за ответы в будущем, если вы используете Postgress и хотите, чтобы динамический ответ @Charlieface был идеальным. Еще раз всем спасибо, я проголосовал за все ответы ^^

peti446 07.08.2024 14:59

Кроме того, как уже упоминалось, если вам не нужны данные о клиенте, вы можете просто удалить использование клиента, которое будет выглядеть как ответ @rahul-jangid и @ jarlh.

peti446 07.08.2024 14:59

Это проблема реляционного деления с той особенностью, что у вас есть несколько делителей. То, что у вас есть, неплохо с точки зрения эффективности.

Но если вы хотите иметь возможность делать это динамически и передавать разное количество групп и блоков групп, тогда вам нужно другое решение.

Вы можете передать его как параметр массива JSONB.

SELECT
  cg.customer_id
FROM customers_to_groups cg
JOIN jsonb_array_elements('[[5,6],[1,3]]'::jsonb) sections(value)
    ON cg.group_id IN (SELECT value::int FROM jsonb_array_elements(sections.value))
GROUP BY
  cg.customer_id
HAVING COUNT(*) = MIN(jsonb_array_length('[[5,6],[1,3]]'::jsonb));

В качестве альтернативы, если у вас есть входные данные в таблице, вы можете использовать полное реляционное деление.

SELECT
  c.id
FROM (
    SELECT
      c.id,
      COUNT(cg.group_id) AS count_matches
    FROM customer c
    CROSS JOIN input_groups ig
    LEFT JOIN customers_to_groups cg
        ON cg.group_id = ig.group_id
       AND cg.customer_id = c.id
    GROUP BY
      c.id,
      ig.section_id 
) c
GROUP BY
  c.id
HAVING COUNT(*) FILTER (WHERE count_matches = 0) = 0;   -- all sections have a match

db<>рабочий пример

Сделайте GROUP BY, используйте предложение HAVING, чтобы убедиться, что присутствует хотя бы один из (5, 6) и хотя бы один из (1, 3).

SELECT customer_id
FROM customers_to_groups
WHERE group_id IN (5, 6, 1, 3)
GROUP BY customer_id
HAVING MAX(group_id) >= 5
   AND MIN(group_id) <= 3;

Вы также можете использовать INTERSECT:

SELECT customer_id FROM customers_to_groups WHERE group_id IN (5, 6)
INTERSECT
SELECT customer_id FROM customers_to_groups WHERE group_id IN (1, 3)

Другие вопросы по теме