我有3张桌子:
设置:
id
name
SetItem:
set_id
item_id
position
TEMPSET:
id
我有一个函数可以从Item
表生成新的随机组合。基本上,总是在成功生成之后,我在Set
表中创建一个新行,获取它的id并将所有项ID添加到SetItem
表中。
每次生成新组合之前,我都会截断TempSet
表,将新项ID填入此表,并通过与SetItem
表中的先前组合进行比较来检查相似性百分比。如果新组合相似度大于或等于30%,我需要阻止这种组合并重新生成新的组合。
相似性意味着 - 在先前生成的组合上存在元素。所以,这个想法是:
如果新生成的集合中有3个以上的元素在某个先前生成的集合上重复,请阻止它并尝试生成另一个组合。
以下是生成新组合的功能:
CREATE DEFINER = `root` @`localhost` FUNCTION `gen_uniq_perm_by_kw` (
comboSize INT ( 5 ),
tries INT ( 3 )
) RETURNS text CHARSET utf8 SQL SECURITY INVOKER BEGIN
iterat :
LOOP
DELETE
FROM
`TempSet`;
INSERT INTO `TempSet` ( `id` ) (
SELECT
`i`.`id`
FROM
`Item` AS `i`
ORDER BY
RAND( )
LIMIT comboSize
);
IF
(
SELECT
1
FROM
`SetItem`
GROUP BY
`set_id`
HAVING
sum(
CASE
WHEN EXISTS (
SELECT
id
FROM
`TempSet`
WHERE
`id` = `item_id`
LIMIT 1
) THEN
1 ELSE 0
END
) / count( 1 ) * 100 >= 30
LIMIT 1
) < 1 THEN
RETURN ( SELECT GROUP_CONCAT( id SEPARATOR '-' ) FROM `TempSet` );
END IF;
SET tries := tries - 1;
IF
tries = 0 THEN
RETURN NULL;
END IF;
END LOOP iterat;
END
当我测试它时,即使新生成的组合元素在任何其他先前生成的组合中不存在,它也会返回null。
我的问题是,我做错了什么?
答案 0 :(得分:2)
我的问题是,我做错了什么?
您的SetItem表中没有任何数据。
编辑:你评论说这是错的;你在SetItem中有300k行。
我有一个工作的例子。您似乎无法像您一样使用标量子查询。我这样做了:
DROP FUNCTION IF EXISTS gen_uniq_perm_by_kw;
DELIMITER ;;
CREATE DEFINER = `root` @`localhost` FUNCTION `gen_uniq_perm_by_kw` (comboSize INT, tries INT) RETURNS text CHARSET utf8 SQL SECURITY INVOKER
BEGIN
iterat :
LOOP
DELETE FROM `TempSet`;
INSERT INTO `TempSet` (`id`)
SELECT `i`.`id` FROM `Item` AS `i` ORDER BY RAND() LIMIT comboSize;
IF EXISTS(
SELECT set_id,
SUM(CASE WHEN EXISTS (SELECT id FROM `TempSet` WHERE `id` = `item_id` LIMIT 1) THEN 1 ELSE 0 END) AS group_sum,
COUNT(*) AS group_count
FROM `SetItem`
GROUP BY `set_id`
HAVING group_sum * 10 / group_count < 3
) THEN
RETURN (SELECT GROUP_CONCAT(id SEPARATOR '-') FROM `TempSet`);
END IF;
SET tries = tries - 1;
IF tries = 0 THEN
RETURN NULL;
END IF;
END LOOP iterat;
END
我还以更简单的方式使用它,而不使用SUM和额外的子查询:
DROP FUNCTION IF EXISTS gen_uniq_perm_by_kw;
DELIMITER ;;
CREATE DEFINER = `root` @`localhost` FUNCTION `gen_uniq_perm_by_kw` (comboSize INT, tries INT) RETURNS text CHARSET utf8 SQL SECURITY INVOKER
BEGIN
iterat :
LOOP
DELETE FROM `TempSet`;
INSERT INTO `TempSet` (`id`)
SELECT `i`.`id` FROM `Item` AS `i` ORDER BY RAND() LIMIT comboSize;
IF EXISTS(
SELECT s.set_id,
COUNT(t.id) AS group_matches,
COUNT(*) AS group_count
FROM SetItem AS s LEFT OUTER JOIN TempSet AS t ON t.id = s.item_id
GROUP BY s.set_id
HAVING group_matches * 10 / group_count < 3
) THEN
RETURN (SELECT GROUP_CONCAT(id SEPARATOR '-') FROM `TempSet`);
END IF;
SET tries = tries - 1;
IF tries = 0 THEN
RETURN NULL;
END IF;
END LOOP iterat;
END
答案 1 :(得分:1)
如果你愿意用“相同”来获得一点点松散 - 请考虑这个替代方案:
在旧版本的MySQL中,您只能使用64位BIGINT UNSIGNED
字符串,除非您愿意使用它们并添加计数。 (我已为此编写代码。)对于较新的版本,BLOB
可用于此操作。
无论是否有散列(数字,0..63,blob),都有可能发生碰撞。在许多应用中,这可以被忽略为系统中的微小“噪声”。你的情况如何?
我提出的建议比你概述的设计更快,可能更小(数据方面)。
答案 2 :(得分:1)
如果您正在使用MySQL 5.7和JSON功能,则可以执行以下操作:
set @v1 = CONCAT("[", (SELECT group_concat(r.id SEPARATOR ',') FROM (select o.id from test.item o order by rand() limit 10) r), "]");
现在@ v1的JSON_ARRAY为10个随机项元素。
通过此查询,您将了解项目收集合并:
select set_id, @v1 as serie, count(*) * 10 as CollisionPercentage from test.setitem
where JSON_CONTAINS(@v1, JSON_ARRAY(item_id))
group by set_id, serie
order by CollisionPercentage desc;
First CollisionPercentage值确定最大碰撞。
<强> EDITED 强>
试试这个。注意声明的数据库名称。
DROP FUNCTION IF EXISTS gen_uniq_perm_by_kw;
DELIMITER ;;
CREATE DEFINER = `root` @`localhost` FUNCTION `gen_uniq_perm_by_kw` (comboSize INT, tries INT, collisions INT) RETURNS text CHARSET utf8 SQL SECURITY INVOKER
BEGIN
iterat :
LOOP
set @v1 = CONCAT(
'[',
(SELECT group_concat(r.id SEPARATOR ',') FROM (select o.id from test.item o order by rand() limit comboSize) r),
']'
);
IF EXISTS(
select set_id, count(*) * 10 as CollisionPercentage from test.setitem
where JSON_CONTAINS(@v1, JSON_ARRAY(item_id))
group by set_id
having count(*) < collisions
order by CollisionPercentage desc
LIMIT 1
) THEN
RETURN @v1;
END IF;
SET tries = tries - 1;
IF tries = 0 THEN
RETURN NULL;
END IF;
END LOOP iterat;
END;;
DELIMITER ;
select gen_uniq_perm_by_kw(5,5,30);
结果
+--------------------------------------+
| test.gen_uniq_perm_by_kw(5,5,30) |
+--------------------------------------+
| [30111,10916,13446,6617,10918] |
+--------------------------------------+
1 row in set (0.00 sec)
答案 3 :(得分:1)
您应检查10个新生成的item_id,而不是检查MD5校验和,而在一个set-id中有3次或更多次出现。
你不能检查:
SELECT count( * )
FROM `Set`
WHERE `Set`.`hash` = @md5
LIMIT 1
但不应该检查:
select 1
from setitems
where item_id in ( a,b,c, put here your 10 fresh generated item )
group by set_id
having count(1) >= 3
limit 1
当您的“列表内”中存在包含3个或更多item_id的集合时,此查询将返回1。
当项目数量不同(不总是10)时,您还可以计算组中的项目来计算百分比:
select 1
from setitems
group by set_id
having sum(
case when find_in_set(item_id , @list)
then 1
else 0
end
) / count(1) * 100 >= 30
limit 1;
@list应以逗号分隔 https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_find-in-set
答案 4 :(得分:-1)
CREATE TABLE `combinations` (
`id` INT(10) NOT NULL,
`nb` INT(10) NOT NULL,
`orderid` INT(10) NOT NULL,
PRIMARY KEY (`id`, `orderid`),
INDEX `On_nb` ( `nb`,`id`)
)
COLLATE='utf8_bin'
ENGINE=InnoDB
;
insert into `combinations` values
(1, 13446,1),
(1, 10860,2),
(1, 10885,3),
(1, 10853,4),
(1, 13048,5),
(1, 13044,6),
(1, 10918,7),
(1, 10916,8),
(1, 6519,9),
(1, 10860,10),
(2, 13527,1),
(2, 10933,2),
(2, 10928,3),
(2, 10922,4),
(2, 6595,5),
(2, 10944,6),
(2, 13446,7),
(2, 10860,8),
(2, 10885,9),
(2, 19888,10),
(3, 13364,1),
(3, 12949,2),
(3, 6732,3),
(3, 6763,4),
(3, 13542,5),
(3, 6617,6),
(3, 13125,7),
(3, 13058,8),
(3, 13059,9),
(3, 30111,10);
select c1.id, count(c1.nb) from `combinations`as c1, `combinations` as c_ori
where c1.nb=c_ori.nb and c_ori.id=2 and c1.id!=c_ori.id
group by c1.id having count(c1.nb)>=3
"id" "count(c1.nb)"
"1" "4"
当最后一个查询返回某些内容时,已存在第二个组合,其粒度至少为30%。 请注意,在您的第一个组合中,编号10860有两次。此算法未考虑正确重复的数字。您的组合中是否需要重复的数字?