在MySQL中插入时检查和阻止类似的字符串

时间:2017-07-12 12:08:51

标签: mysql sql hash combinations similarity

简要信息

我有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。

我的问题是,我做错了什么?

5 个答案:

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

如果你愿意用“相同”来获得一点点松散 - 请考虑这个替代方案:

  • 不要将哈希写成数字,而应将其视为位字符串中的位置。
  • BIT_COUNT(a.bits&amp; b.bits)给出两个位串中相同的位数。

在旧版本的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有两次。此算法未考虑正确重复的数字。您的组合中是否需要重复的数字?