在两种可能性中选择唯一的行
这个问题本身很简单,但是我无法在一个查询中找到解决问题的方法,这是我对该问题的"抽象",以便给出更简单的解释:
我将保留原先的解释,但这是一组示例数据和预期的结果:
好的,这是一些示例数据,我用空行分隔成对
------------- | 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

