【问题标题】:Identify Unique Sentences in SQL String with Repeat Sentences用重复句子识别 SQL 字符串中的唯一句子
【发布时间】:2018-03-08 19:30:44
【问题描述】:

我在 SQL 中有一个 VARCHAR(MAX) 字符串字段,在某些情况下,它在 LEN 中最多包含 100k 个字符。让我们称之为 [Notes_Comments]。

[Notes_Comments] 字段的内容是注释和 cmets,在某些情况下,这些注释和 cmets 会被多次复制/粘贴。没有关于新旧部分开始/结束的描述。标点符号也是命中注定的。

我想弄清楚如何在 [Notes_Comments] 字段中识别唯一的句子或字符串/子字符串,以从本质上获得纯粹的、去重的内容视图。

我构建了一个 SplitString 函数,并且能够开始解析 2,3 和 5 个单词的短语,但它并没有真正让我到达我需要得到完整句子的地方。

示例 [Notes_Comments]:

我想要这个


上午 10:46 与 blah 谈论 blah 上午 11:46 他们说他们不知道我的意思 下午 12:46 构建了一个 SplitString 函数并将单词解析为带有单词索引的表格 下午 1:46 进展顺利识别单词但不是完整的字符串 2:46 PM 删除干扰词 3:46 PM 删除已知类别的单词 4:46 PM 试图弄清楚如何删除一个字符串字段的重复部分。笔记 一些重复的东西。现在添加了一些新的东西。注释 增加了一点。必须对出现多次的文本部分进行重复数据删除。备注 再来一节。


从这里


上午 10:46 与 blah 谈论 blah 上午 11:46 他们说他们不知道我的意思 下午 12:46 构建了一个 SplitString 函数并将单词解析为带有单词索引的表格 下午 1:46 进展顺利识别单词但不是完整的字符串 2:46 PM 删除干扰词 3:46 PM 删除已知类别的单词 注释 一些重复的东西 10:46 AM 与 blah 谈论 blah 11:46 AM 他们说他们不知道我的意思 12:46 PM 构建了一个 SplitString 函数并将单词解析成一个带有单词索引的表 1:46 PM 识别单词但不是完整字符串的良好进展 2:46 PM 删除噪声词 3:46 PM 删除已知类别的单词下午 4:46 试图弄清楚如何删除一个字符串字段的重复部分。笔记 一些重复的东西。现在添加了一些新的东西。注释 增加了一点。必须对出现多次的文本部分进行重复数据删除。 上午 10:46 与 blah 谈论 blah 上午 11:46 他们说他们不知道我的意思 下午 12:46 构建了一个 SplitString 函数并将单词解析为带有单词索引的表格 1:46 PM 识别单词但不是完整字符串的良好进展 2:46 PM 去除噪音单词 3:46 PM 去除已知类别的单词 4:46 PM 试图弄清楚如何去除重复部分一个字符串字段。笔记 一些重复的东西。现在添加了一些新的东西。注释 增加了一点。必须对出现多次的文本部分进行重复数据删除。 Notes 还有一节。


这是我的 SplitString 函数:

CREATE FUNCTION dbo.SplitString 
(
    @str NVARCHAR(max), 
    @separator CHAR(1)
)
RETURNS TABLE
AS
RETURN (
WITH tokens(p, a, b) AS (
    SELECT 
        CAST(1 AS bigint), 
        CAST(1 AS bigint), 
        CHARINDEX(@separator, @str)
    UNION all
    SELECT
        p + 1, 
        b + 1, 
        CHARINDEX(@separator, @str, b + 1)
    FROM tokens
    WHERE b > 0
)
SELECT
    p-1 ItemIndex,
    SUBSTRING(
        @str, 
        a, 
        CASE WHEN b > 0 THEN b-a ELSE LEN(@str) END) 
    AS word
FROM tokens
);

GO

【问题讨论】:

  • 欢迎来到 StackOverflow :) 这感觉像是一个多层次的问题,有很多可能的答案。您描述的重复数据删除在 ​​SQL 中会很复杂,所以如果您对 PHP、Java 等中的周围逻辑没问题,请提及。在巨大的 varchar 上执行此操作可能是最痛苦的方式,因此还值得一提的是是否有理由保持这种方式,或者您是否对其他设计持开放态度。发布您的 SplitString 代码将有助于指出您可以从那里去哪里。最后,这是一次性操作还是常规操作,您的数据集大小是多少?
  • 感谢您的欢迎。我愿意不惜一切代价包围它。最后,我需要能够查询数据,所以我需要最终结果的格式可以导入或加载回数据库。当前数据集位于 SQL 中,它将是每周针对数千行运行的操作。

标签: sql-server tsql parsing split varchar


【解决方案1】:

为了帮助前进,我建议将“Notes”列从当前所在的表中移除,并创建一个“EntityId”和“Note”的新表。 EntityId 是您要将注释关联到的父记录。这样,您的实体和便笺之间就建立了一对多的关系。

