如何在 sql 中创建查询以将句子切成单词并将它们按频率添加到新表中

Posted

技术标签:

【中文标题】如何在 sql 中创建查询以将句子切成单词并将它们按频率添加到新表中【英文标题】:how to create a query in sql to chop sentences into words and add them to new table with their frequency 【发布时间】:2020-08-22 11:12:39 【问题描述】:

我正在尝试做一个我不确定是否可行的查询 我有一个名为 sentences 的表,其中包含 ID、Sentences 和验证,如下图所示。

我有另一个名为word count 的表,其中包含 ID、单词和出现频率。所以我希望如果输入的句子更新或删除,以便该表每天相应更新或更新,因为可能有很多句子

我的预期输出如下图所示。

任何想法都是可行的,任何人都可以帮忙。

【问题讨论】:

【参考方案1】:

如果您运行的是 mysql 8.0,我建议为此使用递归公用表表达式。这个想法是迭代地遍历每条消息,沿途将其分成单词。剩下要做的就是聚合。

with recursive cte as (
    select 
        substring(concat(sent, ' '), 1, locate(' ', sent)) word,
        substring(concat(sent, ' '), locate(' ', sent) + 1) sent
    from messages
    union all
    select 
        substring(sent, 1, locate(' ', sent)) word,
        substring(sent, locate(' ', sent) + 1) sent
    from cte
    where locate(' ', sent) > 0
)
select row_number() over(order by count(*) desc, word) wid, word, count(*) freq
from cte 
group by word
order by wid

在早期版本中,您可以使用数字表模拟相同的行为。

Demo on DB Fiddle

样本数据:

已发送 |验证 :------------------------- | ----: 你好我的名字是亚历克斯| 嘿 alin 和 alex 我是 tom | 你好亚历克斯我的名字是阿林|

结果:

宽 |词 |频率 --: | :----- | ---: 1 |亚历克斯 | 3 2 |阿林 | 2 3 |你好 | 2 4 |是| 2 5 |我的 | 2 6 |姓名 | 2 7 |和 | 1 8 |嘿| 1 9 |我是 | 1 10 |汤姆| 1

说到在单独的表中维护查询的结果,可能比你想象的要复杂:你需要能够根据原表的变化对目标表进行插入、删除或更新,这不能在 MySQL 中的单个语句中完成。此外,使原始表中的标志保持最新会产生竞争条件,在您更新目标目标表时可能会发生变化。

一个更简单的选择是将查询放在一个视图中,这样您就可以随时了解数据的最新情况。为此,您可以将上述查询包装在 create view 语句中,例如:

create view words_view as < above query >;

如果性能成为问题,那么您也可以定期截断并重新填充单词表。

truncate table words;
insert into words < above query >;

【讨论】:

我不想要一个最新的目标文件,但我希望它在每天结束时根据源文件进行更新。 我使用的是 Linux ubuntu 18.04,其中配置了 xampp 7.4.5,所以希望你的想法适用于我的情况 @programmingfreak:这与您的 MySQL 版本有关,而不是您的操作系统或 xamp 的版本。您可以运行select version() 来查看您使用的 MySQL 版本。 好的,但是我可以做点什么,在验证旁边的主表中打印句子中频率较低的单词的 WID,因此添加另一个字段,该字段将打印较少的单词 ID那句话中经常使用的词?我觉得这有点棘手@GMB @programmingfreak:这看起来与您最初提出的问题不同。我建议问一个new question 而不是添加到现有(和赏金)的问题。【参考方案2】:

Perl 和 php 以及其他有一个更强大的用于拆分的正则表达式引擎。我会使用其中之一,而不是 SQL。

我会使用批量插入,使用

INSERT INTO words (word, ct)
    VALUES ('this', 1), ('that', 1), ...   -- about 100 words at a time
    ON DUPLICATE KEY UPDATE ct = VALUES(ct) + 1;

CREATE TABLE words (
    word VARCHAR(66) NOT NULL,
    ct MEDIUMINT UNSIGNED NOT NULL,
    PRIMARY KEY(word)
) ENGINE=InnoDB;

我认为不需要在单独的表中包含单词和计数,也不需要 AUTO_INCREMENT 来获取“word_id”。 word是完美的“自然PK”。但是,您应该决定如何处理大小写折叠和重音去除。

至于拆分成单词……双引号和其他一些字符显然是单词边界。但是有些字符是模棱两可的:

' -- 缩写或引用的一部分?. -- 缩写或句子结尾

等等

【讨论】:

我同意。非 SQL 解决方案(包括使用正则表达式)可能会更简单、更健壮且更高效。【参考方案3】:

基于this DBA Stack Exchange post,我可以想象如下。

基本步骤:

    创建一个包含单词列表的表(在我的示例中为word_index) 创建一个包含字数统计的表(在我的示例中为word_count) 创建一个存储过程,根据SPACE 将句子拆分为单词(可能需要进行调整以允许其他空格,如换行)并将其写入word_index 表中 计算统计并写入word_count

一步一步编写代码:

创建word_index:

CREATE TABLE IF NOT EXISTS `word_index` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `word` varchar(150) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

创建word_count:

CREATE TABLE IF NOT EXISTS `word_count` (
  `word` varchar(150) NOT NULL,
  `occurrences` int(11) DEFAULT NULL,
  PRIMARY KEY (`word`)
)

创建过程transfer_cell,将拆分后的单词转移到目标表中:

DELIMITER //
CREATE FUNCTION `SPLIT_STRING`(val TEXT, delim VARCHAR(12), pos INT) RETURNS text CHARSET latin1
BEGIN
        DECLARE output TEXT;
        SET output = REPLACE(SUBSTRING(SUBSTRING_INDEX(val, delim, pos), CHAR_LENGTH(SUBSTRING_INDEX(val, delim, pos - 1)) + 1), delim, '');
        IF output = '' THEN
            SET output = null;
        END IF;
        RETURN output;
    END//
