根据跨日期范围对订单进行分组

Posted

技术标签:

【中文标题】根据跨日期范围对订单进行分组【英文标题】:group orders based on crossing date ranges 【发布时间】:2020-10-10 09:43:28 【问题描述】:

我需要将订单组合在一起,只需要跨越他们的日期范围

场景 A.

    订单 1,1.3.2020-30.6.2020 订单 2,1.5.2020-31.8.2020 订单 3,31.7.2020-31.10.2020 订单 4,31.7.2020-31.12.2020

所以输出应该是

    订单 1,订单 2 订单 2,订单 3,订单 4

order1,3,4 没有分组,因为它们的范围根本不交叉

场景 B.

同上,另加订单

    订单 5,1.1.2020-31.12.2020

所以输出将是

    订单 1,订单 2,订单 5 订单 2,订单 3,订单 4,订单 5

我尝试使用 Self Join 来检查哪个开始日期在该范围内。 所以在订单 1 的范围内,只有订单 2 的开始日期 -> 我们有一组 然后在订单 2 的范围内,订单 3 和 4 的开始日期都下降 -> 我们有第二组 但随后订单 3 落在订单 4 的开始日期和相反 -> 这将给出另外 2 个组,但它们是无效的,因为订单 2 也跨越了它们的日期范围,也应该包括在内,因为我们应该有 3 次重复在所需的输出中只显示一次,但这种方法会失败。

谢谢

【问题讨论】:

可能对我来说太难理解了,但你能解释一下在第一种情况下分组是如何完成的order 1, order 2order 2, order 3, order 4 吗? 组仅针对日期范围交叉的订单进行,因此第 1 组是订单 1 和 2,第 2 组是订单 2,3 和 4。组中的所有订单必须包含交叉日期范围。订单 1、2、3、4 的组将无效,因为日期范围或订单 1 不与日期范围或订单 3 和 4 交叉。同样,订单 3 和 4 的组将无效,因为订单 2 必须包含在好 为什么订单 3 和 4 是无效组,如果必须包括订单 2,以及当订单 1 可以与订单 2 位于同一组时,为什么订单 2、3 和 4 是有效组?我无法调和这两个语句之间的逻辑。 由于日期范围的原因,您不能将订单 1 与订单 3 和 4 放在同一组中,订单 1 结束于 30.6,但订单 3 和 4 开始于 31.7,它们不会相互交叉.如果没有另一个订单(在本例中为订单 2),其日期范围跨越这两个订单,则订单 3 和 4 组将有效。我只想要日期范围相互重叠的订单组。因此,您不能拥有第 1、2、3、4 组,因为并非所有这些都以某种方式相互交叉,并且类似地适用于第 3 和第 4 组,因为将缺少交叉它们的第 2 组。 请在代码问题中给出minimal reproducible example--cut & paste & runnable code,包括最小的代表性示例输入作为代码;期望和实际输出(包括逐字错误消息);标签和版本;明确的规范和解释。给出尽可能少的代码,即您显示的代码可以通过您显示的代码扩展为不正常的代码。 (调试基础。)对于包含 DBMS 和 DDL(包括约束和索引)的 SQL,并以表格格式作为代码输入。 How to Ask 暂停总体目标的工作,将代码砍到第一个表达式,没有给出你期望的内容,说出你期望的内容和原因。 【参考方案1】:

MATCH_RECOGNIZE 解决方案的结果是不正确的,因为 5 阶应该在两个组中

我使用一些分析函数来解决这个问题:

-- 创建表

Create table cross_dates (order_id number, start_date date , end_date date);

-- 插入日期

