使用 sqlite 递归计算出现次数
Posted
技术标签:
【中文标题】使用 sqlite 递归计算出现次数【英文标题】:Recursively count occurrences with sqlite 【发布时间】:2021-10-10 07:16:21 【问题描述】:考虑以下示例表
x_id | name_id1 | name_id2 |
---|---|---|
x1 | John | Frank |
x2 | Frank | John |
x3 | Jack | John |
x4 | John | Jack |
x5 | Bob | Frank |
x6 | George | Bob |
x7 | Bob | Finn |
x8 | Mark | James |
x9 | James | Finn |
目标是提取以下结果
name | frequency |
---|---|
John | 4 |
Bob | 3 |
James | 2 |
从概念上讲,这是以下过程的结果:
-
计算所有名字出现的频率并选择出现频率最高的名字,即 John,出现 4 次(行
x1
到x4
)。
现在删除所有包含 John 的行,这会留下从 x5
到 x9
的行。再次确定出现频率最高的名称。这给了你 Bob,它出现了 3 次(行 x5
到 x7
)。
现在也删除行x5
到x7
,这样我们就剩下行x8
到x9
。再次确定出现频率最高的名称。这给了我们詹姆斯,它出现了 2 次。
现在也将行 x8
删除到 x9
,这让我们一无所有,所以我们完成了。
此数据存储在 SQLite 中的联结表中,如下所示(在实际情况下,每个 x_id
可以有两个以上的名称)
id | x_id | name_id |
---|---|---|
1 | x1 | John |
2 | x1 | Frank |
3 | x2 | John |
4 | x2 | Frank |
5 | x3 | John |
6 | x3 | Jack |
7 | x4 | John |
8 | x4 | Jack |
9 | x5 | Bob |
10 | x5 | Frank |
11 | x6 | Bob |
12 | x6 | George |
13 | x7 | Bob |
14 | x7 | Finn |
13 | x8 | James |
14 | x8 | Mark |
13 | x9 | James |
14 | x9 | Finn |
我们需要什么样的过程来检索描述的结果?考虑到上面的联结表是可变长度的(只是为了确保我们没有拿出固定数量的@987654337 @s 作为有效答案)。
我确实考虑过使用WITH RECURSIVE
方法,但是这不允许我们这样做
-
在递归选择中执行聚合函数
COUNT
,这在我们想要计算出现次数时似乎是必需的。
删除所有以前的x_id
s,只删除当前在队列中的那些。
【问题讨论】:
频率关系如何? 抱歉,我不确定您在寻找什么? 如果有 4 个 Johns 和 4 个 Bobs 怎么办?应该先选哪个? 好问题,在这种情况下我们可以按字母顺序选择,实际上两者之间没有偏好。 @forpas 值得一提的是:如果这会使查询更容易/可行,那么返回所有频率相同的项目也是可以接受的。 【参考方案1】:我从未使用过 SQLite,但递归查询在许多 DBMS 中都可用,不幸的是,我认为它们的功能不足以满足您的需求。问题是,如果越来越多的行包含结果表中已经存在的名称,则需要逐步忽略它们。我不是 100% 确定,但我相信使用递归 CTE 是不可能做到这一点的。
考虑到 SQLite 不提供允许循环的扩展语言(在这里,您可以找到一些复制循环的方法,例如使用递归 CTE 但我不确定在您的情况下是否可行),可能的合理选择之一是在应用程序端部分处理此问题。
首先,我会创建一个像这样的临时表:
CREATE TEMP TABLE visitedName (
x_id VARCHAR(10),
is_visited BOOLEAN
);
x_id
与表中的x_id
列相同(我称之为mytable
),is_visited
是一个布尔值,指定是否必须忽略x_id
。
然后用x_id
的所有不同值填充visitedName
:
INSERT into visitedName
SELECT DISTINCT x_id, FALSE
FROM mytable
现在您需要找到最流行的名称,忽略 is_visited
为真的行:
SELECT mt.name_id, COUNT(mt.name_id) name_count
FROM mytable mt JOIN visitedName vn ON mt.x_id = vn.x_id
WHERE NOT vn.is_visited
GROUP BY mt.name_id
ORDER BY name_count DESC
LIMIT 1
在应用程序端,您应该检索返回的唯一行并提取name_id
,将其作为参数传递给下一个查询。如果它没有返回任何行,那么你就完成了,你可以DROP TABLE visitedName
。
最后,您更新visitedName
表,将所有x_id
s 标记为已访问,其中包含至少一次出现的name_id
。
UPDATE visitedName
SET is_visited = TRUE
WHERE x_id IN (
SELECT x_id
FROM mytable
WHERE name_id = ? -- name_id got before
)
从第 3 点重新开始。
如果您的连接延迟太高或您期望结果表中的行数太多,则客户端应用程序和 DBMS 之间的持续通信是相当大的开销。在这种情况下,您需要更高级的东西。
对于那些对复制 OP 给出的表感兴趣的人,这是我使用的脚本:
CREATE TABLE mytable(
id integer,
x_id varchar(10),
name_id varchar(50),
PRIMARY KEY (id)
);
INSERT INTO mytable (id, x_id, name_id) VALUES
(1, 'x1', 'John'),
(2, 'x1', 'Frank'),
(3, 'x2', 'John'),
(4, 'x2', 'Frank'),
(5, 'x3', 'John'),
(6, 'x3', 'Jack'),
(7, 'x4', 'John'),
(8, 'x4', 'Jack'),
(9, 'x5', 'Bob'),
(10, 'x5', 'Frank'),
(11, 'x6', 'Bob'),
(12, 'x6', 'George'),
(13, 'x7', 'Bob'),
(14, 'x7', 'Finn'),
(15, 'x8', 'James'),
(16, 'x8', 'Mark'),
(17, 'x9', 'James'),
(18, 'x9', 'Finn');
【讨论】:
感谢您的回答。事实上,一旦这样的列表变大和/或它们的连接延迟,它可能会产生相当大的开销。在我们得到一个完全基于 SQLite 的答案之前,我不会回答这个问题。以上是关于使用 sqlite 递归计算出现次数的主要内容,如果未能解决你的问题,请参考以下文章