使用(递归?)CTE +窗口函数将销售订单归零?

时间:2017-03-11 08:30:23

标签: sql sql-server common-table-expression window-functions

我正在尝试使用递归CTE +窗口函数来查找一系列买入/卖出订单的最后结果。

首先,这里有一些命名法:

  • field_id是商店的ID。
  • Field_number是一个订单号,但可以由同一个人重复使用
  • Field_date是初始订单的日期。
  • Field_inserted是此特定事务发生的时间。
  • Field_sale是我们是买还是退货。

不幸的是,由于系统的工作方式,我不会在退回物品时得到成本,因此找出订单的最后结果很复杂(我们最终还是卖掉了)。我需要将购买与销售相匹配,这通常很有效。但是,当它失败时会出现如下情况,并且我试图在一次传递中找到一种方法,可能使用递归CTE。

这是一些代码。

DECLARE @tablea TABLE (field_id int, field_number CHAR(3), field_date datetime, field_inserted DATETIME, field_sale varchar(4))
INSERT INTO @tablea
VALUES 
(1, 100, '20170311','20170311 01:00:00', 'Buy'), 
(1, 100, '20170311','20170311 01:01:00', 'Retu'),
(1, 100, '20170311','20170311 01:02:00', 'Buy'),
(1, 100, '20170311','20170311 01:03:00', 'Retu'),
(1, 100, '20170311','20170311 01:02:01', 'buy'),
(2, 100, '20170311','20170311 01:03:00', 'REtu'),
(1, 110, '20170311','20170311 01:03:00', 'Buy');

现在删除随后返回的购买。 ISNULL是因为我是NOT IN将忽略_lead / _lag值为NULL的所有行。

WITH cte AS 
(SELECT 
        ROW_NUMBER() OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS row_num,
        field_id,
        field_number, 
        field_date,
        field_sale,
       lead(field_sale) OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS field_sale_lead,      
       lag(field_sale)  OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS field_sale_lag    
FROM   @tablea
)
SELECT * FROM cte
WHERE NOT (cte.field_sale = 'Buy'  AND ISNULL(field_sale_lead,'') = 'Retu')--AND field_sale_lead IS NOT null)
  AND NOT (cte.field_sale = 'Retu' AND ISNULL(field_sale_lag,'') =  'buy' )--AND field_sale_lag  IS NOT NULL)

我觉得很沾沾自喜,以为我拥有它。然而,这是一个简单的案例。购买,退货,购买,退货。让我们试试另一个案例,买入买入回报,这仍然有效,但显然会导致净值为0 ..

DECLARE @tablea TABLE (field_id int, field_number CHAR(3), field_date datetime, field_inserted DATETIME, field_sale varchar(4))
INSERT INTO @tablea
VALUES 
(1, 100, '20170311','20170311 01:00:00', 'Buy'), 
(1, 100, '20170311','20170311 01:01:00', 'Buy'),
(1, 100, '20170311','20170311 01:02:00', 'Retu'),
(1, 100, '20170311','20170311 01:03:00', 'Retu'),
(2, 100, '20170311','20170311 01:03:00', 'Buy'),
(1, 110, '20170311','20170311 01:03:00', 'Buy');


WITH cte AS 
(SELECT 
        ROW_NUMBER() OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS row_num,
        field_id,
        field_number, 
        field_date,
        field_sale,
       lead(field_sale) OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS field_sale_lead,      
       lag(field_sale)  OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS field_sale_lag    
FROM   @tablea
)
SELECT * FROM cte
WHERE NOT (cte.field_sale = 'Buy'  AND ISNULL(field_sale_lead,'') = 'sell')--AND field_sale_lead IS NOT null)
  AND NOT (cte.field_sale = 'sell' AND ISNULL(field_sale_lag,'') =  'buy' )--AND field_sale_lag  IS NOT NULL)

但是,当你这样做时,你会发现它找到了直接匹配,但是现在还有买入/回报对,我想取消它。

就在这时我被卡住了。我以前做过递归CTE,但无论出于什么原因,我都无法弄清楚如何递归并使其取消1/1/100和4/1/100。我设法做的就是让它在递归上窒息。

DECLARE @tablea TABLE (field_id int, field_number CHAR(3), field_date datetime, field_inserted DATETIME, field_sale varchar(4))
INSERT INTO @tablea
VALUES 
(1, 100, '20170311','20170311 01:00:00', 'Buy'), 
(1, 100, '20170311','20170311 01:01:00', 'Buy'),
(1, 100, '20170311','20170311 01:02:00', 'Retu'),
(1, 100, '20170311','20170311 01:03:00', 'Retu'),
(2, 100, '20170311','20170311 01:03:00', 'Buy'),
(1, 110, '20170311','20170311 01:03:00', 'Buy');

