以一组两种可能性选择唯一行

时间:2008-09-29 20:53:42

标签: sql mysql sql-server database postgresql

问题本身很简单,但我无法弄清楚在一个查询中做到这一点的解决方案,而这是我对问题的“抽象”,以便做出更简单的解释:

我会让原始的explenation成立,但这里有一组示例数据和我期望的结果:

好的,所以这里是一些样本数据,我用空行分隔对

-------------
| 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,其中我有两列说IDNAME,它们共同构成了表格的主键。现在我想选择ID=1的内容然后首先检查它是否可以找到NAME具有值“John”的行,如果“John”不存在则应该查找{ {1}}是“布鲁斯” - 但如果“布鲁斯”和“约翰”都存在或只有“约翰”存在,则只返回“约翰”。

另请注意,它应该能够为每个查询返回多个符合上述条件的行,但当然具有不同的ID /名称组合,并且上述说明只是对实际问题的简化。

我可以完全被自己的代码和思路所蒙蔽,但我无法理解这一点。

9 个答案:

答案 0 :(得分:4)

这与你所写的非常相似,但应该相当快,因为​​在这种情况下,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 | 
+----+-----+

答案 1 :(得分:1)

您可以使用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')
像那样。

答案 2 :(得分:1)

无需将此过于复杂,您只需使用MAX()group by ...

select id, max(col) from foo group by id

答案 3 :(得分:0)

试试这个:

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 

答案 4 :(得分:0)

我自己想出了一个解决方案,但它有点复杂和缓慢 - 它也不能很好地扩展到更高级的查询:

SELECT *
FROM users
WHERE name = "bruce"
OR (
    name = "john"
    AND NOT id
    IN (
        SELECT id
        FROM posts
        WHERE name = "bruce"
    )
)

没有重型连接等没有替代方案?

答案 5 :(得分:0)

好的,所以这里是一些样本数据,我用空行分隔对

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

我确实在上面解决了这个问题,但是对于更大的表,这个查询是非常低效的,还是其他方式吗?

答案 6 :(得分:0)

这是一个适用于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

答案 7 :(得分:-1)

您可以使用联接而不是exists,这可能会在优化程序不够智能的情况下改进查询计划:

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 )

答案 8 :(得分:-1)

在PostgreSQL中,我相信会是这样的:

SELECT DISTINCT ON (id) id, name
FROM mytable
ORDER BY id, name = 'John' DESC;

更新 - 假之前的错误排序 - 我最初倒退了。请注意,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