【问题标题】:How to perform this complex calculation using SQL?如何使用 SQL 执行这种复杂的计算?
【发布时间】:2021-07-20 02:31:50
【问题描述】:

抱歉标题含糊不清,但我想不出一个更清晰的标题来描述我的情况。

我有一个 MySQL 表来存储文章之间的引用关系。表结构如下:

表名:PaperReferences

PaperID ReferencedPaperID
1 2
1 3
1 4
5 1
5 4
6 1
7 1

我想为每篇论文计算一个指标,定义为引用它而不是引用它的论文数量。例如,paper 1 的这个指标是 2,因为它有 3 次引用(5、6、7)但只有 2 次没有引用它的引用(6、7)。也就是说,paper 5 不计入,因为它也引用了paper 4,这是paper 1 的引用之一。

我目前正在使用 Python 和迭代来做这件事。但是,它太慢了,我想知道我是否可以使用单个 SQL 查询获得结果。

对我来说最棘手的部分是过滤掉同样引用 paper 1 的参考文献的无效施引论文。

【问题讨论】:

  • 你能显示你的实际表名和列名吗?您是否尝试为单个论文 ID 或所有有引用的论文或所有论文(即使它们没有引用)获取此指标?
  • @ysth 我已将列名修改为实际名称,并且我正在尝试为所有论文获取此指标(当它们没有引用时为零)。
  • 如果你想要没有引用的论文,也需要一个表格来存放论文吗?
  • DB Fiddle 非常适合您的问题。准备数据并描述期望的结果

标签: python mysql sql database


【解决方案1】:

首先弄清楚什么是不允许的,然后将其加入您的引文列表。

不允许的循环:

Select DISTINCT X.PaperID as Citer, Y.PaperID as disallowed 
From PaperReferences X Inner Join PaperReferences Y
    On X.ReferencedPaperID=Y.ReferencedPaperID
Where X.PaperID<>Y.PaperID

现在将表左连接到这个不允许的集合,过滤掉匹配项,并计算你的总数:

Select ReferencedPaperID as paper, Count(*) as citations
From PaperReferences A Left Outer Join (
    Select DISTINCT X.PaperID as Citer, Y.PaperID as disallowed 
    From PaperReferences X Inner Join PaperReferences Y
        On X.ReferencedPaperID=Y.ReferencedPaperID
    Where X.PaperID<>Y.PaperID
    ) Z on A.PaperID=Z.Citer And A.ReferencedPaperID=Z.Disallowed
Where Z.disallowed Is Null
Group By ReferencedPaperID

【讨论】:

  • 注:DISTINCT 是必要的,因为可能有 多个路径来排除 5,尽管此示例没有显示它。将 (5, 3) 条目添加到此表中,您将创建 2 种排除 5 的方法,现有的 1,4,5,1 循环和 1,3,5,1 循环。
  • 我逐渐了解disallowed表。在您的第一个代码块中,您会发现所有论文至少共享给定论文的一个参考,但这些论文不需要引用给定论文。然后从给定论文的引用中排除那些bibliographic coupling 论文(描述此类论文的术语)。但是,我还有一个问题。是否存在性能问题,因为不允许的论文可能没有引用给定的论文,但它们仍然被认为是不允许的(这是不必要的)?
  • 例如,如果我在表中添加一行 (8, 4),则 1 和 5 不允许使用 8,因为它引用了 4。但是,8 没有引用 1 或 5,所以它会不需要考虑?
  • 要弄清楚这些是否引用了其他任何内容,您需要加入 more 次,这将是性能损失。一般来说,加入的表越少,性能就越好。
  • 如果您想确保最佳性能,请确保您在 ReferencedPaperID 上有一个索引。
【解决方案2】:

我很想知道为什么你的 Python 这么慢。这不是很快就能解决问题吗?

data = (
(1, 2),
(1, 3),
(1, 4),
(5, 1),
(5, 4),
(6, 1),
(7, 1)
)

import collections
cite = collections.defaultdict(set)
cited = collections.defaultdict(set)

for row in data:
    cite[row[0]].add(row[1])
    cited[row[1]].add(row[0])

# We remove 5 from cited[1] because cite[1] and cite[5] overlap.

for k,v in cited.items():
    toremove = set()
    for c in v:
        if cite[k] & cite[c]:
            print(f"removing {c} from {k}")
            toremove.add( c )
    v -= toremove

print(cite)
print(cited)

for k,v in cited.items():
    print( f"{k} has {len(v)} sole citations" )

【讨论】:

  • 因为表太大,内存无法存储,所以每次都要查询每篇论文的参考文献和引文。我认为这会严重损害性能。
  • @TomLeung 实际上我更惊讶为什么不用生成器表达式而不是fors。使用生成器表达式,它不会使用更多 RAM,性能很可能会相同或更好
  • @AlexYu 感谢您的洞察力!由于所有数据都存储在数据库中,我的 Python 程序会逐一计算论文的指标,并执行多个 SQL 查询以查找参考文献和引文。我认为使用一次产生所有结果的 SQL 查询可能会快得多,所以我问了这个问题。
  • 很可能在 SQL 中执行计算是正确的方法,但不是因为性能原因。我想在python中必须有可能更快。可以处理大量数据,例如pytables。在多个消费者数据库之间共享结果会更好
  • 有多少条记录?我的代码只对行进行一次顺序传递,因此游标一次可以读取一些内容。如果你不能将这两个字典存储在内存中,那么我不知道你将如何解决这个问题。
【解决方案3】:

你可以这样做:

select ReferencedPaperID, count(*) as indicator
from PaperReferences p
left join (
  select b.PaperID
  from PaperReferences a
  join PaperReferences b on b.PaperId = a.PaperId
  join PaperReferences c on c.ReferencedPaperID = b.ReferencedPaperID
  where a.ReferencedPaperID = c.PaperId
) f on f.PaperId = p.PaperId
where f.PaperId is null
group by ReferencedPaperID

结果:

 ReferencedPaperID  indicator 
 ------------------ --------- 
 2                  1         
 3                  1         
 4                  1         
 1                  2         

请参阅DB Fiddle 的运行示例。

【讨论】:

  • 感谢您的回答!但是,我认为@Chris Maurer 的答案可能更合适,因为论文 4 的指标应该是 2,而不是 1。
  • 在这个例子中,4 应该被计算两次。它被 1 和 5 引用,它本身没有引用任何内容。该内部查询仅返回 5,因此在所有上下文中都排除了 5。在 (1, 5) 上下文中它是正确的,但是在 (4, 5) 上下文中它需要被计算。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多