简体   繁体   English

使用递归的SQL-Oracle Update表

[英]SQL-Oracle Update table using recursivity

Lets assume we have a table like this one: 让我们假设我们有一个像这样的表:

+--------+------------+---------------+----------------+
| 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     |
+--------+------------+---------------+----------------+

I want to update it adding a new field which indicates the first position of a group. 我想更新它,添加一个新字段,指示组的第一个位置。 Forming part of a group means that follows these rules: 形成组的一部分意味着遵循以下规则:

  1. Share the same name 分享相同的名称
  2. Position numbers must be correlatives (Ex: Position 4 and 6 need a number 5 to create a group). 位置编号必须是相关的(例如:位置4和6需要数字5才能创建一个组)。
  3. The final date of first row must coincide with the initial date of the second one, and so on. 第一行的最终日期必须与第二行的初始日期一致,依此类推。

Having all of this in consideration, this should be the outcome: 考虑到所有这些因素,这应该是结果:

+--------+------------+---------------+----------------+------------+
| 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          |
+--------+------------+---------------+----------------+------------+

I can make it work only on groups of 2 members, but I do not know how to approach it in a more than 2 members situation. 我只能在2名成员的小组上工作,但我不知道如何在超过2名成员的情况下处理它。

This is an example code I used, which obviously does not work for big groups. 这是我使用的示例代码,显然不适用于大组。

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);

Should I use recursive queries to solve this? 我应该使用递归查询来解决这个问题吗? I don't know how to implement it in SQL in this situation. 在这种情况下,我不知道如何在SQL中实现它。

Any help is well appreciated! 任何帮助都非常感谢!

You can do this using a series of analytic functions, like so: 您可以使用一系列分析函数来执行此操作,如下所示:

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

The innermost subquery determines whether the position and dates of the current and previous row are correlated. 最里面的子查询确定当前行和上一行的位置和日期是否相关。 If they aren't, then it puts 1, otherwise it puts 0. 如果它们不是,那么它放1,否则放0。

The next subquery then calculates a running sum across these numbers - this has the effect of creating the same number for correlated rows (eg. 1 for positions 1 to 3, 2 for position 4 and 3 for position 6) which we can then use to group against. 然后,下一个子查询计算这些数字的运行总和 - 这具有为相关行创建相同数字的效果(例如,对于位置1到3为1,对于位置4为2,对于位置6为3),然后我们可以使用它们小组反对。

The outer query then simply finds the minimum position number per name and the newly created grouping column. 然后外部查询只查找每个名称的最小位置编号和新创建的分组列。

You could then use this query in your update statement to do the actual update (obviously, you wouldn't need the initial sample_data subquery, as you'd just use your table_name in the rest of the query directly). 然后,您可以在update语句中使用此查询来执行实际更新(显然,您不需要初始sample_data子查询,因为您只需在查询的其余部分直接使用table_name)。

You can use the LAG() and LAST_VALUE() analytic function to get the initial position for each group and then use MERGE (instead of UPDATE ) to update the table. 您可以使用LAG()LAST_VALUE()分析函数来获取每个组的初始位置,然后使用MERGE (而不是UPDATE )来更新表。

Oracle Setup : 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;

Update Query : 更新查询

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;

Output : 输出

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 

You can do this with window functions. 您可以使用窗口功能执行此操作。

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;

The basic idea is to determine if a new group starts. 基本思想是确定新组是否开始。 This firsts uses lag() to get the data in the "previous" row. 第一次使用lag()来获取“上一行”中的数据。 I am guessing that "previous" is based on the position (rather than the initial_date ). 我猜“前一个”是基于position (而不是initial_date )。

Then, a flag is created when a group starts -- "1" for a new group, "0" if not. 然后,在组开始时创建一个标志 - 对于一个新组为“1”,如果不是则为“0”。 The cumulative sum of this flag identifies a group. 该标志的累积总和标识一个组。

The outermost query simply assigns the minimum position in the group as the new field. 最外面的查询只是将组中的最小位置指定为新字段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM