【问题标题】:How can I select adjacent rows to an arbitrary row (in sql or postgresql)?如何选择任意行的相邻行(在 sql 或 postgresql 中)?
【发布时间】:2015-12-04 18:23:39
【问题描述】:

我想根据特定条件选择一些行,然后从该集合中获取一个条目以及它之前和之后的 5 行。

现在,如果表上有一个主键,我可以用数字方式执行此操作(例如,主键在数字上比目标行的键小 5,比目标行的键大 5)。

所以选择主键为 7 的行和附近的行:

select primary_key from table where primary_key > (7-5) order by primary_key limit 11;

2
3
4
5
6
-=7=-
8
9
10
11
12

但如果我只选择某些行开头,我会失去使用主键的数字方法(假设键的顺序没有任何间隙),并且需要另一种方法来获得最接近的特定目标行之前和之后的行。

这种选择的主键输出可能看起来更随机,因此不太适合数学定位(因为某些结果会被过滤掉,例如使用where active=1):

select primary_key from table where primary_key > (34-5) 
    order by primary_key where active=1 limit 11;

30
-=34=-
80
83
100
113
125
126
127
128
129

请注意,由于条件示例(例如,因为有许多非活动项目)导致的主键中的间隙,我不再得到最接近的上面的 5 和下面的 5 , 而我得到的是下面最接近的 1 和上面最接近的 9。

【问题讨论】:

  • 我认为您编写的 SELECT 语句在这两个示例中都适用。
  • Err,select 语句会返回值,是的,但是它返回的行基本上是随机的,而不是上面的 5 和下面的 5,这是问题所在。

标签: sql postgresql selection


【解决方案1】:

如果您使用一种编程语言运行两个查询,有很多方法可以做到这一点,但这里有一种方法可以在一个 SQL 查询中做到这一点:

(SELECT * FROM table WHERE id >= 34 AND active = 1 ORDER BY id ASC LIMIT 6)
UNION
(SELECT * FROM table WHERE id < 34 AND active = 1 ORDER BY id DESC LIMIT 5)
ORDER BY id ASC

这将返回上面的 5 行、目标行和下面的 5 行。

【讨论】:

  • 简单有效,适用于很多情况,这是我用过的。
  • 提示:我们可以使用ceil(limit)floor(limit)
【解决方案2】:

这是使用分析函数超前和滞后的另一种方法。如果我们可以在 WHERE 子句中使用分析函数,那就太好了。因此,您需要使用子查询或 CTE。这是一个适用于 pagila 示例数据库的示例。

WITH base AS (
    SELECT lag(customer_id, 5) OVER (ORDER BY customer_id) lag, 
      lead(customer_id, 5) OVER (ORDER BY customer_id) lead, 
      c.*
    FROM customer c
    WHERE c.active = 1
    AND c.last_name LIKE 'B%'
) 
SELECT base.* FROM base 
JOIN (
  -- Select the center row, coalesce so it still works if there aren't 
  -- 5 rows in front or behind
  SELECT COALESCE(lag, 0) AS lag, COALESCE(lead, 99999) AS lead 
  FROM base WHERE customer_id = 280
) sub ON base.customer_id BETWEEN sub.lag AND sub.lead

sgriffinusa 解决方案的问题是您不知道您的中心行最终会是哪个 row_number。他认为这将是第 30 行。

【讨论】:

    【解决方案3】:

    对于类似的查询,我使用没有 CTE 的分析函数。比如:

    select ..., LEAD(gm.id) OVER (ORDER BY Cit DESC) as leadId, LEAD(gm.id, 2) OVER (ORDER BY Cit DESC) as leadId2, LAG(gm.id) OVER (ORDER BY Cit DESC) as lagId, LAG(gm.id, 2) OVER (ORDER BY Cit DESC) as lagId2 ... where id = 25912 or leadId = 25912 or leadId2 = 25912 or lagId = 25912 or lagId2 = 25912

    这样的查询对我来说比使用 join 的 CTE 更快(Scott Bailey 的回答)。但当然不那么优雅

    【讨论】:

    • ... 除了您不能在WHERE 子句中使用解析值,因此您编写的内容将不起作用(至少在 MS SQL 中不起作用)。
    【解决方案4】:

    您可以使用 row_number()(从 8.4 开始提供)来执行此操作。这可能不是正确的语法(不熟悉 postgresql),但希望能说明这个想法:

    SELECT *
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY primary_key) AS r, *
          FROM table
          WHERE active=1) t
    WHERE 25 < r and r < 35
    

    这将生成具有序列号的第一列。您可以使用它来识别单行及其上方和下方的行。

    【讨论】:

      【解决方案5】:

      如果您想以“相对纯”的方式执行此操作,您可以编写一个对行进行排序和编号的查询。喜欢:

      select (
        select count(*) from employees b
        where b.name < a.name
      ) as idx, name
      from employees a
      order by name
      

      然后将其用作公用表表达式。编写一个选择,将其过滤到您感兴趣的行,然后使用表右侧副本的索引不大于或小于表的索引的标准将其连接回自身排在左边。仅在右侧的行上投影。喜欢:

      with numbered_emps as (
        select (
          select count(*)
          from employees b
          where b.name < a.name
        ) as idx, name
        from employees a
        order by name
      )
      select b.*
      from numbered_emps a, numbered_emps b
      where a.name like '% Smith' -- this is your main selection criterion
      and ((b.idx - a.idx) between -5 and 5) -- this is your adjacency fuzzy-join criterion
      

      还有什么比这更简单的!

      不过,我想基于行号的解决方案会更快。

      【讨论】:

        猜你喜欢
        • 2014-08-04
        • 1970-01-01
        • 1970-01-01
        • 2019-03-10
        • 1970-01-01
        • 1970-01-01
        • 2015-05-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多