这需要做很多工作才能改变,但维护起来会容易得多,并且可以防止与您当前遇到的类似问题。

对于纯粹的 sql 方法,我将首先更改表关系。执行拆分,并将每条单独的消息插入到新表中。完成此操作后,您可以编写一个单独的过程来识别重复项,并对除其中一个之外的所有重复项执行删除。

我应该注意到,下面是一种应用方法。

如果你选择更新你的表关系,先实现它,然后去重会变成一个两步的过程,但是实现起来比较简单。

对于重复数据删除,您必须考虑到您所说的标点符号是命中还是未命中的事实。

我会拆分您的时间戳的 :??AM 和 :??PM 语法。您必须弄清楚语法,但这应该可以帮助您确定条目的开始和结束位置。将它们拆分成一个列表,在添加时将每个 Trim(),如果您根本不需要标点符号,请在“。”上进行替换。和“,”以及您不想要的其他标点符号,同时将它们添加到您的收藏中。

现在您有两个选择,如果您使用表更新,则可以插入所有值,然后在 sql 或代码中过滤重复项。

或者您可以创建一个新列表,进行一些循环并仅将唯一项目添加到此新列表中。如果您保持单列将它们连接成一个字符串并更新您的数据库,或者如果您采用一对多设计,则为每个唯一条目执行插入。

如果您想进行不太精确的匹配,请为每个子字符串构建一个哈希。也许“时间戳”+“”+第一个X字符+最后一个Y字符。然后比较你的哈希值是否相等,再次只保留一个。我推荐这种模式,因为从您的示例中可以看出,重复的笔记具有重复的时间戳。如果不尝试仅将第一个 x 和最后一个 y 作为哈希。

我强烈建议您更新您的表结构,以免您以后遇到类似的麻烦。剩下的就是一点点正则表达式,以及字符串拆分和比较。此外,如果您尝试使用散列进行调试,以确保在将其提交到您的数据库之前不要剪掉太多。

【讨论】:

  • 我也想过这样做。我实际上开始这样做是为了对相同的笔记/数据进行一些短语提取。我将它加载到一个临时表中并删除一堆无用的东西来清理它,然后我解析出单词组合以查找短语。我喜欢你在日期时间拆分的想法。那么,如果我将一对多关系的文本/注释和索引号加载到另一个表中并希望在日期时间进行拆分,那么该代码会是什么样子?
【解决方案2】:

如果您要在代码中拆分字符串,您需要先组合一个正则表达式。我会匹配子字符串“:.. AM”或“:.. PM”。考虑到这是一个注释字段,用户可能可以键入任何他们想要的内容,因此仅匹配“:”似乎有点危险。

请注意,这是未经测试的,但逻辑应该是合理的。您可能需要修改匹配对象访问器,并测试索引值是否准确地拆分到您想要的位置等。

首先将您的字段读入字符串。然后利用正则表达式在字符串中查找消息。类似于以下内容。

string pattern = @":.?*M";
Regex rgx = new Regex(pattern, RegexOptions.None);
MatchCollection matches = rgx.Matches(input);

matches 集合中的匹配对象包含找到匹配项的起点的索引。您需要遍历它们并将子字符串提取到单独的字符串中。比如:

List<string> allNotes = new List<string>();    

for(i = 0, i < matches.Count, i++)
{
    string nextNote = string.Empty;
    int currentIndex = matches[i].index - 2;
    int endIndex = -1;
    if(matches[i+1] != null)
    { 
        endIndex = matches[i + 1].index - 2;
    }

    if(endIndex != -1)
    {
        nextNote = substring(currentIndex, (endIndex - currentIndex);
    }
    else
    {
        nextNote = substring(currentIndex);
    }

    if(!string.IsNullOrEmpty(nextNote))
    {
        allNotes.Add(nextNote);
    }
}

这些子字符串应该跨越时间戳的开始到下一个时间戳之前的字符。

然后您可以执行另一个功能来对列表进行重复数据删除。类似的东西:

Dictionary <string, string> dedupedDict = new Dictionary<string, string>();

foreach(string note in allNotes)
{
    string noteHash = string.concat(note.substring(0, 6), note.substring(note.length - 7));

    if(!dedupedDict.Keys.Contains(noteHash))
    {   
        dedupedDict.Add(noteHash, note);
    }
}

return dedupedDict.Values().ToList();

您可能需要检查字符串的最小长度以避免索引超出范围异常,和/或更改哈希大小(我的示例应该是第一个和最后 6 个字符)。如果您认为这有助于匹配,您可能还希望在某些时候对标点符号进行 Trim() 和/或 Replace(".", "")。

然后使用去重注释列表填充新的 CallNotes 表。您还需要您的呼叫 ID。

清理完当前值后,您可以使用相同的方法在新笔记放入数据库之前拦截它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-17
    • 1970-01-01
    • 2018-10-01
    • 2014-12-02
    • 1970-01-01
    相关资源
    最近更新 更多