使用递归的SQL-Oracle Update表

时间:2016-06-09 09:52:26

标签: sql oracle oracle11g

让我们假设我们有一个像这样的表:

+--------+------------+---------------+----------------+
| 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. 第一行的最终日期必须与第二行的最后日期一致,依此类推。
  4. 考虑到所有这一切,这应该是结果:

    +--------+------------+---------------+----------------+------------+
    | 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中实现它。

    非常感谢任何帮助!

3 个答案:

答案 0 :(得分: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到3为1,对于位置4为2,对于位置6为3),我们可以然后用来反对。

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

然后,您可以在update语句中使用此查询来执行实际更新(显然,您不需要初始sample_data子查询,因为您只需在其余部分中使用table_name直接查询)。

答案 1 :(得分:4)

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

Oracle安装程序

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 

答案 2 :(得分: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()获取&#34;之前&#34;中的数据。行。我在猜测&#34;之前&#34;基于position(而不是initial_date)。

然后,当一个小组开始时会创建一个标志 - &#34; 1&#34;对于一个新的团队,&#34; 0&#34;如果不。此标志的累积总和标识一个组。

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