WITH cte AS 
(SELECT 
        ROW_NUMBER() OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS row_num,
        field_id,
        field_number, 
        field_date,
        field_sale,
        field_inserted,
       lead(field_sale) OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS field_sale_lead,      
       lag(field_sale)  OVER (PARTITION BY field_id, field_number, field_date ORDER BY field_inserted) AS field_sale_lag    
FROM   @tablea
--) 
--SELECT * FROM cte
--WHERE NOT (cte.field_sale = 'Buy'  AND ISNULL(field_sale_lead,'') = 'Retu')--AND field_sale_lead IS NOT null)
--AND NOT (cte.field_sale = 'Retu' AND ISNULL(field_sale_lag,'') =  'buy' )--AND field_sale_lag  IS NOT NULL)

UNION ALL
SELECT 
        ROW_NUMBER() OVER (PARTITION BY  cte.field_id, cte.field_number, cte.field_date ORDER BY cte.field_inserted) AS row_num,
        cte.field_id,
        cte.field_number, 
        cte.field_date,
        cte.field_sale,
        cte.field_inserted,
       lead(cte.field_sale) OVER (PARTITION BY cte.field_id, cte.field_number, cte.field_date ORDER BY cte.field_inserted) AS field_sale_lead,      
       lag(cte.field_sale)  OVER (PARTITION BY cte.field_id, cte.field_number, cte.field_date ORDER BY cte.field_inserted) AS field_sale_lag    
FROM   @tablea INNER JOIN cte ON cte.field_date = [@tablea].field_date AND cte.field_id = [@tablea].field_id AND cte.field_number = [@tablea].field_number
)
SELECT * FROM cte
WHERE NOT (cte.field_sale = 'Buy'  AND ISNULL(field_sale_lead,'') = 'Retu')--AND field_sale_lead IS NOT null)
  AND NOT (cte.field_sale = 'Retu' AND ISNULL(field_sale_lag,'') =  'buy' )--AND field_sale_lag  IS NOT NULL)

2 个答案:

答案 0 :(得分:1)

我们可以使用common table expression row_number() 来解决这个没有循环或递归

如果我正确理解您的问题,您希望删除已退回的销售 ,对于每个'retu',它应删除最新的'buy'

首先,我们将使用id添加row_number()到我们的行集,以便我们可以唯一地标识行。

接下来,我们添加由br_rn分区的field_id, field_number, field_date(购买/返回行号的缩写),但我们还将 field_sale添加到分区;我们将按field_inserted desc订购。 这样我们就可以将每个'retu'与最新的'buy'进行匹配,一旦我们能够做到这一点,我们就可以使用not exists()消除所有对:

;with cte as (
  select 
      id = row_number() over (
        order by field_id, field_number, field_date, field_inserted asc
        ) 
    , field_id
    , field_number
    , field_date 
    , field_inserted 
    , field_sale
    , br_rn = row_number() over (
        partition by field_id, field_number, field_date, field_sale
        order by field_inserted desc
        ) 
  from @tablea
)
select 
    id 
  , field_number
  , field_date
  , field_inserted
  , field_sale
from cte
where not exists (
  select 1
  from cte as i
  where i.field_id = cte.field_id
    and i.field_number = cte.field_number
    and i.field_date = cte.field_date
    and i.br_rn = cte.br_rn
    and i.id <> cte.id
    )
order by id

rextester演示:http://rextester.com/TKXOC61533

对于此输入:

  (1, 100, '20170311','20170311 01:00:00', 'Buy') 
, (1, 100, '20170311','20170311 01:01:00', 'Buy')
, (1, 100, '20170311','20170311 01:02:00', 'Retu')
, (1, 100, '20170311','20170311 01:03:00', 'Retu')
, (2, 100, '20170311','20170311 01:03:00', 'Buy')
, (1, 110, '20170311','20170311 01:03:00', 'Buy');

返回:

+----+----------+--------------+------------+---------------------+------------+
| id | field_id | field_number | field_date |   field_inserted    | field_sale |
+----+----------+--------------+------------+---------------------+------------+
|  5 |        1 |          110 | 2017-03-11 | 2017-03-11 01:03:00 | Buy        |
|  6 |        2 |          100 | 2017-03-11 | 2017-03-11 01:03:00 | Buy        |
+----+----------+--------------+------------+---------------------+------------+

并为此输入:

  (1, 100, '20170311','20170311 01:01:00', 'Buy')
, (1, 100, '20170311','20170311 01:02:00', 'Buy')
, (1, 100, '20170311','20170311 01:03:00', 'Buy') 
, (1, 100, '20170311','20170311 01:04:00', 'Retu')
, (1, 100, '20170311','20170311 01:05:00', 'Buy') 
, (1, 100, '20170311','20170311 01:06:00', 'Retu')
, (1, 100, '20170311','20170311 01:07:00', 'Retu')
, (2, 100, '20170311','20170311 01:03:00', 'Buy')
, (1, 110, '20170311','20170311 01:03:00', 'Buy');

返回:

