【问题标题】:SQL SELECT 3 consecutive records with the same valueSQL SELECT 3个具有相同值的连续记录
【发布时间】:2021-10-30 02:00:36
【问题描述】:

我想从“参与”表(姓名,年份)中选择 3 个连续(按年份)具有相同值的记录:

Name    Year
-------------
Carol   1999
Carol   2000
Carol   2001
Carol   2002
Faith   1996
John    2001
John    2002
John    2003
John    2009
Lyla    1994
Lyla    1996
Lyla    1997

这是我的初始代码:

SELECT DISTINCT p1.name, p1.year
FROM participatition p1,
     participatition p2
WHERE (p1.year = p2.year + 1 OR p1.year = p2.year - 1) AND p1.name = p2.name
ORDER BY p1.name, p1.year

返回所有连续记录,但我只想要满足 3 个连续条件的记录,即不是 Lyla:

Name    Year
-------------
Carol   1999
Carol   2000
Carol   2001
Carol   2002
John    2001
John    2002
John    2003
Lyla    1996
Lyla    1997

是否可以在我的代码的基础上构建,例如添加一个额外的标准,以优化选择而不实现 row_number() 方法?

我想要以下输出:

姓名

卡罗尔 约翰

即所有记录,如果至少有 3 个连续的

【问题讨论】:

  • 输出应该是什么样子?如果至少有 3 个连续记录,它应该只列出 3 条记录还是所有记录。它不清楚应该给输入什么输出
  • 今日提示:切换到现代、明确的JOIN 语法。更容易编写(没有错误),更容易阅读(和维护),并且在需要时更容易转换为外连接。
  • 当你说“不是 Lyla”时,为什么 Lyla 在预期结果中?
  • 使用 modern 连接语法 jarlh 是指我们在过去三十年中一直使用的语法,而不是 1980 年代使用的逗号分隔连接 :-)
  • 为什么是DISTINCT?参与表可以有多行同名同年吗?还是仅仅因为join会产生重复?

标签: mysql sql window-functions


【解决方案1】:

将其视为间隙和孤岛问题并使用以下技巧将连续的行组合在一起:

WITH cte1 AS (
    SELECT *, Year - ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Year) AS grp
    FROM t
), cte2 AS (
    SELECT *, COUNT(*) OVER (PARTITION BY Name, grp) AS grp_count
    FROM cte1
)
SELECT *
FROM cte2
WHERE grp_count >= 3
ORDER BY Name, Year

如果您查看grp 列中的值,您会发现模式。

db<>fiddle

【讨论】:

  • 您的组不正确,因为不同的名称可以成为同一个组。请看dbfiddle.uk/…
  • @ThorstenKettner 已修复
  • 太棒了。这使它成为一个很好的解决方案,因为与我的查询不同,您的查询不限于几个连续的行,而是很容易扩展。
【解决方案2】:

可能有更优雅的方式。但是,好吧,这就是我想出的:

select name, year
from
(
  select 
    name, year,
    case when lag(year, 2) over (partition by name order by year) = year - 2 then 1 else 0 end +
    case when lag(year, 1) over (partition by name order by year) = year - 1 then 1 else 0 end +
    case when lead(year, 1) over (partition by name order by year) = year + 1 then 1 else 0 end +
    case when lead(year, 2) over (partition by name order by year) = year + 2 then 1 else 0 end +
    1 as consecutive_rows
  from participatition
) analyzed
where consecutive_rows >= 3
order by name, year;

如果表参与可以包含一个名称和年份的多行,请将DISTINCT 添加到子查询(也称为派生表)。

