计算 MySQL 中两列之间匹配数的有效方法

Efficient way to compute number of matchings between two columns in MySQL

描述

我有一个 MySQL table,如下所示:

CREATE TABLE `ticket` (
  `ticket_id` int(11) NOT NULL AUTO_INCREMENT,
  `ticket_number` varchar(30) DEFAULT NULL,
  `pick1` varchar(2) DEFAULT NULL,
  `pick2` varchar(2) DEFAULT NULL,
  `pick3` varchar(2) DEFAULT NULL,
  `pick4` varchar(2) DEFAULT NULL,
  `pick5` varchar(2) DEFAULT NULL,
  `pick6` varchar(2) DEFAULT NULL,
  PRIMARY KEY (`ticket_id`)
) ENGINE=InnoDB AUTO_INCREMENT=19675 DEFAULT CHARSET=latin1;

我们还假设我们已经将以下值存储在数据库中:

+-----------+-------------------+-------+-------+-------+-------+-------+-------+
| ticket_id |   ticket_number   | pick1 | pick2 | pick3 | pick4 | pick5 | pick6 |
+-----------+-------------------+-------+-------+-------+-------+-------+-------+
| 655       | 08-09-21-24-46-52 | 8     | 9     | 21    | 24    | 46    | 52    |
| 658       | 08-23-24-40-42-45 | 8     | 23    | 24    | 40    | 42    | 45    |
| 660       | 07-18-19-20-22-31 | 7     | 18    | 19    | 20    | 22    | 45    |
| ...       | ...               | ...   | ...   | ...   | ...   | ...   | ...   |
| 19674     | 06-18-33-43-49-50 | 6     | 18    | 33    | 43    | 49    | 50    |
+-----------+-------------------+-------+-------+-------+-------+-------+-------+

现在,我的目标是根据 ticket_number 字段中的各自值(每组 6 个元素,拆分通过 -)。换句话说,假设我比较 ticket_id = 655ticket_id = 658,根据它们各自 ticket_number 字段中的元素,然后我会发现元素 0824出现在两组中。如果我们现在比较 ticket_id = 660ticket_id = 19674,那么我们只有一个巧合:18.

我实际用来执行这些比较的是以下查询:

select A.ticket_id, A.ticket_number, P.ticket_id, P.ticket_number, count(P.ticket_number) as cnt from ticket A inner join ticket P on A.ticket_id != P.ticket_id
where 
((A.ticket_number like concat("%", lpad(P.pick1,2,0), "%"))
+ (A.ticket_number like concat("%", lpad(P.pick2,2,0), "%"))
+ (A.ticket_number like concat("%", lpad(P.pick3,2,0), "%"))
+ (A.ticket_number like concat("%", lpad(P.pick4,2,0), "%"))
+ (A.ticket_number like concat("%", lpad(P.pick5,2,0), "%"))
+ (A.ticket_number like concat("%", lpad(P.pick6,2,0), "%")) > 3) group by A.ticket_id
having cnt > 5;

也就是说,首先我创建一个 INNER JOIN 连接所有具有不同 ticket_id 的行,然后我将每个 P.pickX (X=[1..6]) 与 A.ticket_number 生成的 INNER JOIN 操作,我计算两组之间的匹配数。

最后执行后,得到了这样的东西:

+-------------+-------------------+-------------+-------------------+-----+
| A.ticket_id |  A.ticket_number  | P.ticket_id |  P.ticket_number  | cnt |
+-------------+-------------------+-------------+-------------------+-----+
| 8489        | 14-21-28-32-48-49 | 2528        | 14-21-33-45-48-49 | 6   |
| 8553        | 02-14-17-38-47-53 | 2364        | 02-30-38-44-47-53 | 6   |
| 8615        | 05-12-29-33-36-43 | 4654        | 12-21-29-33-36-37 | 6   |
| 8686        | 09-13-29-34-44-48 | 6038        | 09-13-17-29-33-44 | 6   |
| 8693        | 01-10-14-17-42-50 | 5330        | 01-10-37-42-48-50 | 6   |
| ...         | ...               | ...         | ...               | ... |
| 19195       | 05-13-29-41-46-51 | 5106        | 07-13-14-29-41-51 | 6   |
+-------------+-------------------+-------------+-------------------+-----+ 

问题

问题是我对 10476 rows 的 table 执行了此操作,导致更多棕褐色 1 亿 ticket_numberpickX 进行比较,持续约 172 秒总而言之。这太慢了。

目标

我的目标是尽可能快地执行此操作,以便在不到一秒的时间内完成,因为这必须实时进行。

这可能吗?

如果你想保留当前的结构,那么将 pick1..6 改为 tinyint 类型而不是 varchar

TINYINT(1) 存储 -128 到 128 之间的值(如果有符号)。然后您的查询将不会与 % 连接,这是 运行.

缓慢的原因

那么,这两个查询会给你相同的结果

select * FROM ticket where pick1 = '8';
select * FROM ticket where pick1 = '08';

这是 sql 结构:

CREATE TABLE `ticket` (
  `ticket_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `ticket_number` varchar(30) DEFAULT NULL,
  `pick1` tinyint(1) unsigned zerofill DEFAULT NULL,
  `pick2` tinyint(1) unsigned zerofill DEFAULT NULL,
  `pick3` tinyint(1) unsigned zerofill DEFAULT NULL,
  `pick4` tinyint(1) unsigned zerofill DEFAULT NULL,
  `pick5` tinyint(1) unsigned zerofill DEFAULT NULL,
  `pick6` tinyint(1) unsigned zerofill DEFAULT NULL,
    PRIMARY KEY (`ticket_id`)
  ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

我想,你甚至可以删除 zerofill

如果这不起作用,请更改 table 设计。

数字可以有多大?看起来像 50。如果答案是 63 或更少,则将格式更改为:

所有 6 个数字都存储在一个 SET ('0','1','2',...,'50') 中,并使用 suitable 操作设置第 n 位。

然后,比较两组变成BIT_COUNT(x & y)找出有多少匹配。一个简单的比较将测试是否相等。

如果您的目标是查看 table 中是否已经有特定的彩票猜测,则为该列建立索引以便快速查找。我指的不是分钟甚至秒,而是几毫秒。即使是十亿行。

位运算可以用 SQL 或您的客户端语言完成。例如,要为 (11, 33, 7) 构建 SET,代码将是

INSERT INTO t SET picks = '11,33,7'  -- order does not matter

这也行得通:

... picks = (1 << 11) |
            (1 << 33) |
            (1 <<  7)

一个简单的例子:

CREATE TABLE `setx` (
  `picks` set('1','2','3','4','5','6','7','8','9','10') NOT NULL
) ENGINE=InnoDB;
INSERT INTO setx (picks) VALUES ('2,10,6');
INSERT INTO setx (picks) VALUES ('1,3,5,7,9'), ('2,4,6,8,10'), ('9,8,7,6,5,4,3,2,1,10');
SELECT picks, HEX(picks+0) FROM setx;
+----------------------+--------------+
| picks                | HEX(picks+0) |
+----------------------+--------------+
| 2,6,10               | 222          |
| 1,3,5,7,9            | 155          |
| 2,4,6,8,10           | 2AA          |
| 1,2,3,4,5,6,7,8,9,10 | 3FF          |
+----------------------+--------------+
4 rows in set (0.00 sec)