【问题标题】:How to compare the current row with next and previous row in PostgreSQL?如何将当前行与 PostgreSQL 中的下一行和上一行进行比较?
【发布时间】:2024-01-24 03:19:01
【问题描述】:

我想知道如何在 SQL 查询中检索结果,并与下一行或上一行进行一些逻辑比较。我正在使用 PostgreSQL。

示例
假设我的数据库中有一个包含两个属性(有序位置和随机数)的表,我想检索偶数之间的奇数。我该怎么做?

实际用法
我想找到位于另外两个具有名称类别的单词之间的单词(并且该单词不是名称)。排序由句子和位置提供。

编辑 我想知道 PostgreSQL 的 Window 函数是否是解决此类问题的最佳解决方案,而不是查询。我听说过它们,但从未使用过。

【问题讨论】:

  • 这三个词是否应该在同一个句子中并且它们的位置是按顺序排列的?
  • @ErwinBrandstetter 是的,所有单词必须在同一个句子中并按顺序排列。

标签: sql postgresql window


【解决方案1】:

你可以在这个地址找到最好的解决方案:

http://blog.sqlauthority.com/2013/09/25/sql-server-how-to-access-the-previous-row-and-next-row-value-in-select-statement-part-4/

SQL Server 2012 及更高版本的查询 1:

SELECT
LAG(p.FirstName) OVER(ORDER BY p.BusinessEntityID) PreviousValue,
    p.FirstName,
    LEAD(p.FirstName) OVER(ORDER BY p.BusinessEntityID) NextValue
FROM Person.Person p
GO

SQL Server 2005+ 及更高版本的查询 2:

WITH CTE AS(
    SELECT rownum = ROW_NUMBER() OVER(ORDER BY p.BusinessEntityID),
    p.FirstName FROM Person.Person p
)
SELECT
prev.FirstName PreviousValue,
    CTE.FirstName,
    nex.FirstName NextValue
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1
GO

【讨论】:

  • 谢谢...我喜欢 LEG 和 LEAD 功能。
  • 查询 2 符合我的需要。谢谢!
【解决方案2】:

这应该可行:

SELECT w1.word AS word_before, w.word, w2.word AS word_after
FROM   word w
JOIN   word w1 USING (sentence)
JOIN   word w2 USING (sentence)
WHERE  w.category <> 'name'
AND    w1.pos = (w.pos - 1)
AND    w1.category = 'name'
AND    w2.pos = (w.pos + 1)
AND    w2.category = 'name'
  • 使用两个自联接
  • 所有单词必须在同一个句子 (?) 中并按顺序排列。
  • 之前和之后的单词必须属于“名称”类别。单词本身不是“名称”
  • 这假定类别IS NOT NULL

回答您的附加问题:不,在这种情况下,窗口函数不会特别有用,self-join 是这里的魔法词。

编辑:
我站得更正了。 Renato 使用窗口函数 lag() and lead() 演示了一个很酷的解决方案。
注意细微的差别:

  • 自联接对绝对值进行操作:如果缺少pos -1 的行,则pos 的行不符合条件。
  • 带有lag()lead() 的Renatos 版本对ORDER BY 创建的行的相对位置 进行操作。

在许多情况下(比如可能在手头的那个?)两个版本导致相同的结果。 id空间有差距会有不同的结果。

【讨论】:

  • 请看一下我的编辑。我还没有尝试过这个SQL,但我认为它可以解决。问题是在这之后我会有更复杂的事情要做。你知道 Window 函数是否更适合这个?我从来没有用过它们。
  • 看看我的回答。我使用了窗口功能。我不知道是好是坏,但我喜欢,而且现在看起来很容易使用。
【解决方案3】:

这是我使用WINDOW functions 的解决方案。我使用了laglead 函数。两者都返回从当前行偏移的行中的列中的值。 lag 回到偏移量,lead 进入下一个偏移量。

SELECT tokcat.text
FROM (
    SELECT text, category, chartype, lag(category,1) OVER w as previousCategory, lead(category,1) OVER w as nextCategory
    FROM token t, textBlockHasToken tb
    WHERE tb.tokenId = t.id
    WINDOW w AS (
        PARTITION BY textBlockId, sentence
        ORDER BY textBlockId, sentence, position
    )
) tokcat
WHERE 'NAME' = ANY(previousCategory)
AND 'NAME' = ANY(nextCategory)
AND 'NAME' <> ANY(category)

简化版:

SELECT text
FROM (
    SELECT text
          ,category 
          ,lag(category) OVER w as previous_cat
          ,lead(category) OVER w as next_cat
    FROM   token t
    JOIN   textblockhastoken tb ON tb.tokenid = t.id
    WINDOW w AS (PARTITION BY textblockid, sentence ORDER BY position)
    ) tokcat
WHERE  category <> 'NAME'
AND    previous_cat = 'NAME'
AND    next_cat = 'NAME';

要点

  • = ANY() 不需要,窗口函数返回单个值
  • 子查询中的一些冗余字段
  • 无需按列排序,您 PARTITION BY - ORDER BY 适用于 分区
  • 不要使用不带引号的混合大小写标识符,这只会导致混淆。 (更好的是:不要在 PostgreSQL 中使用混合大小写标识符永远

【讨论】:

  • +1 很酷的解决方案!我编辑了一个简化版本。不喜欢就删掉。您可以使用执行速度更快的三个查询运行测试并将其添加到您的答案中吗?会很有趣...
  • @ErwinBrandstetter 只有一件事:ANY 是必需的,因为类别是一个数组,而不是单个值。
  • @ErwinBrandstetter 我也会尝试遵循其他提示。我还没有太多实际使用 SQL 的练习。其他字段是我正在做的真正查询有点复杂,有更多过滤器。我只发布了获取名称之间的单词的主要目标。
最近更新 更多