计算 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 = 655
和 ticket_id = 658
,根据它们各自 ticket_number
字段中的元素,然后我会发现元素 08
和 24
出现在两组中。如果我们现在比较 ticket_id = 660
和 ticket_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_number
与 pickX
进行比较,持续约 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)
描述
我有一个 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 = 655
和 ticket_id = 658
,根据它们各自 ticket_number
字段中的元素,然后我会发现元素 08
和 24
出现在两组中。如果我们现在比较 ticket_id = 660
和 ticket_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_number
与 pickX
进行比较,持续约 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)