使用 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 次(行x1x4)。 现在删除所有包含 John 的行,这会留下从 x5x9 的行。再次确定出现频率最高的名称。这给了你 Bob,它出现了 3 次(行 x5x7)。 现在删除行x5x7,这样我们就剩下行x8x9。再次确定出现频率最高的名称。这给了我们詹姆斯,它出现了 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_ids,只删除当前在队列中的那些。

【问题讨论】:

频率关系如何? 抱歉,我不确定您在寻找什么? 如果有 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_ids 标记为已访问,其中包含至少一次出现的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 递归计算出现次数的主要内容,如果未能解决你的问题,请参考以下文章

递归计算字符串中的字符出现次数

递归计算对象在字符串中出现的次数

如何计算fibonacci函数的递归调用次数

Prolog中的指令顺序和递归

F#遍历相互递归的树来计算元素

LeetCode答题记录233. 数字1的个数