SQL: найти записи в отношении 1: n, которые не соответствуют условию, охватывающему несколько строк - PullRequest
3 голосов
/ 17 мая 2010

Я пытаюсь оптимизировать SQL-запросы в Akonadi и столкнулся со следующей проблемой, которую, по-видимому, нелегко решить с помощью SQL, по крайней мере для меня:

Предположим, что следующая структура таблицы (должна работать в SQLite, PostgreSQL, MySQL):

CREATE TABLE a (
  a_id INT PRIMARY KEY
);

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

CREATE TABLE b (
  b_id INT PRIMARY KEY,
  a_id INT,
  name VARCHAR(255) NOT NULL
);

INSERT INTO b (b_id, a_id, name)
       VALUES (1, 1, 'foo'), (2, 1, 'bar'), (3, 1, 'asdf'),
              (4, 2, 'foo'), (5, 2, 'bar'), (6, 3, 'foo');

Теперь моя проблема - найти записи в a, в которых отсутствуют name записи в таблице b. Например. Мне нужно убедиться, что каждая запись в a содержит как минимум name записей "foo" и "bar" в таблице b. Следовательно, запрос должен возвращать что-то похожее на:

a_id = 3 is missing name "bar"
a_id = 4 is missing name "foo" and "bar"

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

Одним из решений в MySQL будет:

SELECT a.a_id,
       CONCAT('|', GROUP_CONCAT(name ORDER BY NAME ASC SEPARATOR '|'), '|') as names
  FROM a
  LEFT JOIN b USING( a_id )
  GROUP BY a.a_id
  HAVING names IS NULL OR names NOT LIKE '%|bar|foo|%';

Мне еще предстоит измерить производительность завтра, но я сильно сомневаюсь, что это будет быстрым для десятков тысяч записей в a и в три раза больше для b. Кроме того, мы хотим поддерживать SQLite и PostgreSQL, где, насколько мне известно, функция GROUP_CONCAT недоступна.

Спасибо, спокойной ночи.

Ответы [ 4 ]

1 голос
/ 17 мая 2010

Это должно работать с любой стандартной СУБД SQL:

SELECT 
   a.a_id, 
   Foo.b_id as Foo_Id,
   Bar.b_id as Bar_Id
FROM a
LEFT OUTER JOIN (SELECT a_id, b_id FROM b WHERE name = 'foo') as Foo ON
   a.a_id = Foo.a_id
LEFT OUTER JOIN (SELECT a_id, b_id FROM b WHERE name = 'bar') as Bar ON
   a.a_id = Bar.a_id
WHERE
   Foo.a_id IS NULL
   OR Bar.a_id IS NULL
0 голосов
/ 17 мая 2010

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

0 голосов
/ 17 мая 2010

Я получил хороший совет по #sql на freenode от Ari-Ugwu и Xgc: используя шаблон CrossTab :

SELECT a.a_id, SUM(name = "foo") as hasFoo, SUM(name = "bar") as hasBar, ...
  FROM a
  LEFT JOIN b USING (a_id)
  GROUP BY a.a_id
  HAVING hasFoo < 1 OR hasFoo IS NULL OR hasBar < 1 OR hasBar IS NULL...;
0 голосов
/ 17 мая 2010

Ну, вы могли бы сделать с определением в базе данных, которые являются обязательными элементами. Поэтому я создам один:

CREATE TABLE required(name varchar(255) primary key);
INSERT INTO required VALUES('foo'), ('bar');

(это может быть временная таблица или просто встроенное объединение констант, если оно динамическое)

Теперь набор строк, которые мы ожидаем найти в b, определяется следующим образом:

SELECT a.a_id, required.name FROM a CROSS JOIN required;

Итак, мы внешне присоединяемся к этому набору против b, чтобы определить, что присутствует, а что нет:

SELECT a.a_id, required.name, b.b_id
FROM a
     CROSS JOIN required
     LEFT JOIN b ON b.a_id = a.a_id AND b.name = required.name;

или альтернативно:

SELECT a.a_id, required.name
FROM a CROSS JOIN required
WHERE NOT EXISTS (SELECT 1 FROM b WHERE b.a_id = a.a_id AND b.name = required.name);

Предполагая, что для b (a_id, name) есть индекс (и, вероятно, из вашего описания это ограничение уникальности), который должен хорошо работать. В той или иной степени он будет сканировать a и сверять с b по индексу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...