【讨论】:

    【解决方案3】:

    如果每个名称没有重复的年份,则需要LEAD() 窗口函数来检查下第二行。
    如果该行中的年份等于当前年份 + 2,则这意味着该名称有 3 个连续年份:

    WITH cte AS (
      SELECT *, LEAD(Year, 2) OVER (PARTITION BY Name ORDER BY Year) next_next
      FROM participatition
    )
    SELECT DISTINCT p.* 
    FROM participatition p INNER JOIN cte c
    ON p.Name = c.Name AND p.Year BETWEEN c.Year AND c.next_next
    WHERE c.next_next = c.Year + 2;
    

    请参阅demo

    【讨论】:

    • 不过,这也会输出 John|2009,它不属于连续块之一。
    • @ThorstenKettner 我认为输出将是每个名称连续 3 年的所有行。我会编辑。
    【解决方案4】:

    我会简单地使用lead():

    select distinct name
    from (select p.*,
                 lead(year, 2) over (partition by name order by year) as year_2
          from participation p
         ) p
    where year_2 = year + 2;
    

    对于每一行,这会查看前面两行的相同名称,按年份排序。如果该行是当前年份加 2,则您连续三年。

    【讨论】:

      【解决方案5】:

      如果您使用的是 Oracle 数据库,您可以使用 row pattern matching 解决此问题:

      with rws as (
        select 'Carol' nm, 1999 yr from dual union all
        select 'Carol' nm, 2000 yr from dual union all
        select 'Carol' nm, 2001 yr from dual union all
        select 'Carol' nm, 2002 yr from dual union all
        select 'Faith' nm, 1996 yr from dual union all
        select 'John' nm, 2001 yr from dual union all
        select 'John' nm, 2002 yr from dual union all
        select 'John' nm, 2003 yr from dual union all
        select 'John' nm, 2009 yr from dual union all
        select 'Lyla' nm, 1994 yr from dual union all
        select 'Lyla' nm, 1996 yr from dual union all
        select 'Lyla' nm, 1997 yr from dual 
      )
        select * from rws match_recognize (
          partition by nm
          order by yr
          all rows per match
          pattern ( init cons{2} )
          define
            cons as yr = prev ( yr ) + 1
        );
        
      NM            YR     
      Carol       1999 
      Carol       2000 
      Carol       2001 
      John        2001 
      John        2002 
      John        2003 
      

      【讨论】:

      • 这看起来确实是特定于 DBMS。 OP 尚未告诉我们他们使用的是哪个 DBMS。
      【解决方案6】:

      在我的初始代码中添加如下 Group By 和 Have 子句建立在现有代码的基础上(过滤了所有连续的名称、年份):

      SELECT DISTINCT p1.name
        FROM participatition p1, participatition p2 
        WHERE (p1.year = p2.year+1 OR p1.year = p2.year-1) AND p1.name = p2.name
        GROUP BY p1.name
        HAVING COUNT(p1.name) > 2
        ORDER BY p1.name, p1.year
      

      感谢所有答案 - 我从未意识到有这么多替代解决方案让我大开眼界。

      【讨论】:

      • 你为什么要坚持这种过时的连接语法?这是一个内连接,所以使用INNER JOIN。那么,为什么是DISTINCT?您按名称分组并选择名称。那么怎么可能有必须删除的重复项呢?不能。最后,您的 ORDER BY 子句使查询无效。您按年份排序,但按名称分组后,数据集中没有您可以排序的年份。您可以按MAX(year)MIN(year) 或其他聚合排序,但不能按年份排序,因为每个名称可能有多个年份。如果您的 DBMS 允许这样做,那么 DBMS 就有缺陷。
      • 我所知道的唯一允许无效ORDER BY 子句的DBMS 是将MySQL 设置为作弊模式,即没有正确的SET sql_mode = 'ONLY_FULL_GROUP_BY'。在那种 botch 模式下使用 MySQL 时,它会任意选择一个值,即将p1.year 默默地转换为ANY_VALUE(p1.year)。您使用的是哪个 DBMS?至于ORDER BY:无论如何都要删除年份,因为它根本没有意义;您无法对按名称进一步排序的不同名称列表进行排序。
      • 你猜对了 Thorsten 的猜测 MySQL。我将在 PostgresSQL 中再次尝试,并尝试更好地了解您的其他 cmets。感谢您的反馈。
      【解决方案7】:

      这次我使用 PSQL 根据来自 @ThorstenKettner 的反馈(不同的、内部连接等)更新了我的代码

      SELECT p1.name
        FROM participation p1
        JOIN participation p2 ON p1.name = p2.name
        WHERE (p1.year = p2.year+1 OR p1.year = p2.year-1)
        GROUP BY p1.name
        HAVING COUNT(p1.name) > 2
        ORDER BY p1.name
      

      这似乎工作正常,易于理解且不那么复杂。但是我想测试所有解决方案,所以我可以应用这些解决方案来适应新的要求。所以感谢大家的慷慨帮助。特别是。丹克TK!

      【讨论】:

      • 改变主意:实际上我上面修改过的代码不是 100% 正确的,而且是偶然的——可能不适用于其他数据。看过@GordonLinoff lead() 解决方案并研究了领先/滞后是如何工作的,我认为这是最优雅的解决方案。确实感谢戈登。
      【解决方案8】:
      WITH CTE AS (
          SELECT name
               , year-lag(year,2) OVER(PARTITION BY name ORDER BY year ASC) as two_years_ago
          FROM t
      )
      SELECT name, two_years_ago 
      FROM cte
      WHERE two_years_ago=2
      

      【讨论】:

      • 欢迎来到 SO。在大多数情况下,纯代码答案缺乏解释。考虑解释这如何回答问题,以便为未来的其他用户增加价值。
      猜你喜欢
      • 2019-01-28
      • 2012-12-20
      • 1970-01-01
      • 2021-11-12
      • 2020-07-19
      • 1970-01-01
      • 2014-11-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多