SQL:查找1:n关系中不符合跨越多行的条件的条目

时间:2010-05-16 20:40:14

标签: sql mysql sqlite postgresql

我正在尝试优化Akonadi中的SQL查询并遇到以下问题,这显然不容易用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 个答案:

答案 0 :(得分:1)

这适用于任何SQL标准RDBMS:

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

答案 1 :(得分:0)

那么,您可以在数据库中使用某些必需元素的定义。所以我会创建一个:

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)上有一个索引(并且你的描述似乎可能是唯一性约束)应该可以正常工作。在某种程度上,它将使用索引扫描和交叉检查b。

答案 2 :(得分:0)

我在Areen-Ugwu和Xgc的freenode上的#sql中得到了一个很好的提示:使用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...;

答案 3 :(得分:0)

事实证明,这些都不比简单地在程序本身中做那些东西更快......后者更容易做到,因此我毕竟选择了这个。