insert into cross_dates values( 1, to_date('01.03.2020', 'dd.mm.yyyy'), to_date('30.06.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 2, to_date('01.05.2020', 'dd.mm.yyyy'), to_date( '31.08.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 3, to_date('31.07.2020', 'dd.mm.yyyy'), to_date( '31.08.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 4, to_date('31.07.2020', 'dd.mm.yyyy'), to_date( '31.10.2020', 'dd.mm.yyyy'));
insert into cross_dates values( 5, to_date('01.01.2020', 'dd.mm.yyyy'), to_date( '31.12.2020', 'dd.mm.yyyy'));

--SQL

select 'Order '|| min_order_id ||': ',  listagg( order_id, ',') within group (order by order_id)  list
from (
    select distinct min_order_id, order_id from (
        with  dates (cur_date, end_date, order_id, start_date) as (
              select start_date, end_date, order_id, start_date
              from cross_Dates
              union all
              select cur_date + 1, end_date, order_id,start_date
              from dates
              where cur_date < end_date )
    select d.order_id, 
           min(d.order_id) over(partition by greatest(d.start_date, cd.start_date)) min_order_id
    from dates d, cross_Dates cd
    where d.cur_date between cd.start_date and cd.end_date ))
group by min_order_id 
having count(*) > 1;

结果:

Order 1:    1,2,5
Order 2:    2,3,4,5

-- 添加新列并更新旧记录

alter table cross_dates add (item varchar2(1)); 

update cross_dates set item = 'A'; 

-- 插入新记录 B

insert into cross_dates values( 1, to_date('01.01.2020', 'dd.mm.yyyy'), to_date( '30.06.2020', 'dd.mm.yyyy'), 'B');
insert into cross_dates values( 1, to_date('01.07.2020', 'dd.mm.yyyy'), to_date( '31.12.2020', 'dd.mm.yyyy'), 'B');

我的假设:

    A 和 B 是不同的命令,即使交叉也不在同一组中 订单 1 B - 有两条记录作为延续 - 在我的理解中算作一个订单:订单 1 B 01.01.2020 - 21.12.2020

如果我的假设是正确的,SQL 可能如下所示:

 select distinct min_order_id, order_id, item from (
           with  dates (cur_date, end_date, order_id, start_date, item) as (
              select start_date, end_date, order_id, start_date, item
              from cross_Dates             
              union all
              select cur_date + 1, end_date, order_id,start_date, item
              from dates
              where cur_date < end_date )
    select d.order_id,  d.item,
           min(d.order_id) over(partition by greatest(d.start_date, cd.start_date),d.item) min_order_id
    from dates d, cross_Dates cd
    where d.cur_date between cd.start_date and cd.end_date and d.item = cd.item )
    order by item, min_order_id; 

结果:

MIN_ORDER_ID ORDER_ID I


       1          1 A
       1          2 A
       1          5 A
       2          2 A
       2          3 A
       2          4 A
       2          5 A
       5          5 A
       1          1 B

如果我的假设不正确,请告诉我在这种情况下应该是什么样的结果。

:)

【讨论】:

莫妮卡干得好,非常有趣,但让我们假设这个修改。我们可以摆脱 LISTAGG 并简单地逐行列出同一组下的所有订单,例如 Group 1 , 1 然后 Group 1 , 2 然后 Group 1 , 5 ......Group 2 , 2 然后 Group 2 , 3.. ...假设还有一列 Item 的值为 A 和 B。A 具有上述订单,B 具有不同的订单,订单 1 为 1.1.2020-30.6.2020,再次订购 1 1.7.2020-31.12.2020(它是一个扩展,因此 ID 相同)。【参考方案2】:

您可以使用MATCH_RECOGNIZE 查找下一个值的开始日期早于或等于组中所有先前值的结束日期的组。然后您可以聚合和排除完全包含在另一个组中的组:

WITH groups ( id, ids, start_date, end_date ) AS (
  SELECT id,
         LISTAGG( grp_id, ',' ) WITHIN GROUP ( ORDER BY start_date ),
         MIN( start_date ),
         MIN( end_date )
  FROM   (
    SELECT t.id,
           x.id AS grp_id,
           x.start_date,
           x.end_date
    FROM   table_name t
           INNER JOIN table_name x
           ON (
                   x.start_date >= t.start_date
               AND x.start_date <= t.end_date
              )
  )
  MATCH_RECOGNIZE (
    PARTITION BY id
    ORDER BY start_date
    MEASURES
      MATCH_NUMBER() AS mno
    ALL ROWS PER MATCH
    PATTERN ( FIRST_ROW GROUPED_ROWS* )
    DEFINE GROUPED_ROWS AS (
      GROUPED_ROWS.start_date <= MIN( end_date )
    )
  )
  WHERE mno = 1
  GROUP BY id
)
SELECT id,
       ids
FROM   groups g
WHERE  NOT EXISTS (
  SELECT 1
  FROM   groups x
  WHERE  g.ID <> x.ID
  AND    x.start_date <= g.start_date
  AND    g.end_date   <= x.end_date
)

样本数据:

CREATE TABLE table_name ( id, start_date, end_date ) AS
SELECT 'order 1', DATE '2020-03-01', DATE '2020-06-30' FROM DUAL UNION ALL
SELECT 'order 2', DATE '2020-05-01', DATE '2020-08-31' FROM DUAL UNION ALL
SELECT 'order 3', DATE '2020-07-31', DATE '2020-10-31' FROM DUAL UNION ALL
SELECT 'order 4', DATE '2020-07-31', DATE '2020-12-31' FROM DUAL;

输出:

身份证 |身份识别系统 :-------- | :------------------------ 订单 2 |订单 2,订单 3,订单 4 订单 1 |订单 1,订单 2

我是你:

INSERT INTO table_name ( id, start_date, end_date )
VALUES ( 'order 5', DATE '2020-01-01', DATE '2020-12-31' );

输出将是:

身份证 |身份识别系统 :-------- | :------------------------ 订单 2 |订单 2,订单 3,订单 4 订购 5 |订单 5,订单 1,订单 2

db小提琴here

【讨论】:

以上是关于根据跨日期范围对订单进行分组的主要内容,如果未能解决你的问题,请参考以下文章

多表同步 ES 的问题

对订单进行分组并获取每个订单的产品列表

Laravel 多订单按天分组查看

每年年初获取有效订单

Power Bi 中如何实现跨表的大小比较筛选

Woocommerce 集团订单总额按自定义字段日期