return mysql 中预先确定的一组 ID 中具有最高 ID 的值
return the value with the highest ID, of a predetermined set of IDs in mysql
我正在尝试提取与为每个 patientID 预定义的一组 detailID 的最高 detailID 关联的值。
这里是 table 的示例(也可以作为 fiddle):
CREATE TABLE `patient_details` (
patientId INT UNSIGNED NOT NULL,
detailId INT UNSIGNED NOT NULL,
`value` VARCHAR(256),
PRIMARY KEY (patientId, detailId),
UNIQUE INDEX details_of_patient (patientId, detailId)
);
INSERT INTO patient_details (patientId, detailId, `value`)
VALUES
(123, 75, '01'),
(123, 98, '02'),
(123, 151, '03 hit'),
(123, 251, '04'),
(321, 65, '05'),
(321, 75, '04'),
(321, 98, '03'),
(321, 151, '02 hit'),
(321, 180, '01'),
(123456, 75, '01'),
(123456, 89, '12/01/2022'),
(123456, 151, '03 hit'),
(123456, 215, '5681'),
(678910, 75, '01'),
(678910, 151, '03'),
(678910, 203, '12/01/2022 hit'),
(678910, 215, '56813')
;
我需要做的是拉取最高的detailID 75,151,203的值。
我尝试使用 if 函数来测试 detailID,但出现语法错误。
逻辑上我认为我应该能够做这样的嵌套 IF
select
patientId,
table.value
if(detailid=203,set @largest_detailID=203,if(detailid=151,set @largest_detailID=151,if(detailid=75,set @largest_detailID=75,)))
from table
where detailID=@largest_detailID
我期望的结果
patientID
value
123
03 hit
321
02 hit
123456
03 hit
678910
12/01/2022 hit
虽然此站点上有许多问题和答案解决了从对应于最大值或最小值(例如“Retrieving the last record in each group", "MySQL Left Join + Min”)的行中获取非聚合列的问题,但它们不会 select 来自有限的值列表,调整答案以添加此限制也不是微不足道的。
与其试图弄乱所有 IF
和 @
变量,为什么不使用 detailID
的降序来提供帮助,然后添加一个 LIMIT 1
根据您的条件中的 3 detailID
个数字仅获取存在的最高参考:
试试:
SELECT patientId, value
FROM patientInfo
WHERE detailID IN (75, 151, 203)
ORDER BY detailID DESC
LIMIT 1;
...并且由于 203
和 89
的 detailID
在查询结果中不存在,因此将获得预期的条目:
示例 dbfiddle.
假设您想将此逻辑应用于多个患者,请尝试使用 ROW_NUMBER() 按 patientId 对数据进行分区,然后根据最高的 detailId 进行排序:
WITH cte AS (
SELECT *
, ROW_NUMBER() OVER(
PARTITION BY PatientId
ORDER BY DetailID DESC
) AS RowNum
FROM YourTable
WHERE DetailId IN (75, 151, 203)
)
SELECT *
FROM cte
WHERE RowNum = 1
示例数据:
patientId
detailId
value
123456
75
01
123456
89
12/01/2022
123456
151
03
123456
215
56813
678910
75
01
678910
203
12/01/2022
678910
151
03
678910
215
56813
结果:
patientId
detailId
value
RowNum
123456
151
03
1
678910
203
12/01/2022
1
db<>fiddle here
group-wise maximums and minimums 的查询提供了一个很好的起点;棘手的部分可能是弄清楚如何有效地整合对 ID 的限制。
相关子查询
A correlated subquery容易适配,但性能较差。没有限制,MySQL手册中的第一个例子可以通过简单的改名来转换:
SELECT patientId, detailId, `value`
FROM patient_details AS pd
WHERE detailId=(
SELECT MAX(pd2.detailId)
FROM patient_details AS pd2
WHERE pd.patientId = pd2.patientId
)
;
由于 sub-query 是结果的简单最大值,向 WHERE
子句添加条件将适当限制 detailId
:
SELECT patientId, detailId, `value`
FROM patient_details AS pd
WHERE detailId=(
SELECT MAX(pd2.detailId)
FROM patient_details AS pd2
WHERE pd.patientId = pd2.patientId
AND pd2.detailId IN (75, 151, 203) -- the added restriction
)
;
Performance-wise,这涉及对外部查询的完整table扫描;应用 EXPLAIN
to the query for the sample schema & data gives the following plan:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
pd
NULL
ALL
NULL
NULL
NULL
NULL
17
100
Using where
2
DEPENDENT SUBQUERY
pd2
NULL
ref
PRIMARY,details_of_patient
PRIMARY
4
pd.patientId
4
30
Using where; Using index
注意主查询将 17 行中的 100% 连接到从属子查询。
优化
下面的不相关子查询会建议优化:限制detailId
的范围。将优化应用于相关子查询给出:
SELECT patientId, detailId, `value`
FROM patient_details AS pd
WHERE detailId=(
SELECT MAX(pd2.detailId)
FROM patient_details AS pd2
WHERE pd.patientId = pd2.patientId
AND pd2.detailId IN (75, 151, 203)
AND pd2.detailId BETWEEN 75 AND 203 -- optimization
) AND pd.detailId BETWEEN 75 AND 203 -- optimization
;
这减少了主查询中由 9 连接的行数:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
pd
NULL
ALL
NULL
NULL
NULL
NULL
17
11.11
Using where
2
DEPENDENT SUBQUERY
pd2
NULL
ref
PRIMARY,details_of_patient
PRIMARY
4
so.pd.patientId
4
5.88
Using where; Using index
优化同样会影响从属子查询中的行过滤,但最终不会影响性能,因为这些行没有连接到任何后续内容。
不相关的子查询
不相关的子查询也很容易以同样的方式改编:更改名称并向子查询添加条件:
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
JOIN (
SELECT patientId, MAX(detailId) AS detailId
FROM patient_details
WHERE detailId IN (75, 151, 203) -- the added restriction
GROUP BY patientId
) AS pd2
ON pd.patientId = pd2.patientId AND pd.detailId = pd2.detailId
;
但是,性能比基本相关子查询差一点:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
NULL
ALL
NULL
NULL
NULL
NULL
5
100
Using where
1
PRIMARY
pd
NULL
eq_ref
PRIMARY,details_of_patient
PRIMARY
8
pd2.patientId,pd2.detailId
1
100
NULL
2
DERIVED
patient_details
NULL
index
PRIMARY,details_of_patient
details_of_patient
8
NULL
17
30
Using where; Using index
优化
性能不佳主要是因为 IN
条件。无限制查询的计划性能更高:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
NULL
ALL
NULL
NULL
NULL
NULL
5
100
Using where
1
PRIMARY
pd
NULL
eq_ref
PRIMARY,details_of_patient
PRIMARY
8
pd2.patientId,pd2.detailId
1
100
NULL
2
DERIVED
patient_details
NULL
range
PRIMARY,details_of_patient
PRIMARY
4
NULL
5
100
Using index for group-by
请注意,根据此计划,查询使用计划中针对受限查询显示的 range join on pd2
, as opposed to the index 扫描。这指向一个可能的优化:在 detailId
:
上添加一个范围条件
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
JOIN (
SELECT patientId, MAX(detailId) AS detailId
FROM patient_details
WHERE detailId IN (75, 151, 203)
AND detailId BETWEEN 75 AND 203 -- the added optimization
GROUP BY patientId
) AS pd2
ON pd.patientId = pd2.patientId AND pd.detailId = pd2.detailId
;
这会产生比未优化查询更好的计划(尽管仍然落后于优化查询),因为它减少了主 select:
中的行数
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
NULL
ALL
NULL
NULL
NULL
NULL
2
100
Using where
1
PRIMARY
pd
NULL
eq_ref
PRIMARY,details_of_patient
PRIMARY
8
pd2.patientId,pd2.detailId
1
100
NULL
2
DERIVED
patient_details
NULL
index
PRIMARY,details_of_patient
details_of_patient
8
NULL
17
5.88
Using where; Using index
从 MySQL 8.0 开始,查询可以写成 CTE:
WITH pd2 AS (
SELECT patientId, MAX(detailId) AS detailId
FROM patient_details
WHERE detailId IN (75, 151, 203)
AND detailId BETWEEN 75 AND 203
GROUP BY patientId)
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
JOIN pd2
ON pd.patientId = pd2.patientId AND pd.detailId = pd2.detailId
;
左连接
左连接最难适应,因为天真地添加 detailID
限制在大多数情况下会产生不正确的结果。从文档示例开始(重命名后):
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
LEFT JOIN patient_details AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
向 WHERE
子句添加条件将以两种不同的方式失败:
JOIN
发生在 WHERE
之前;在评估后一个子句时,可能已经排除了所需的行。
pd2.detailId
对于 groupwise-maximum 行将为 NULL,因此测试它总是会完全针对所需的行失败。
过滤必须在 JOIN
之前进行,这意味着它必须在替换简单的 table 引用的子查询中完成:
SELECT pd.patientId, pd.detailId, pd.`value`
FROM (SELECT * FROM patient_details WHERE detailId IN (75, 151, 203)) AS pd
LEFT JOIN (SELECT * FROM patient_details WHERE detailId IN (75, 151, 203)) AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
注意子查询是重复的。对于 MySQL 8.0 及更高版本,可以使用 CTE 编写查询,即 DRYer(因此更健壮):
WITH pd AS (SELECT * FROM patient_details WHERE detailId IN (75, 151, 203))
SELECT pd.patientId, pd.detailId, pd.`value`
FROM pd
LEFT JOIN pd AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
估计性能中的数量与相关子查询相似:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
patient_details
NULL
ALL
NULL
NULL
NULL
NULL
17
30
Using where
1
SIMPLE
patient_details
NULL
ref
PRIMARY,details_of_patient
PRIMARY
4
patient_details.patientId
4
100
Using where; Using index
但是,这可以更好地尽早过滤行,导致 pd
的 17 行中只有 30% 或大约 5 行加入到 pd2
。
优化
不相关子查询的优化可以应用于此查询:
WITH pd AS (
SELECT *
FROM patient_details
WHERE detailId IN (75, 151, 203)
AND detailId BETWEEN 75 AND 203
)
SELECT pd.patientId, pd.detailId, pd.`value`
FROM pd
LEFT JOIN pd AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
这在加入之前过滤掉行方面做得更好:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
patient_details
NULL
ALL
NULL
NULL
NULL
NULL
17
5.88
Using where
1
SIMPLE
patient_details
NULL
ref
PRIMARY,details_of_patient
PRIMARY
4
so.patient_details.patientId
4
100
Using where; Using index
请注意,pd
的 17 行中估计有 5.88% 将连接到 pd2
,这是所有选项中表现最好的之一。
具有 Window 功能的 CTE
MySQL 文档中的最后一个示例使用 window 函数 Rank
为每一行分配一个位置并根据该位置进行过滤。与子查询方法一样,这个查询很容易通过简单地包含条件来调整,但只适用于从 MySQL 8.0 开始(因为这是支持 window 函数的第一个版本)。在通常的重命名和限制添加之后:
WITH pd AS (
SELECT patientId, detailId, `value`,
RANK() OVER (PARTITION BY patientId
ORDER BY detailId DESC
) AS `Rank`
FROM patient_details
WHERE detailId IN (75, 151, 203)
)
SELECT patientId, detailId, `value`
FROM pd
WHERE `Rank` = 1
;
请注意,这基本上是 SOS 答复中的查询,仅使用 Rank
而不是 Row_Number
。性能还是比较不错的:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
ref
<auto_key0>
<auto_key0>
8
const
1
100.00
2
DERIVED
patient_details
ALL
17
30
Using where; Using filesort
优化不会改善问题,因为主要 select 无法进一步优化。添加通常的优化会导致从派生的 selection 中过滤出更多行,但是(正如优化的相关子查询所指出的那样)这不会影响性能,因为派生的 selection 不是然后加入其他任何东西。
限制
考虑 Paul T 的解决方案:
SELECT patientId, value
FROM patient_details
WHERE detailID IN (75, 151, 203)
AND patientId = 123456
ORDER BY detailID DESC
LIMIT 1;
这对单个 ID 有很好的性能,只扫描 3 行:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
patient_details
NULL
range
PRIMARY,details_of_patient
PRIMARY
8
NULL
3
100
Using where
将 detailId
优化的范围限制应用于此查询不会提高性能。
只注意这个查询 returns单个患者 ID 的结果。要获得多个患者的结果,查询需要对每个患者单独进行 运行,导致性能与未优化的相关子查询相当(尽管对数据库有更多 round-trips)。
我正在尝试提取与为每个 patientID 预定义的一组 detailID 的最高 detailID 关联的值。
这里是 table 的示例(也可以作为 fiddle):
CREATE TABLE `patient_details` (
patientId INT UNSIGNED NOT NULL,
detailId INT UNSIGNED NOT NULL,
`value` VARCHAR(256),
PRIMARY KEY (patientId, detailId),
UNIQUE INDEX details_of_patient (patientId, detailId)
);
INSERT INTO patient_details (patientId, detailId, `value`)
VALUES
(123, 75, '01'),
(123, 98, '02'),
(123, 151, '03 hit'),
(123, 251, '04'),
(321, 65, '05'),
(321, 75, '04'),
(321, 98, '03'),
(321, 151, '02 hit'),
(321, 180, '01'),
(123456, 75, '01'),
(123456, 89, '12/01/2022'),
(123456, 151, '03 hit'),
(123456, 215, '5681'),
(678910, 75, '01'),
(678910, 151, '03'),
(678910, 203, '12/01/2022 hit'),
(678910, 215, '56813')
;
我需要做的是拉取最高的detailID 75,151,203的值。
我尝试使用 if 函数来测试 detailID,但出现语法错误。
逻辑上我认为我应该能够做这样的嵌套 IF
select
patientId,
table.value
if(detailid=203,set @largest_detailID=203,if(detailid=151,set @largest_detailID=151,if(detailid=75,set @largest_detailID=75,)))
from table
where detailID=@largest_detailID
我期望的结果
patientID | value |
---|---|
123 | 03 hit |
321 | 02 hit |
123456 | 03 hit |
678910 | 12/01/2022 hit |
虽然此站点上有许多问题和答案解决了从对应于最大值或最小值(例如“Retrieving the last record in each group", "MySQL Left Join + Min”)的行中获取非聚合列的问题,但它们不会 select 来自有限的值列表,调整答案以添加此限制也不是微不足道的。
与其试图弄乱所有 IF
和 @
变量,为什么不使用 detailID
的降序来提供帮助,然后添加一个 LIMIT 1
根据您的条件中的 3 detailID
个数字仅获取存在的最高参考:
试试:
SELECT patientId, value
FROM patientInfo
WHERE detailID IN (75, 151, 203)
ORDER BY detailID DESC
LIMIT 1;
...并且由于 203
和 89
的 detailID
在查询结果中不存在,因此将获得预期的条目:
示例 dbfiddle.
假设您想将此逻辑应用于多个患者,请尝试使用 ROW_NUMBER() 按 patientId 对数据进行分区,然后根据最高的 detailId 进行排序:
WITH cte AS ( SELECT * , ROW_NUMBER() OVER( PARTITION BY PatientId ORDER BY DetailID DESC ) AS RowNum FROM YourTable WHERE DetailId IN (75, 151, 203) ) SELECT * FROM cte WHERE RowNum = 1
示例数据:
patientId | detailId | value |
---|---|---|
123456 | 75 | 01 |
123456 | 89 | 12/01/2022 |
123456 | 151 | 03 |
123456 | 215 | 56813 |
678910 | 75 | 01 |
678910 | 203 | 12/01/2022 |
678910 | 151 | 03 |
678910 | 215 | 56813 |
结果:
patientId | detailId | value | RowNum |
---|---|---|---|
123456 | 151 | 03 | 1 |
678910 | 203 | 12/01/2022 | 1 |
db<>fiddle here
group-wise maximums and minimums 的查询提供了一个很好的起点;棘手的部分可能是弄清楚如何有效地整合对 ID 的限制。
相关子查询
A correlated subquery容易适配,但性能较差。没有限制,MySQL手册中的第一个例子可以通过简单的改名来转换:
SELECT patientId, detailId, `value`
FROM patient_details AS pd
WHERE detailId=(
SELECT MAX(pd2.detailId)
FROM patient_details AS pd2
WHERE pd.patientId = pd2.patientId
)
;
由于 sub-query 是结果的简单最大值,向 WHERE
子句添加条件将适当限制 detailId
:
SELECT patientId, detailId, `value`
FROM patient_details AS pd
WHERE detailId=(
SELECT MAX(pd2.detailId)
FROM patient_details AS pd2
WHERE pd.patientId = pd2.patientId
AND pd2.detailId IN (75, 151, 203) -- the added restriction
)
;
Performance-wise,这涉及对外部查询的完整table扫描;应用 EXPLAIN
to the query for the sample schema & data gives the following plan:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | pd | NULL | ALL | NULL | NULL | NULL | NULL | 17 | 100 | Using where |
2 | DEPENDENT SUBQUERY | pd2 | NULL | ref | PRIMARY,details_of_patient | PRIMARY | 4 | pd.patientId | 4 | 30 | Using where; Using index |
注意主查询将 17 行中的 100% 连接到从属子查询。
优化
下面的不相关子查询会建议优化:限制detailId
的范围。将优化应用于相关子查询给出:
SELECT patientId, detailId, `value`
FROM patient_details AS pd
WHERE detailId=(
SELECT MAX(pd2.detailId)
FROM patient_details AS pd2
WHERE pd.patientId = pd2.patientId
AND pd2.detailId IN (75, 151, 203)
AND pd2.detailId BETWEEN 75 AND 203 -- optimization
) AND pd.detailId BETWEEN 75 AND 203 -- optimization
;
这减少了主查询中由 9 连接的行数:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | pd | NULL | ALL | NULL | NULL | NULL | NULL | 17 | 11.11 | Using where |
2 | DEPENDENT SUBQUERY | pd2 | NULL | ref | PRIMARY,details_of_patient | PRIMARY | 4 | so.pd.patientId | 4 | 5.88 | Using where; Using index |
优化同样会影响从属子查询中的行过滤,但最终不会影响性能,因为这些行没有连接到任何后续内容。
不相关的子查询
不相关的子查询也很容易以同样的方式改编:更改名称并向子查询添加条件:
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
JOIN (
SELECT patientId, MAX(detailId) AS detailId
FROM patient_details
WHERE detailId IN (75, 151, 203) -- the added restriction
GROUP BY patientId
) AS pd2
ON pd.patientId = pd2.patientId AND pd.detailId = pd2.detailId
;
但是,性能比基本相关子查询差一点:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100 | Using where | |
1 | PRIMARY | pd | NULL | eq_ref | PRIMARY,details_of_patient | PRIMARY | 8 | pd2.patientId,pd2.detailId | 1 | 100 | NULL |
2 | DERIVED | patient_details | NULL | index | PRIMARY,details_of_patient | details_of_patient | 8 | NULL | 17 | 30 | Using where; Using index |
优化
性能不佳主要是因为 IN
条件。无限制查询的计划性能更高:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100 | Using where | |
1 | PRIMARY | pd | NULL | eq_ref | PRIMARY,details_of_patient | PRIMARY | 8 | pd2.patientId,pd2.detailId | 1 | 100 | NULL |
2 | DERIVED | patient_details | NULL | range | PRIMARY,details_of_patient | PRIMARY | 4 | NULL | 5 | 100 | Using index for group-by |
请注意,根据此计划,查询使用计划中针对受限查询显示的 range join on pd2
, as opposed to the index 扫描。这指向一个可能的优化:在 detailId
:
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
JOIN (
SELECT patientId, MAX(detailId) AS detailId
FROM patient_details
WHERE detailId IN (75, 151, 203)
AND detailId BETWEEN 75 AND 203 -- the added optimization
GROUP BY patientId
) AS pd2
ON pd.patientId = pd2.patientId AND pd.detailId = pd2.detailId
;
这会产生比未优化查询更好的计划(尽管仍然落后于优化查询),因为它减少了主 select:
中的行数id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100 | Using where | |
1 | PRIMARY | pd | NULL | eq_ref | PRIMARY,details_of_patient | PRIMARY | 8 | pd2.patientId,pd2.detailId | 1 | 100 | NULL |
2 | DERIVED | patient_details | NULL | index | PRIMARY,details_of_patient | details_of_patient | 8 | NULL | 17 | 5.88 | Using where; Using index |
从 MySQL 8.0 开始,查询可以写成 CTE:
WITH pd2 AS (
SELECT patientId, MAX(detailId) AS detailId
FROM patient_details
WHERE detailId IN (75, 151, 203)
AND detailId BETWEEN 75 AND 203
GROUP BY patientId)
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
JOIN pd2
ON pd.patientId = pd2.patientId AND pd.detailId = pd2.detailId
;
左连接
左连接最难适应,因为天真地添加 detailID
限制在大多数情况下会产生不正确的结果。从文档示例开始(重命名后):
SELECT pd.patientId, pd.detailId, pd.`value`
FROM patient_details AS pd
LEFT JOIN patient_details AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
向 WHERE
子句添加条件将以两种不同的方式失败:
JOIN
发生在WHERE
之前;在评估后一个子句时,可能已经排除了所需的行。pd2.detailId
对于 groupwise-maximum 行将为 NULL,因此测试它总是会完全针对所需的行失败。
过滤必须在 JOIN
之前进行,这意味着它必须在替换简单的 table 引用的子查询中完成:
SELECT pd.patientId, pd.detailId, pd.`value`
FROM (SELECT * FROM patient_details WHERE detailId IN (75, 151, 203)) AS pd
LEFT JOIN (SELECT * FROM patient_details WHERE detailId IN (75, 151, 203)) AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
注意子查询是重复的。对于 MySQL 8.0 及更高版本,可以使用 CTE 编写查询,即 DRYer(因此更健壮):
WITH pd AS (SELECT * FROM patient_details WHERE detailId IN (75, 151, 203))
SELECT pd.patientId, pd.detailId, pd.`value`
FROM pd
LEFT JOIN pd AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
估计性能中的数量与相关子查询相似:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | patient_details | NULL | ALL | NULL | NULL | NULL | NULL | 17 | 30 | Using where |
1 | SIMPLE | patient_details | NULL | ref | PRIMARY,details_of_patient | PRIMARY | 4 | patient_details.patientId | 4 | 100 | Using where; Using index |
但是,这可以更好地尽早过滤行,导致 pd
的 17 行中只有 30% 或大约 5 行加入到 pd2
。
优化
不相关子查询的优化可以应用于此查询:
WITH pd AS (
SELECT *
FROM patient_details
WHERE detailId IN (75, 151, 203)
AND detailId BETWEEN 75 AND 203
)
SELECT pd.patientId, pd.detailId, pd.`value`
FROM pd
LEFT JOIN pd AS pd2
ON pd.patientId = pd2.patientId
AND pd.detailId < pd2.detailId
WHERE pd2.patientId IS NULL
;
这在加入之前过滤掉行方面做得更好:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | patient_details | NULL | ALL | NULL | NULL | NULL | NULL | 17 | 5.88 | Using where |
1 | SIMPLE | patient_details | NULL | ref | PRIMARY,details_of_patient | PRIMARY | 4 | so.patient_details.patientId | 4 | 100 | Using where; Using index |
请注意,pd
的 17 行中估计有 5.88% 将连接到 pd2
,这是所有选项中表现最好的之一。
具有 Window 功能的 CTE
MySQL 文档中的最后一个示例使用 window 函数 Rank
为每一行分配一个位置并根据该位置进行过滤。与子查询方法一样,这个查询很容易通过简单地包含条件来调整,但只适用于从 MySQL 8.0 开始(因为这是支持 window 函数的第一个版本)。在通常的重命名和限制添加之后:
WITH pd AS (
SELECT patientId, detailId, `value`,
RANK() OVER (PARTITION BY patientId
ORDER BY detailId DESC
) AS `Rank`
FROM patient_details
WHERE detailId IN (75, 151, 203)
)
SELECT patientId, detailId, `value`
FROM pd
WHERE `Rank` = 1
;
请注意,这基本上是 SOS 答复中的查询,仅使用 Rank
而不是 Row_Number
。性能还是比较不错的:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | ref | <auto_key0> | <auto_key0> | 8 | const | 1 | 100.00 | |||
2 | DERIVED | patient_details | ALL | 17 | 30 | Using where; Using filesort |
优化不会改善问题,因为主要 select 无法进一步优化。添加通常的优化会导致从派生的 selection 中过滤出更多行,但是(正如优化的相关子查询所指出的那样)这不会影响性能,因为派生的 selection 不是然后加入其他任何东西。
限制
考虑 Paul T 的解决方案:
SELECT patientId, value
FROM patient_details
WHERE detailID IN (75, 151, 203)
AND patientId = 123456
ORDER BY detailID DESC
LIMIT 1;
这对单个 ID 有很好的性能,只扫描 3 行:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | patient_details | NULL | range | PRIMARY,details_of_patient | PRIMARY | 8 | NULL | 3 | 100 | Using where |
将 detailId
优化的范围限制应用于此查询不会提高性能。
只注意这个查询 returns单个患者 ID 的结果。要获得多个患者的结果,查询需要对每个患者单独进行 运行,导致性能与未优化的相关子查询相当(尽管对数据库有更多 round-trips)。