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

Posted

技术标签:

【中文标题】在 MySQL 中插入时检查和防止类似的字符串【英文标题】:Checking and preventing similar strings while insertion in MySQL 【发布时间】:2017-12-16 20:39:20 【问题描述】:

简介

我有 3 张桌子:

设置:

id
name

设置项:

set_id
item_id
position

临时设置:

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 作为结果。

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

【问题讨论】:

修复您的数据结构,以便每行存储一个项目而不是一个字符串。这是在 SQL 中存储数据的正确方法。 @GordonLinoff 我已经有了。它对我有什么帮助? 我有单独的表,其中组合元素存储在多对多关系结构中 删除此问题并提出另一个问题,描述该数据结构。 对于这种情况,最好使用图形数据库,它可以显示存在多少组合具体元素。检查neo4j。否则开发 2 名工人:generatorvalidatorGenerator 将生成组合字符串并设置isValid: 0isValidated: 0 标志。 Validator 将在 isValidated: 0 记录上工作,并将检查每一行的每个元素并验证您的条件并设置 isValid: 1(如果没问题)和 isValidated: 1 以防止再次验证它。 【参考方案1】:

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

您的 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

【讨论】:

You don't have any data in your SetItem table.: 我已经设置了 id item id,基于关系的数据。来自先前组合的大约 300k 行。例如,如果项目 id:123、124、320 在集合 id 1 中,则 SetItem 看起来像,1 | 123 | 1; 1 | 124 | 2; 1 | 320 | 3 以下是结果:["11820","5548","5019","6247","8254","9469","5512","3482","7304","10011","6170","12514","5039","14429","5573","9453"] ["6247","8254","11820","9452","12220","4212","6170","14429","5548","5512","10088","12514","8960","5019","5573","9489"] 我手动计算了重复元素。这些集合中有超过 3 个重复元素 有什么建议吗? 我从您最初的尝试中复制了您的 &gt;= 3 条件。但如果你只想要那些 有 30% 匹配的人,你似乎需要相反的条件 &lt; 3。我已经在上面编辑了我的答案。 结果必须是 【参考方案2】:

如果你愿意让“相同”有点松散,请考虑以下替代方案:

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

在旧版本的 mysql 中,您只能使用 64 位 BIGINT UNSIGNED 字符串,除非您愿意拥有一堆字符串并将它们加起来。 (我已经为此编写了代码。)对于较新的版本,BLOB 可以用于此操作。

无论散列(数字、0..63、blob)如何,都有可能发生冲突。在许多应用中,这可以作为系统中的小“噪音”而被忽略。你的情况如何?

我的建议比您概述的设计要快得多,并且可能更小(数据方面)。

【讨论】:

我需要更多解释。这个答案非常广泛。您能否更具体一些并尝试根据当前结构进行解释? 首先我想更好地理解最终目标。以及像 10885 这样的数字来自哪里。 (这对于哈希来说相当小且容易重复。) 数字来自项目。项目表具有自动递增的 id。 然后一个 20K 的位串就可以工作,没有任何松散的-goosey。 更新问题,请看一下。现在一定更清楚了。【参考方案3】:

您应该检查 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

当您的“in-list”中存在具有 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

【讨论】:

可能超过10个,可以吗? 这是可能的,但你应该决定如何计算百分比......我的例子我使用 >= 3 对应于现有集合的 30%......公式,您还应该确定集合中的项目数以找到百分比... 您能否将您的解决方案集成到提供的功能中?我很难在现有功能上实施您的解决方案 我帮不了你,我不写mysql代码。 这一行:CASE WHEN item_id` IN @list THEN` 给了我错误。 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ...【参考方案4】:

如果您使用的是 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 有一个包含 10 个随机项目元素的 JSON_ARRAY。

通过此查询,您将知道项目集合冲突:

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;

第一个 CollisionPercentage 值确定最大碰撞。

已编辑

试试这个。注意声明的数据库名称。

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)

【讨论】:

能否将您的解决方案集成到提供的功能中?我很难在现有功能上实施您的解决方案 对不起,我很忙。我认为展示方式就足够了。 更新问题,请看一下。现在一定更清楚了。 @TuralAliyev 检查是否适合你【参考方案5】:
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 出现了两次。该算法没有考虑正确重复的数字。您的组合中是否需要重复的数字?

【讨论】:

这是错误的。 1个号码只能组合1次 我需要更多解释 所以如果所有组合都有 10 个数字,查询类似“select c1.id, count(c1.nb) from combinationsas c1, combinations as c_ori where c1.nb=c_ori.nb并且 c_ori.id=9999 和 c1.id!=c_ori.id 由 c1.id 分组,具有 count(c1.nb)>=3;"其中 9999 是最后插入的组合 id 检查表中是否有相似的数据:如果有这样的组合,它将返回行,如果没有相似的组合,则返回任何内容。 您可以在插入另一个具有相同结构的表(例如 temp_combination)之前对组合进行测试。测试新组合的查询将是“从组合中选择 c1.id, count(c1.nb) 作为 c1, temp_combinations 作为 c_ori 其中 c1.nb=c_ori.nb 和 c_ori.id=9999 和 c1.id!=c_ori。 id 由 c1.id 分组,count(c1.nb)>=3;"如果没有返回任何行,您可以将内容从 temp_combinations 移动到 combinations 这个查询计算每个组合匹配数字的数量。语句“有 count(c1.nb)”确保只返回至少有 3 个匹配数字的组合

以上是关于在 MySQL 中插入时检查和防止类似的字符串的主要内容,如果未能解决你的问题,请参考以下文章

Erlang:如果列表中存在字符,则在插入文本时检查字符

在mysql中插入时保留双引号和单引号以及转义字符

如何将字符串数组插入mysql json列

如何防止光标在离开 Vim 的插入模式时向后移动一个字符?

如何防止跨站点脚本?

已解决 在 MySQL/Python 中使用转义字符创建插入查询