+----+----------+--------------+------------+---------------------+------------+
| id | field_id | field_number | field_date |   field_inserted    | field_sale |
+----+----------+--------------+------------+---------------------+------------+
|  1 |        1 |          100 | 2017-03-11 | 2017-03-11 01:01:00 | Buy        |
|  8 |        1 |          110 | 2017-03-11 | 2017-03-11 01:03:00 | Buy        |
|  9 |        2 |          100 | 2017-03-11 | 2017-03-11 01:03:00 | Buy        |
+----+----------+--------------+------------+---------------------+------------+

输入:

  (1, 100, '20170311','20170311 01:01:00', 'Buy')
, (1, 100, '20170311','20170311 01:02:00', 'Buy')
, (1, 100, '20170311','20170311 01:04:00', 'Retu')
, (1, 100, '20170311','20170311 01:05:00', 'Retu')
, (1, 100, '20170312','20170311 01:06:00', 'Buy')
, (1, 100, '20170312','20170311 01:07:00', 'Buy')
, (2, 100, '20170311','20170311 01:03:00', 'Buy')
, (1, 110, '20170311','20170311 01:03:00', 'Buy')

返回:

+----+----------+--------------+------------+---------------------+------------+
| id | field_id | field_number | field_date |   field_inserted    | field_sale |
+----+----------+--------------+------------+---------------------+------------+
|  5 |        1 |          100 | 2017-03-12 | 2017-03-11 01:06:00 | Buy        |
|  6 |        1 |          100 | 2017-03-12 | 2017-03-11 01:07:00 | Buy        |
|  7 |        1 |          110 | 2017-03-11 | 2017-03-11 01:03:00 | Buy        |
|  8 |        2 |          100 | 2017-03-11 | 2017-03-11 01:03:00 | Buy        |
+----+----------+--------------+------------+---------------------+------------+

在我们消除任何配对之前,它可能有助于说明我们正在做什么来看看cte返回的内容。

在我们过滤它之前,只查看需要过滤的集合:

+----+----------+--------------+------------+---------------------+------------+-------+
| id | field_id | field_number | field_date |   field_inserted    | field_sale | br_rn |
+----+----------+--------------+------------+---------------------+------------+-------+
|  1 |        1 |          100 | 2017-03-11 | 2017-03-11 01:01:00 | Buy        |     4 |
|  2 |        1 |          100 | 2017-03-11 | 2017-03-11 01:02:00 | Buy        |     3 |
|  3 |        1 |          100 | 2017-03-11 | 2017-03-11 01:03:00 | Buy        |     2 |
|  4 |        1 |          100 | 2017-03-11 | 2017-03-11 01:04:00 | Retu       |     3 |
|  5 |        1 |          100 | 2017-03-11 | 2017-03-11 01:05:00 | Buy        |     1 |
|  6 |        1 |          100 | 2017-03-11 | 2017-03-11 01:06:00 | Retu       |     2 |
|  7 |        1 |          100 | 2017-03-11 | 2017-03-11 01:07:00 | Retu       |     1 |
+----+----------+--------------+------------+---------------------+------------+-------+

这样看,我们可以很容易地看到'buy'订单id 1的{​​{1}} br_rn并且没有关联{ {1}}。

答案 1 :(得分:0)

有一点我可以建议在可能的情况下删除一对顺序买入/返回。尝试

DECLARE @tablea TABLE (field_id int, field_number CHAR(3), field_date datetime, field_inserted DATETIME, field_sale varchar(4))
INSERT INTO @tablea
VALUES 
(1, 100, '20170311','20170311 01:01:00', 'Buy'),
(1, 100, '20170311','20170311 01:02:00', 'Buy'), 
(1, 100, '20170311','20170311 01:03:00', 'Buy'), 
(1, 100, '20170311','20170311 01:04:00', 'Retu'),
(1, 100, '20170311','20170311 01:05:00', 'Buy'), 
(1, 100, '20170311','20170311 01:06:00', 'Retu'),
(1, 100, '20170311','20170311 01:07:00', 'Retu'),
(2, 100, '20170311','20170311 01:03:00', 'Buy'),
(1, 110, '20170311','20170311 01:03:00', 'Buy');

select * from @tablea
order by field_id,
        field_number, 
        field_inserted 

declare @eoj int =1;
while @eoj > 0
begin
    WITH cte AS 
    (
        SELECT 
            case field_sale when 'Buy' then 
                  lead (field_sale)  OVER (PARTITION BY field_id, field_number  ORDER BY field_inserted)
                  when 'Retu' then 
                  lag (field_sale)  OVER (PARTITION BY field_id, field_number  ORDER BY field_inserted)
                  end nbr_type,
            field_id,
            field_number, 
            field_date,
            field_sale,
            field_inserted 
    FROM   @tablea 
    ) 
    delete  
    from cte 
    where nbr_type is not null and nbr_type <> field_sale;
    set @eoj = @@rowcount;
    -- check it
    select * from @tablea
    order by field_id,
            field_number, 
            field_inserted; 
end;

它将重复N + 1次,其中N是最长返回序列的长度。在上面的例子中N = 2.