如何在 sql 中创建一个查询以将句子切成单词并将它们添加到新的 table 中
how to create a query in sql to chop sentences into words and add them to new table with their frequency
我正在尝试做一个我不确定是否可行的查询
我有一个名为 sentences 的 table,它包含 ID、句子和验证,如下图所示。
我有另一个 table 调用,word count 其中包含 ID、单词和频率。所以我希望每当输入一个句子时更新,或删除这个 table 相应地更新或每天更新一个,因为可能有很多句子
我的预期输出如下图所示。
任何想法都是可行的任何人都可以帮忙。
基于this DBA Stack Exchange post我可以想象出类似下面的东西。
基本步骤:
- 创建一个 table 以包含单词列表(在我的示例中为
word_index
)
- 创建一个 table 来包含字数统计(在我的例子中是
word_count
)
- 创建一个存储过程以根据
SPACE
将句子拆分为单词(可能需要进行调整以允许换行等其他空格)并将其写入 word_index
table
- 计算统计并写入
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
将拆分词传输到目标 table:
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 ;
这是基本设置。请注意,我使用 table 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;
结果:
这是上面记录的结果的屏幕截图:
如果你是运行 MySQL 8.0,我会为此推荐一个递归通用table表达式。这个想法是迭代地遍历每条消息,沿途将其拆分为单词。剩下要做的就是汇总。
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
在早期版本中,您可以使用数字 table 来模拟相同的行为。
示例数据:
sent | verif
:------------------------- | ----:
hello my name is alex | null
hey alin and alex I'm tom | null
hello alex my name is alin | null
结果:
wid | word | freq
--: | :----- | ---:
1 | alex | 3
2 | alin | 2
3 | hello | 2
4 | is | 2
5 | my | 2
6 | name | 2
7 | and | 1
8 | hey | 1
9 | I'm | 1
10 | tom | 1
当谈到在单独的 table 中维护查询结果时,它可能比您想象的要复杂:您需要能够插入、删除或更新目标 table 取决于原始 table 中的更改,这不能在 MySQL 中的单个语句中完成。此外,使原始 table 中的标志保持最新会产生竞争条件,在您更新目标 table.
时可能会发生更改
一个更简单的选择是将查询放在一个视图中,这样您就可以始终了解数据的最新情况。为此,您可以将上述查询包装在 create view
语句中,例如:
create view words_view as < above query >;
如果性能成为问题,那么您还可以定期截断和重新填充单词 table。
truncate table words;
insert into words < above query >;
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
是一个完美的 "natural PK"。但是,您应该决定如何处理大小写折叠和重音去除。
至于分词……双引号和其他一些字符显然是分词的。但是有些字符有歧义:
'
-- 缩写或引用的一部分?
.
-- 缩写或句末
等等
警告:这是 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
对于最新的 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;
PS。我无法重现输出排序,因为我无法理解 freq
组中的排序标准。
我正在尝试做一个我不确定是否可行的查询 我有一个名为 sentences 的 table,它包含 ID、句子和验证,如下图所示。
我有另一个 table 调用,word count 其中包含 ID、单词和频率。所以我希望每当输入一个句子时更新,或删除这个 table 相应地更新或每天更新一个,因为可能有很多句子
我的预期输出如下图所示。
任何想法都是可行的任何人都可以帮忙。
基于this DBA Stack Exchange post我可以想象出类似下面的东西。
基本步骤:
- 创建一个 table 以包含单词列表(在我的示例中为
word_index
) - 创建一个 table 来包含字数统计(在我的例子中是
word_count
) - 创建一个存储过程以根据
SPACE
将句子拆分为单词(可能需要进行调整以允许换行等其他空格)并将其写入word_index
table - 计算统计并写入
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
将拆分词传输到目标 table:
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 ;
这是基本设置。请注意,我使用 table 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;
结果:
这是上面记录的结果的屏幕截图:
如果你是运行 MySQL 8.0,我会为此推荐一个递归通用table表达式。这个想法是迭代地遍历每条消息,沿途将其拆分为单词。剩下要做的就是汇总。
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
在早期版本中,您可以使用数字 table 来模拟相同的行为。
示例数据:
sent | verif :------------------------- | ----: hello my name is alex | null hey alin and alex I'm tom | null hello alex my name is alin | null
结果:
wid | word | freq --: | :----- | ---: 1 | alex | 3 2 | alin | 2 3 | hello | 2 4 | is | 2 5 | my | 2 6 | name | 2 7 | and | 1 8 | hey | 1 9 | I'm | 1 10 | tom | 1
当谈到在单独的 table 中维护查询结果时,它可能比您想象的要复杂:您需要能够插入、删除或更新目标 table 取决于原始 table 中的更改,这不能在 MySQL 中的单个语句中完成。此外,使原始 table 中的标志保持最新会产生竞争条件,在您更新目标 table.
时可能会发生更改一个更简单的选择是将查询放在一个视图中,这样您就可以始终了解数据的最新情况。为此,您可以将上述查询包装在 create view
语句中,例如:
create view words_view as < above query >;
如果性能成为问题,那么您还可以定期截断和重新填充单词 table。
truncate table words;
insert into words < above query >;
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
是一个完美的 "natural PK"。但是,您应该决定如何处理大小写折叠和重音去除。
至于分词……双引号和其他一些字符显然是分词的。但是有些字符有歧义:
'
-- 缩写或引用的一部分?
.
-- 缩写或句末
等等
警告:这是 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
对于最新的 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;
PS。我无法重现输出排序,因为我无法理解 freq
组中的排序标准。