【问题标题】:SQL: find entries in 1:n relation that don't comply with condition spanning multiple rowsSQL:在 1:n 关系中查找不符合跨越多行条件的条目
【发布时间】:2010-05-16 20:40:14
【问题描述】:

我正在尝试优化 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 中查找表b 中缺少name 条目的条目。例如。我需要确保a 中的每个条目至少在表b 中具有name 条目"foo""bar"。因此查询应该返回类似于:

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 函数不可用。

谢谢,晚安。

【问题讨论】:

  • 我可以问为什么单引号是“更好”的 araqnid?
  • 单引号是sql中引用字符串的标准。例如,用于填充表的示例脚本没有在 postgresql 中运行。众所周知,Mysql 允许这样的事情。不确定sqlite。据我所知,Sql server 和 oracle 没有。
  • 这就是为什么 EAV 是一种反模式

标签: sql mysql sqlite postgresql


【解决方案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

【讨论】:

  • 是的,这应该可以工作,并且可以根据搜索生成查询,但我觉得这可能会很慢,因为每个搜索的名称都需要一个连接,请参阅下面的答案以获得更好的答案选项。
【解决方案2】:

好吧,您可以在数据库中进行一些定义,这些定义是必需的元素。所以我会创建一个:

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 进行交叉检查。

【讨论】:

  • 搜索标志 'foo', 'bar' 可以随时更改,我每次执行此查询时都不愿意使用临时表。否则我明天会调查这个的表现。谢谢
  • 在 postgres 中,您可以直接在 FROM 子句中指定 (values('foo'),('bar'))。更便携,您可以编写 (select 'foo' union all select 'bar') 等,这更冗长但做同样的事情。
【解决方案3】:

Ari-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...;

【讨论】:

  • 仅供参考:在 GROUP BY 和 HAVING 子句中使用列别名是不明智的 - 并非所有数据库都支持它,大多数只允许在 ORDER BY 中引用列别名。
  • 是的,postgres 不支持 HAVING sigh 中的别名。 SQLite 工作虽然......但更糟糕的是: - postgres 需要在 SUM 中显式强制转换 - postgres 需要 all 列在 GROUP BY 或聚合函数中,这使得查询非常复杂,我赌也低效。我想我会评估一下代码中缺少的东西......
【解决方案4】:

事实证明,这些都没有比在程序本身中做这些事情更快......而且后者更容易做到,因此我最终选择了它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-20
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 2022-11-27
    • 2018-08-03
    相关资源
    最近更新 更多