我有这张桌子:
CREATE TABLE Table1
(`id` int, `x_id` int)
;
INSERT INTO Table1
(`id`, `x_id`)
VALUES
(1, 90),
(1, 91),
(1, 92),
(2, 90),
(2, 91),
(2, 92),
(2, 93)
;
我有一个向量[90,91,92]。我的疑问很简单:
SELECT DISTINCT(id) FROM Table1 WHERE x_id IN ( 90,91,92);
正确返回它们。我怎样才能得到x_ids值与我的向量完全匹配的id?
答案 0 :(得分:3)
我想假设数据集中有数百万行,因为这会严重影响查询设计。
考虑到这一点,你可以做类似的事情:
SELECT id,
SUM(x_id IN (90,91,92)) AS score,
SUM(1) AS count
FROM Table1
WHERE id IN (
SELECT id FROM Table1 WHERE x_id IN (90,91,92)
)
GROUP BY id
HAVING score = count AND count = 3;
这只会考虑x_id
匹配90,91或92的行。它会针对每个score
计算一个x_id
个值匹配的id
。它还会计算每count
个不同x_id
值的id
。这有助于我们排除值为90,91和92的ID,但也有其他值。
精确矢量匹配的score
等于count
。
这种方法在具有数百万行的表上应该更有效,因为这些行中只有一部分将引用至少一个目标值。
假设每个(id, x_id)
元组都是唯一的。
修改强>
修复了示例中的HAVING count = 3
问题,正如@Strawberry在评论中所报告的那样。
使用这样的子查询时,请确保您使用的是最新版本的MySQL。由于查询规划器忽略了密钥并进行了昂贵的扫描,MySQL 5.5及更早版本的子查询性能很差。
为了演示额外子查询的性能改进,我们可以生成一堆示例数据以插入Table1
。这是一个简单的PHP脚本,使用长度为2-5的随机向量生成100,000行,值为1-100:
<?php
$possible_values = range(1,100);
foreach(range(1,100000) as $id) {
$vector = array_rand($possible_values, mt_rand(2,5));
$values = array_map(function($x_id) use ($id) {
return sprintf("(%d, %d)", $id, $x_id);
}, $vector);
echo sprintf("INSERT INTO Table1 (id, x_id) VALUES %s;\n",
implode(',', $values)
);
}
我们假设表格如下:
CREATE TABLE `Table1` (
`id` int(11) DEFAULT NULL,
`x_id` int(11) DEFAULT NULL,
KEY `id` (`id`, `x_id`)
KEY `x_id` (`x_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
让我们比较子查询优化的好处,找到一个短矢量:
mysql> SELECT SQL_NO_CACHE id,
-> SUM(x_id IN (6,25)) AS score,
-> SUM(1) AS count
-> FROM Table1
-> WHERE id IN (
-> SELECT id FROM Table1 WHERE x_id IN (6,25)
-> )
-> GROUP BY id
-> HAVING score = count AND count = 2;
+-------+-------+-------+
| id | score | count |
+-------+-------+-------+
| 15265 | 2 | 2 |
| 40816 | 2 | 2 |
| 75000 | 2 | 2 |
| 75239 | 2 | 2 |
| 83498 | 2 | 2 |
+-------+-------+-------+
5 rows in set (0.04 sec)
mysql> SELECT SQL_NO_CACHE id
-> FROM table1
-> GROUP BY id
-> HAVING SUM(x_id IN (6,25)) = COUNT(x_id)
-> AND COUNT(*) = 2;
+-------+
| id |
+-------+
| 15265 |
| 40816 |
| 75000 |
| 75239 |
| 83498 |
+-------+
5 rows in set (0.14 sec)
优化 100ms 更快(未经优化的查询需要29%的时间)。
您可以使用EXPLAIN
了解原因。
未经优化我们几乎扫描整个桌子:
mysql> explain SELECT SQL_NO_CACHE id FROM table1 GROUP BY id HAVING SUM(x_id IN (6,25)) = COUNT(x_id) AND COUNT(*) = 2 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: table1
partitions: NULL
type: index
possible_keys: id
key: id
key_len: 10
ref: NULL
rows: 338846
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
优化
mysql> explain SELECT SQL_NO_CACHE id, SUM(x_id IN (6,25)) AS score, SUM(1) AS count FROM Table1 WHERE id IN ( SELECT id FROM Table1 WHERE x_id IN (6,25) ) GROUP BY id HAVING score = count AND count = 2 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: <subquery2>
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
filtered: 100.00
Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: Table1
partitions: NULL
type: ref
possible_keys: id
key: id
key_len: 5
ref: <subquery2>.id
rows: 3
filtered: 100.00
Extra: Using index
*************************** 3. row ***************************
id: 2
select_type: MATERIALIZED
table: Table1
partitions: NULL
type: range
possible_keys: x_id,id
key: x_id
key_len: 5
ref: NULL
rows: 6874
filtered: 100.00
Extra: Using index condition
3 rows in set, 1 warning (0.00 sec)
在优化过程中,我们能够将我们聚合的行子集限制在~338,846到仅约6,874。对于MySQL来说,这是一项很多的工作。
对于较长的向量,例如19,61,62,96
,优化的查询以 80ms 与 150ms 运行,并进行表扫描(几乎快两倍)。
额外的复杂性可能不值得保存 100ms ,但如果Table1
有数百万行,优化查询的性能与未优化的聚合方法相比将变得非常明显整个桌子。
答案 1 :(得分:2)
这是一种方式(假设id和x_id的每个组合都是唯一的):
SELECT id
FROM table1
GROUP
BY id
HAVING SUM(x_id IN (90,91,92)) = COUNT(x_id)
AND COUNT(*) = 3;