2016-06-09 79 views
3

SQL,甲骨文更新表让我们假设我们有这样一个表:使用递归性

+--------+------------+---------------+----------------+ 
| Name | Position | Initial Date | Final Date  | 
+--------+------------+---------------+----------------+ 
| XXX | 1   | 2016/06/07 | 2016/06/08  | 
| XXX | 2   | 2016/06/08 | 2016/06/09  | 
| XXX | 3   | 2016/06/09 | 2016/06/10  | 
| XXX | 4   | 2016/06/13 | 2016/06/14  | 
| XXX | 6   | 2016/06/14 | 2016/06/15  | 
| YYY | 1   | 2016/06/02 | 2016/06/03  | 
+--------+------------+---------------+----------------+ 

我想更新添加新的字段,指示一组的第一个位置。 形成一个基团的一部分是指遵循以下规则:

  1. 共享相同的名称
  2. 位置编号必须是关联度(例如:位置4和6需要的数5,创建的基团)。
  3. 第一行的最后日期必须与第二行的最后日期重合,依此类推。

拥有这一切的考虑,这应该是结果:

+--------+------------+---------------+----------------+------------+ 
| Name | Position | Initial Date | Final Date  | New field | 
+--------+------------+---------------+----------------+------------+ 
| XXX | 1   | 2016/06/07 | 2016/06/08  | 1   | 
| XXX | 2   | 2016/06/08 | 2016/06/09  | 1   | 
| XXX | 3   | 2016/06/09 | 2016/06/10  | 1   | 
| XXX | 4   | 2016/06/13 | 2016/06/14  | 4   | 
| XXX | 6   | 2016/06/14 | 2016/06/15  | 6   | 
| YYY | 1   | 2016/06/02 | 2016/06/03  | 1   | 
+--------+------------+---------------+----------------+------------+ 

我可以把它只有2个成员组的工作,但我不知道如何处理它更比2成员的情况。

这是我使用的一个示例代码,显然不适用于大团体。

update table1 f1 
set f1.new_field = NVL((select f2.position 
        from table1 f2 
        where f1.name = f2.name and 
        f2.position = f1.position+1 and 
        f1.final_date = f2.initial_date),f1.position); 

我应该使用递归查询来解决这个问题吗?在这种情况下,我不知道如何在SQL中实现它。

任何帮助,非常感谢!

回答

4

为此,您可以使用一系列的分析功能,像这样:

with sample_data as (select 'XXX' name, 1 position, to_date('07/06/2016', 'dd/mm/yyyy') initial_date, to_date('08/06/2016', 'dd/mm/yyyy') final_date from dual union all 
        select 'XXX' name, 2 position, to_date('08/06/2016', 'dd/mm/yyyy') initial_date, to_date('09/06/2016', 'dd/mm/yyyy') final_date from dual union all 
        select 'XXX' name, 3 position, to_date('09/06/2016', 'dd/mm/yyyy') initial_date, to_date('10/06/2016', 'dd/mm/yyyy') final_date from dual union all 
        select 'XXX' name, 4 position, to_date('13/06/2016', 'dd/mm/yyyy') initial_date, to_date('14/06/2016', 'dd/mm/yyyy') final_date from dual union all 
        select 'XXX' name, 6 position, to_date('14/06/2016', 'dd/mm/yyyy') initial_date, to_date('15/06/2016', 'dd/mm/yyyy') final_date from dual union all 
        select 'YYY' name, 1 position, to_date('02/06/2016', 'dd/mm/yyyy') initial_date, to_date('03/06/2016', 'dd/mm/yyyy') final_date from dual) 
-- end of mimicking a table called "sample_data" containing your data 
select name, 
     position, 
     initial_date, 
     final_date, 
     min(position) over (partition by name, grp_sum) new_field 
from (select name, 
       position, 
       initial_date, 
       final_date, 
       sum(change_grp_required) over (partition by name order by position) grp_sum 
     from (select name, 
         position, 
         initial_date, 
         final_date, 
         case when position - lag(position, 1, position) over (partition by name order by position) != 1 
           or initial_date != lag(final_date, 1, initial_date - 1) over (partition by name order by position) then 1 
          else 0 
         end change_grp_required 
       from sample_data)); 

NAME POSITION INITIAL_DATE FINAL_DATE NEW_FIELD 
---- ---------- ------------ ---------- ---------- 
XXX   1 2016/06/07 2016/06/08   1 
XXX   2 2016/06/08 2016/06/09   1 
XXX   3 2016/06/09 2016/06/10   1 
XXX   4 2016/06/13 2016/06/14   4 
XXX   6 2016/06/14 2016/06/15   6 
YYY   1 2016/06/02 2016/06/03   1 

