MySQL:从向量获取与行完全相同的数据的DB行

时间:2017-10-19 15:47:09

标签: mysql

我有这张桌子:

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?

2 个答案:

答案 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;