在两种可能性中选择唯一的行

时间:2020-03-06 14:53:46  来源:igfitidea点击:

这个问题本身很简单,但是我无法在一个查询中找到解决问题的方法,这是我对该问题的"抽象",以便给出更简单的解释:

我将保留原先的解释,但这是一组示例数据和预期的结果:

好的,这是一些示例数据,我用空行分隔成对

-------------
| 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