2016-06-09 95 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