最里面的子查询确定位置和当前和以前行的日期是否相关。如果它们不是,那么它将放1,否则放0。

下一个子查询然后计算跨这些数字的运行总和 - 这会产生相关行的相同数字的效果(例如,对于位置1 1到3,2为位置4和3为位置6),然后我们可以使用它来反对。

然后,外部查询只需查找每个名称的最小位置编号和新创建的分组列。

然后,您可以使用此查询在update语句来完成实际的更新(当然,你不需要初始sample_data子查询,因为你只是直接使用你的表名在查询的其余部分)。

+0

谢谢@Boneist为您快速回复。我在更新中使用它,它绝对对我有用。我甚至不知道这个lag()函数的存在。 – Roy90

+0

分析函数是非常有用的野兽;如果你还没有,我强烈建议你看看他们并与他们一起玩。他们非常强大* {:-)另外,我认为@MT0的解决方案可能比我的解决方案更快,所以我建议您测试两种数据,并查看哪一个性能最好。 – Boneist

1

您可以使用窗口功能来做到这一点。

select t.*, min(position) over (partition by name, grp) as new_field 
from (select t.*, 
      sum(case when (prev_position = position - 1) and 
          (prev_final_date = initial_date) 
         then 0 else 1 
       end) over (partition by name) as grp 
     from (select t.*, 
        lag(position) over (partition by name order by position) as prev_position, 
        lag(final_date) over (partition by name order by position) as prev_final_date 
      from t 
      ) t 
    ) t; 

其基本思想是确定一个新组是否开始。这首先使用lag()来获取“上一个”行中的数据。我猜测“之前”是基于position(而不是initial_date)。

然后,一个组开始时创建一个标志 - 新组的“1”,否则为“0”。这个标志的累积和确定了一个组。

最外面的查询只是将组中的最小位置指定为新字段。

+0

不应该是'(prev_position = position - 1)和(prev_final_date = initial_date)' - 即'和'而不是'或'? – Boneist

+0

OP在组中使用'new_field'值的最小位置值。 – MT0

+0

@Boneist。 。 。是的,我认为你是对的。 –

4

您可以使用LAG()LAST_VALUE()分析函数获取每个组的初始位置,然后使用MERGE(而不是UPDATE)更新表格。

甲骨文设置

CREATE TABLE table_name (Name, Position, Initial_Date, Final_Date) AS 
SELECT 'XXX', 1, DATE '2016-06-07', DATE '2016-06-08' FROM DUAL UNION ALL 
SELECT 'XXX', 2, DATE '2016-06-08', DATE '2016-06-09' FROM DUAL UNION ALL 
SELECT 'XXX', 3, DATE '2016-06-09', DATE '2016-06-10' FROM DUAL UNION ALL 
SELECT 'XXX', 4, DATE '2016-06-13', DATE '2016-06-14' FROM DUAL UNION ALL 
SELECT 'XXX', 6, DATE '2016-06-14', DATE '2016-06-15' FROM DUAL UNION ALL 
SELECT 'YYY', 1, DATE '2016-06-02', DATE '2016-06-03' FROM DUAL; 

ALTER TABLE table_name ADD new_field INT; 

更新查询

MERGE INTO table_name d 
USING (
     SELECT LAST_VALUE(start_of_group) IGNORE NULLS 
       OVER (PARTITION BY Name ORDER BY position) 
       AS new_field 
     FROM (
      SELECT name, 
       position, 
       CASE WHEN position - 1 = LAG(position ) 
              OVER (PARTITION BY NAME 
                ORDER BY position) 
         AND initial_date = LAG(final_date) 
              OVER (PARTITION BY NAME 
                ORDER BY position) 
         THEN NULL 
         ELSE position 
         END AS start_of_group 
      FROM table_name t 
     ) 
    ) s 
     ON (d.ROWID = s.ROWID) 
WHEN MATCHED THEN 
    UPDATE SET new_field = s.new_field; 

输出

SELECT * FROM table_name; 

NAME POSITION INITIAL_DATE  FINAL_DATE   NEW_FIELD 
---- ---------- ------------------- ------------------- ---------- 
XXX   1 2016-06-07 00:00:00 2016-06-08 00:00:00   1 
XXX   2 2016-06-08 00:00:00 2016-06-09 00:00:00   1 
XXX   3 2016-06-09 00:00:00 2016-06-10 00:00:00   1 
XXX   4 2016-06-13 00:00:00 2016-06-14 00:00:00   4 
XXX   6 2016-06-14 00:00:00 2016-06-15 00:00:00   6 
YYY   1 2016-06-02 00:00:00 2016-06-03 00:00:00   1