Пересечение динамического числа таблиц - PullRequest
0 голосов
/ 05 мая 2018

У меня есть два отношения, a и b, с атрибутами, заданными

CREATE TABLE a (id int, b_id int)
CREATE TABLE b (id int)

, для которого я могу предположить, что все пары значений в a и все значения в b являются уникальными и будут основаны на базе данных SQL Server 2016.

Данный элемент b определяет подмножество a.id, заданное теми элементами, для которых соответствующее a.b_id является заданным значением, и моя цель состоит в том, чтобы создать пересечение всех этих подмножеств.

Скажем, например, что a содержит шесть значений,

INSERT INTO a VALUES (1, 1), (1, 2), (1, 3), (2, 2), (3, 2), (3, 3)

Тогда ожидаемые результаты будут включать следующее:

b: (1), (2), (3). Expected result: (1)
b: (1), (2).      Expected result: (1)
b: (2).           Expected result: (1), (2), (3)
b: (2), (3).      Expected result: (1), (3)
b: [Empty].       Expected result: (1), (2), (3)

Используя уникальность, для случая непустого b это может быть достигнуто через

SELECT a.id FROM a
JOIN b on a.b_id = b.id
GROUP BY a.id
HAVING COUNT(a.id) = (SELECT COUNT(*) FROM b)

но это кажется неуклюжим, учитывая, что в SQL есть оператор INTERSECT, и если бы я написал тот же запрос, скажем, в LINQ, я бы просто агрегировал пересечения. Он также не дает желаемого результата в случае пустого b, не рассматривая его как особый случай.

Итак, возникает вопрос: существует ли более идиоматический способ выполнения вышеуказанного запроса, который также работает правильно для тривиального b?

Ответы [ 2 ]

0 голосов
/ 05 мая 2018

То, что вы пытаетесь сделать, называется реляционным делением [ 1 , 2 ].

CREATE TABLE a (id INT, b_id INT);
CREATE TABLE b (id INT);

INSERT INTO a VALUES
  (1, 1), (1, 2), (1, 3),
  (2, 2), (3, 2), (3, 3);

DECLARE @i INT = 0;
WHILE @i < 5 BEGIN
  TRUNCATE TABLE b;
  IF @i = 0 INSERT b VALUES (1), (2), (3);
  IF @i = 1 INSERT b VALUES (1), (2);
  IF @i = 2 INSERT b VALUES (2);
  IF @i = 3 INSERT b VALUES (2), (3);
  SELECT DISTINCT x.id
  FROM a AS x
  WHERE NOT EXISTS (
    SELECT *
    FROM b AS y
    WHERE NOT EXISTS (
      SELECT *
      FROM a AS z
      WHERE z.id = x.id AND z.b_id=y.id
    )
  )
  SET @i = @i + 1;
END;

Протестируйте онлайн .

0 голосов
/ 05 мая 2018

Вы можете расширить свой подход для обработки пустого набора, используя:

SELECT a.id
FROM a
LEFT JOIN  b on a.b_id = b.id
GROUP BY a.id
HAVING COUNT(b.id) = (SELECT COUNT(*) FROM b);

Демоверсия DBFiddle | DBFiddle Demo - все тестовые случаи

Дополнительно: Переходные данные (используется во втором демо)


EDIT:

Еще один подход к обработке пустого набора и оставлению INNER JOIN:

SELECT a.id FROM a
JOIN b on a.b_id = b.id
GROUP BY a.id
HAVING COUNT(a.id) = (SELECT COUNT(*) FROM b)
UNION
SELECT a.id
FROM a
WHERE NOT EXISTS (SELECT 1 FROM b);

DBFiddle Demo 3

...