在两种可能性中选择唯一的行
这个问题本身很简单,但是我无法在一个查询中找到解决问题的方法,这是我对该问题的"抽象",以便给出更简单的解释:
我将保留原先的解释,但这是一组示例数据和预期的结果:
好的,这是一些示例数据,我用空行分隔成对
------------- | Key | Col | (Together they from a Unique Pair) -------------- | 1 Foo | | 1 Bar | | | | 2 Foo | | | | 3 Bar | | | | 4 Foo | | 4 Bar | --------------
我期望的结果是,在运行一次查询之后,需要能够在一个查询中选择此结果集:
1 - Foo 2 - Foo 3 - Bar 4 - Foo
原始说明:
我有一个表,称其为" TABLE",其中有两列,分别是" ID"和" NAME",它们共同构成了该表的主键。现在我要选择" ID = 1",然后首先检查是否可以找到" NAME"值为" John"的行,如果" John"不存在,则应该在" NAME"的行中查找是" Bruce",但仅在" Bruce"和" John"同时存在或者仅存在" John"的情况下才返回" John"。
还应注意,它应该能够为每个查询返回几行,这些行与上述条件匹配,但ID /名称组合当然不同,并且上述说明只是对实际问题的简化。
我可能对自己的代码和思路完全不了解,但我无法弄清楚。
解决方案
我们可以使用联接代替现有联接,并且在优化器不够智能的情况下,这可以改善查询计划:
SELECT f1.id ,f1.col FROM foo f1 LEFT JOIN foo f2 ON f1.id = f2.id AND f2.col = 'Foo' WHERE f1.col = 'Foo' OR ( f1.col = 'Bar' AND f2.id IS NULL )
试试这个:
select top 1 * from ( SELECT 1 as num, * FROM TABLE WHERE ID = 1 AND NAME = 'John' union SELECT 2 as num, * FROM TABLE WHERE ID = 1 AND NAME = 'Bruce' ) t order by num
我们可以使用OUTER JOIN将初始表连接到自身,如下所示:
create table #mytest ( id int, Name varchar(20) ); go insert into #mytest values (1,'Foo'); insert into #mytest values (1,'Bar'); insert into #mytest values (2,'Foo'); insert into #mytest values (3,'Bar'); insert into #mytest values (4,'Foo'); insert into #mytest values (4,'Bar'); go select distinct sc.id, isnull(fc.Name, sc.Name) sel_name from #mytest sc LEFT OUTER JOIN #mytest fc on (fc.id = sc.id and fc.Name = 'Foo')
像那样。
在PostgreSQL中,我相信是这样的:
SELECT DISTINCT ON (id) id, name FROM mytable ORDER BY id, name = 'John' DESC;
在true之前先更新false排序,然后我本来就将其反向。请注意,DISTINCT ON是PostgreSQL的功能,不是标准SQL的一部分。这里发生的是,它仅显示遇到的任何给定ID的第一行。由于我们是按天气顺序排序的,因此名称为John,因此将在所有其他名称上选择名为John的行。
在第二个示例中,它将是:
SELECT DISTINCT ON (key) key, col FROM mytable ORDER BY key, col = 'Foo' DESC;
这将为我们提供:
1 - Foo 2 - Foo 3 - Bar 4 - Foo
我本人想出了一个解决方案,但是它很复杂,很慢,也不能很好地扩展到更高级的查询中:
SELECT * FROM users WHERE name = "bruce" OR ( name = "john" AND NOT id IN ( SELECT id FROM posts WHERE name = "bruce" ) )
没有繁重的联接,没有其他选择吗?
好的,这是一些示例数据,我用空行分隔成对
------------- | Key | Col | (Together they from a Unique Pair) -------------- | 1 Foo | | 1 Bar | | | | 2 Foo | | | | 3 Bar | | | | 4 Foo | | 4 Bar | --------------
我期望的结果是:
1 - Foo 2 - Foo 3 - Bar 4 - Foo
我确实在上面解决了这个问题,但是对于更大的表来说,该查询效率非常低,还有其他方法吗?
这与我们写的内容非常相似,但是应该相当快,因为在这种情况下,NOT EXISTS比NOT IN更有效...
mysql> select * from foo; +----+-----+ | id | col | +----+-----+ | 1 | Bar | | 1 | Foo | | 2 | Foo | | 3 | Bar | | 4 | Bar | | 4 | Foo | +----+-----+ SELECT id , col FROM foo f1 WHERE col = 'Foo' OR ( col = 'Bar' AND NOT EXISTS( SELECT * FROM foo f2 WHERE f1.id = f2.id AND f2.col = 'Foo' ) ); +----+-----+ | id | col | +----+-----+ | 1 | Foo | | 2 | Foo | | 3 | Bar | | 4 | Foo | +----+-----+
这是在SQL Server 2005及更高版本中运行的示例。这是一种有用的模式,我们想根据自定义顺序选择第一行(或者前n行)。这样一来,我们不仅可以在两个具有自定义优先级的值中进行选择,还可以选择任意数量。我们可以使用ROW_NUMBER()函数和CASE表达式:
CREATE TABLE T (id int, col varchar(10)); INSERT T VALUES (1, 'Foo') INSERT T VALUES (1, 'Bar') INSERT T VALUES (2, 'Foo') INSERT T VALUES (3, 'Bar') INSERT T VALUES (4, 'Foo') INSERT T VALUES (4, 'Bar') SELECT id,col FROM (SELECT id, col, ROW_NUMBER() OVER ( PARTITION BY id ORDER BY CASE col WHEN 'Foo' THEN 1 WHEN 'Bar' THEN 2 ELSE 3 END ) AS RowNum FROM T ) AS X WHERE RowNum = 1 ORDER BY id
无需过于复杂,只需使用MAX()和group by ...
select id, max(col) from foo group by id