请注意,以下问题专门针对 MySQL 。
想象一个名为Cars
的表,其结构如下(我们可以忽略缺少正确的键约束等,因为它与我的问题无关):
CREATE TABLE Cars
(
id Integer,
maker_id Integer,
status_id Integer,
notes Varchar(100)
);
现在想象一下加载一些测试数据:
INSERT INTO Cars
(id, maker_id, status_id, notes)
VALUES
(1, 1001, 0, 'test1'),
(2, 1001, 0, 'test2'),
(3, 1001, 0, 'test3'),
(4, 1002, 0, 'test4'),
(5, 1002, 0, 'test5'),
(6, 1002, 1, 'test6'),
(7, 1002, 1, 'test7'),
(8, 1002, 2, 'test8'),
(9, 1003, 3, 'test9'),
(10, 1003, 3, 'test10'),
(11, 1003, 4, 'test11'),
(12, 1003, 4, 'test12'),
(13, 1003, 5, 'test13'),
(14, 1003, 5, 'test14')
共有14条记录,DISTINCT
(1001,1002,1003)中有3 maker_id
个值,DISTINCT
中有status_id
个值{0,1,2} ,3,4,5)。
现在,想象一下DISTINCT
对(maker_id
,status_id
)。
SELECT DISTINCT maker_id, status_id FROM Cars;
以下是SQL Fiddle中示例的链接:http://sqlfiddle.com/#!9/cb1c7/2
这会产生以下记录(maker_id
,status_id
):
我需要返回的逻辑如下:
如果给定的maker_id
值(例如,1001)仅对其对应的DISTINCT
(maker_id
,status_id
)对有1个不同的记录,则只需返回它。在这个例子中:(1001,0)。
如果给定的maker_id
值与其对应的DISTINCT
(maker_id
,status_id
)对的超过1 个不同记录,则返回全部其中除了 status_id
值为0的那个。在这个例子中:(1002,1),(1002,2),(1003,3),(1003,4) ,和(1003,5)。
请注意,我们遗漏了(1002,0)。
任何人都可以想到一个更简洁/更高效(在运行时方面)编写此查询的方式吗?在现实世界中,我的桌子有数百万条记录。
我想出了以下内容:
SELECT
subq.maker_id,
subq.status_id
FROM
(
SELECT DISTINCT
maker_id,
status_id,
(SELECT COUNT(*) FROM Cars WHERE maker_id = c.maker_id AND status_id != 0 GROUP BY maker_id) AS counter
FROM Cars AS c
) AS subq
WHERE
subq.counter IS NULL
OR (subq.counter IS NOT NULL AND subq.status_id != 0)
;
以下是SQL Fiddle中的一个示例:http://sqlfiddle.com/#!9/cb1c7/3
答案 0 :(得分:2)
有几种查询模式可以返回指定的结果。有些看起来比其他看起来更复杂。性能上可能存在大差异。
在庞大的集合上执行GROUP BY
操作可能成本很高(就资源和已用时间而言,如果MySQL无法使用索引来优化该操作,则可能会花费很多时间。(使用GROUP BY
操作是获得status_id
每个maker_id
计数的一种方法。)
当重复执行相关子查询时,相关子查询可能很昂贵。当需要执行的次数有限时,我通常只能从相关子查询中看到更好的性能。
我认为获得良好表现的最好机会是:
未经过测试
SELECT c.maker_id
, c.status_id
FROM Cars c
WHERE c.status_id > 0
UNION ALL
SELECT d.maker_id
, d.status_id
FROM Cars d
LEFT
JOIN Cars e
ON e.maker_id = d.maker_id
AND e.status_id > 0
WHERE e.maker_id IS NULL
AND d.status_id = 0
至于是否比其他查询方法更有效或更简洁,我们需要测试。
但是对于使用此查询获得良好性能的任何镜头,我们将需要一个索引。
.. ON Cars (maker_id, status_id)
我们希望EXPLAIN输出会在Extra
列中显示“使用索引”。而且我们并不期待“使用filesort”。
这种方法的一个重大缺点是,有效地将两次通过表(或索引)。
第一个SELECT非常简单......把我status_id
不为零的所有行。我们需要所有这些行。它可能是一个索引,例如
... ON Cars (status_id, maker_id)
可能对该查询有益。但是,如果我们返回表格的很大一部分,我会向甜甜圈投注美元,以便对其他索引的完整扫描速度一样快或更快。
第二个SELECT
使用反连接模式。这样做是让所有行status_id
等于零的行,并从该集合“过滤掉”有另一行的任何行,对于maker_id
status_id
行{ 1}}除了零。
我们使用外部联接操作(LEFT JOIN
)进行过滤,以返回status_id=0
的所有行,以及所有匹配的行。 技巧是WHERE
子句中的谓词,用于过滤掉所有匹配的行。所以我们留下的是没有找到匹配的行。也就是说,maker_id
的值只有 status_id=0
行。
我们可以使用NOT EXISTS
谓词而不是反连接获得等效结果。但根据我的经验,有时表现并不好。我们可以重写第二个SELECT
(在UNION ALL
操作之后)
SELECT d.maker_id
, d.status_id
FROM Cars d
WHERE d.status_id = 0
AND NOT EXISTS
( SELECT 1
FROM Cars e
WHERE e.maker_id = d.maker_id
AND e.status_id > 0
)
该查询的性能将取决于合适的索引,就像反连接一样。
重要提示:不省略ALL
关键字。 UNION ALL
操作只是连接两个查询的结果。如果我们省略ALL
关键字,那么我们要求MySQL执行“排序唯一”操作以消除重复的行。
注意:UNION ALL
而不是OR
条件的原因是我通常会使用UNION ALL
获得更好的查询计划。当谓词在不同的列和条件上时,MySQL优化器似乎对OR
做得不好,并且谓词可用于“驱动”执行计划。使用UNION ALL
,将其分为两个查询,我们通常可以为这两个部分制定一个好的计划。
答案 1 :(得分:1)
此查询将有助于:)
select
distinct c1.maker_id, c1.status_id
from
Cars AS c1
where
c1.status_id!=0
or c1.maker_id not in (
select distinct c2.maker_id
from Cars AS c2
where c2.status_id!=0
)
答案 2 :(得分:1)
作为我第一个回答的完全不同的方法......
鉴于一种不可能的情况......桌面上没有索引,也没有创建索引的可能性......我们基本上留下了两个非常糟糕的选择:一种巨大的集合和一些通行证通过它,或通过一张巨大的桌子大量通过。
两个邪恶中较小的一个似乎是那种。我们无法提供索引,但我们可以在查询中动态构建索引。还有一大堆磁盘io到/ tmp。 SSD上安装了一个散热器,对吗?
我们只对整个翻转组进行排序,然后重复几次。
SELECT t.maker_id
, t.status_id
FROM ( SELECT IF(s.status_id=0 AND s.maker_id=@p_maker,NULL,s.status_id) AS status_id
, @p_maker := s.maker_id AS maker_id
FROM ( SELECT @p_maker := NULL ) i
CROSS
JOIN ( SELECT c.maker_id
, c.status_id
FROM Cars c
GROUP BY c.maker_id DESC, c.status_id DESC
ORDER BY c.maker_id DESC, c.status_id DESC
) s
) t
WHERE t.status_id IS NOT NULL
答案 3 :(得分:1)
我对表现并不完全确定..但我喜欢优雅:
SELECT maker_id,
status_id
FROM cars
WHERE status_id != 0
GROUP BY maker_id, status_id
UNION ALL
SELECT maker_id,
MAX(status_id) max_status_id
FROM cars
GROUP BY maker_id
HAVING max_status_id = 0