DELIMITER ;

-- Dumping structure for procedure test.TRANSFER_CELL
DELIMITER //
CREATE PROCEDURE `transfer_cell`()
BEGIN
        DECLARE i INTEGER;
        SET i = 1;
        REPEAT
            INSERT INTO word_index (word)
            SELECT SPLIT_STRING(sent, ' ', i)
            FROM sentences
            WHERE SPLIT_STRING(sent, ' ', i) IS NOT NULL;
            SET i = i + 1;
        UNTIL ROW_COUNT() = 0
        END REPEAT;
    END//
DELIMITER ;

这是基本设置。请注意,我使用表 sentences 而不是 sentencess 和双 s。

更新统计数据:

TRUNCATE TABLE word_index;
TRUNCATE TABLE word_count;

CALL transfer_cell();
INSERT INTO word_count
  SELECT word, COUNT(1) occurrences FROM word_index 
  GROUP BY word;

结果:

这是上面记录的结果的屏幕截图:

【讨论】:

很抱歉提出这个问题,但我在 phpmyadmin 内部并进入 sql 执行前 2 个 sql 代码,但第三个没有在 phpmyadmin 或 php 中执行,我该如何测试它? @programmingfreak 将其传递到 PhpMyAdmin 的查询窗口时是否出现任何错误?我建议使用数据库 IDE(HeidiSQL 是免费的)。根据用于执行 DDS 语句的用户,可能会禁止创建函数。 是的,我的系统是 ubuntu,我使用 xampp 本地主机访问 phpmyadmin,它在传递查询时给了我错误 @programmingfreak 你能分享你得到的错误信息吗? 对于 phpmyadmin 中存储的例程,请参阅:how-to-write-a-stored-procedure-in-phpmyadmin【参考方案4】:

对于最新的 MySQL 版本(8.0.4 及更高版本),您可以使用

SELECT ROW_NUMBER() OVER (ORDER BY COUNT(word) DESC, word) wid, word, COUNT(word) freq 
FROM sentencess 
CROSS JOIN JSON_TABLE( CONCAT('["', REPLACE(sentencess.sent, ' ', '","'), '"]'),
                       "$[*]" COLUMNS( word VARCHAR(254) PATH "$" )
                     ) AS jsontable
GROUP BY word
ORDER BY freq DESC, word;

fiddle

PS。我无法重现输出排序,因为我无法理解 freq 组中的排序标准。

【讨论】:

【参考方案5】:

警告:这是 TSql,不是 MySQL。

-- 1. To create a function that splits the sentence into words, and returns the Words Table
-- 2. To insert into your Result Table all of the words Table results
-- 3. Calculate the Frequency

----------[ The Split Function ]
CREATE FUNCTION dbo.udf_SplitString 
        (
                @Sentence   varchar(max)
            ,   @Separator  char(1) 
        )
        RETURNS @WordList TABLE (Word varchar(50)) 
    AS
        BEGIN
            SET @Separator  =   ISNULL(@Separator, ' ') 

            DECLARE @Word   varchar(50)

            SET @Sentence = LTRIM(@Sentence) + @Separator -- Make sure last word has a separator after. Also get rid of leading spaces.

            WHILE   (CHARINDEX(@Separator, @Sentence) > 0)
                BEGIN
                    SET @Word = SUBSTRING(@Sentence, 1, CHARINDEX(@Separator, @Sentence) - 1)
                    INSERT INTO @WordList   SELECT LTRIM(@Word)
                    -- Remove word added to the List from the sentence.
                    SET @Sentence =  SUBSTRING(@Sentence,   CHARINDEX(@Separator, @Sentence) + 1,   LEN(@Sentence))
                    SET @Sentence =  LTRIM(@Sentence)           
            END                 
            RETURN
        END 

----------[ The Script ]
DECLARE     @SentenceList   TABLE   (Sentence varchar(max))
INSERT INTO @SentenceList   VALUES
            ('hello my name is alex')
        ,   ('hey alin and alex I''m tom')  
        ,   ('hello alex my name is alin')

DECLARE     @WordList   TABLE   (Word varchar(50))

INSERT INTO @WordList   
SELECT  
        W.Word
FROM        @SentenceList   S
CROSS APPLY (
                SELECT Word FROM dbo.udf_SplitString(S.Sentence, ' ')
            ) W 

SELECT 
        ID  =   ROW_NUMBER() OVER(ORDER BY SUM(1) DESC, Word)
    ,   Word
    ,   Frequency   =   SUM(1)
FROM @WordList
GROUP BY Word

【讨论】:

它应该只计算每个单词之间的一个空格,并从开头或单词之间修剪任何其他多余的空格 所以基本上单词应该被标记在它们之前或之后没有任何空格 它会做到的。该函数负责处理额外的空间。在任何句子中添加额外的空格并运行脚本。记得创建函数。 您使用哪个程序来访问数据库并创建我的 phpmyadmin gui sql 不接受的功能? 对不起,是TSql

以上是关于如何在 sql 中创建查询以将句子切成单词并将它们按频率添加到新表中的主要内容,如果未能解决你的问题,请参考以下文章

如何存储句子中的单词

如何在 SQL Server 中创建一个接受一列数据的函数?

如何检索用于在 Oracle 中创建视图的 SQL?

从数据库中替换句子中的单词(Python / Django)

如何在 MariaDb 中创建表并将数据导入 sql? [复制]

如何在 obiee 中创建词/标签云