【问题标题】:How to get consecutive lot number based on a range?如何根据范围获得连续的批号?
【发布时间】:2019-10-20 20:47:27
【问题描述】:

我有一张这样的桌子:

LotNumber
1065
1026
95092
95090
95089
95088
85087
95086
95085
95084
95083
95082
95081

我的 SQL 查询应该如何根据范围(第一次匹配)返回连续的批号。

例如返回范围内的 3, (95090, 95089, 95088) 返回范围内的 5 (95086, 95085, 95084, 95083, 95082)

【问题讨论】:

    标签: sql database postgresql window-functions gaps-and-islands


    【解决方案1】:

    这是间隙和孤岛问题的复杂变体,因为:

    • 你想要一个组中的所有元素(不仅仅是开始和结束)
    • 您只需要第一组中满足条件的元素

    以下查询符合您的预期:

    select lotNumber
    from (
        select 
            lotNumber,
            cnt,
            dense_rank() over(order by grp) rn
        from (
            select 
                lotNumber,
                grp,
                count(*) over(partition by grp) cnt
            from (
                select 
                    lotNumber,
                    sum(case when lotNumber = lagLotNumber - 1 then 0 else 1 end) 
                        over(order by id) grp
                from (
                    select
                        id,
                        lotNumber, 
                        lag(lotNumber) over(order by id) lagLotNumber
                    from mytable
                ) t
            ) t
        ) t
        where cnt >= 3
    ) t
    where rn = 1
    

    子句where cnt >= 3 可用于控制连续数字的目标长度。

    Demo on DB Fiddle

    | lotnumber |
    | --------- |
    | 95090     |
    | 95089     |
    | 95088     |
    

    分步说明

    首先,这只能解决如果存在可用于对记录进行排序的列(在sql表中,记录默认情况下是无序的)。我假设存在这样的列并称为id。这是数据集:

    | lotnumber | id  |
    | --------- | --- |
    | 1065      | 1   |
    | 1026      | 2   |
    | 95092     | 3   |
    | 95090     | 4   |
    | 95089     | 5   |
    | 95088     | 6   |
    | 85087     | 7   |
    | 95086     | 8   |
    | 95085     | 9   |
    | 95084     | 10  |
    | 95083     | 11  |
    | 95082     | 12  |
    | 95081     | 13  |
    

    1) 第一步包括恢复每条记录的先前lotNumber。为此,我们使用lag()

    select
        id,
        lotNumber, 
        lag(lotNumber) over(order by id) lagLotNumber
    from mytable
    
    | id  | lotnumber | laglotnumber |
    | --- | --------- | ------------ |
    | 1   | 1065      |              |
    | 2   | 1026      | 1065         |
    | 3   | 95092     | 1026         |
    | 4   | 95090     | 95092        |
    ...
    

    2) 然后,我们使用累积和将记录分组,其中批号是连续的。当两条记录不连续时,开始一个新的组:

    select 
        lotNumber,
        sum(case when lotNumber = lagLotNumber - 1 then 0 else 1 end) 
            over(order by id) grp
    from (
        ... above query ...
    ) t
    
    | lotnumber | grp |
    | --------- | --- |
    | 1065      | 1   |
    | 1026      | 2   |
    | 95092     | 3   |
    | 95090     | 4   |
    | 95089     | 4   |
    | 95088     | 4   |
    | 85087     | 5   |
    | 95086     | 6   |
    | 95085     | 6   |
    | 95084     | 6   |
    | 95083     | 6   |
    | 95082     | 6   |
    | 95081     | 6   |
    

    3) 下一步是计算每个组中有多少条记录,并带有窗口计数

    select 
        lotNumber,
        grp,
        count(*) over(partition by grp) cnt
    from (
        ... above query ...
    ) t;
    
    | lotnumber | grp | cnt |
    | --------- | --- | --- |
    | 1065      | 1   | 1   |
    | 1026      | 2   | 1   |
    | 95092     | 3   | 1   |
    | 95090     | 4   | 3   |
    | 95089     | 4   | 3   |
    | 95088     | 4   | 3   |
    | 85087     | 5   | 1   |
    | 95086     | 6   | 6   |
    | 95085     | 6   | 6   |
    | 95084     | 6   | 6   |
    | 95083     | 6   | 6   |
    | 95082     | 6   | 6   |
    | 95081     | 6   | 6   |
    

    4) 有了这些信息,我们现在可以过滤至少具有目标连续记录数的组。同时,我们按批号升序对组进行排名。过滤条件where cnt >= 3可以根据需要改变,控制目标连续记录数。

    在这里,我们有两组至少有 3 个连续数字:

    select 
        lotNumber,
        cnt,
        dense_rank() over(order by grp) rn
    from (
        ... above query ...
    ) t
    where cnt >= 3;
    
    | lotnumber | cnt | rn  |
    | --------- | --- | --- |
    | 95090     | 3   | 1   |
    | 95089     | 3   | 1   |
    | 95088     | 3   | 1   |
    | 95086     | 6   | 2   |
    | 95085     | 6   | 2   |
    | 95084     | 6   | 2   |
    | 95083     | 6   | 2   |
    | 95082     | 6   | 2   |
    | 95081     | 6   | 2   |
    

    5) 最后一步是只过滤每个组中的顶部记录。

    select lotNumber
    from (
        ... above query ...
    ) t
    where rn = 1
    
    | lotnumber |
    | --------- |
    | 95090     |
    | 95089     |
    | 95088     |
    

    【讨论】:

    • 我可能过于复杂了,我愿意看看是否有人提出了更简单的解决方案!
    【解决方案2】:

    如果你有一个值n,你可以使用lead()。要获得系列中的第一个:

    select t.*
    from (select t.*, 
                 lead(lotnumber, <n> - 1) over (order by lotnumber) as n_after
          from t
         ) t
    where n_after - lotnumber = <n> - 1;
    

    Postgres 允许 lag()/lead() 偏移量为零,因此这甚至适用于 n = 1。

    Here 是一个 dbfiddle。

    【讨论】:

    • 完美的解决方案 gordon,即使我想知道使用 lead() 或 lag() 检查值是否连续会更简单
    • 使用 OP 的示例数据,这适用于 n=3,但似乎不适用于 n=6:参见 this fiddlde
    • @GMB 。 . .答案中有(至少我认为是)一个错字。无论“n”的值如何,它都会给出非常合理的结果。
    【解决方案3】:

    使用 row_number 排序

       Select 
              LotNumber, 
              row_number() over 
          (partition by 
    
     Substr(3,length(LotNumber),lotnumber) 
            order by 
            LotNumber ) rn
           from table  order by rn;
    

    【讨论】:

      猜你喜欢
      • 2021-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-09
      • 2022-10-05
      • 1970-01-01
      • 2011-08-05
      • 2017-01-30
      相关资源
      最近更新 更多