web-dev-qa-db-ja.com

階層クエリを使用して、親の値に基づいて階層内の値を渡すにはどうすればよいですか?

カテゴリの表と、ユーザーごとのカテゴリーの割引の別の表があります。

create table category
( 
  id NUMBER NOT NULL,
  parent_id NUMBER,
  name nvarchar2(255),
  PRIMARY KEY( id)
);

create table category_discount
( 
  user_id NUMBER NOT NULL,
  category_id NUMBER NOT NULL,
  discount NUMBER NOT NULL,
  PRIMARY KEY( user_id, category_id)
);

insert into category( id, parent_id, name ) VALUES( 1, null, 'root');
insert into category( id, parent_id, name ) VALUES( 2, 1, 'C1');
insert into category( id, parent_id, name ) VALUES( 11, 7, 'C11');
insert into category( id, parent_id, name ) VALUES( 12, 7, 'C12');
insert into category( id, parent_id, name ) VALUES( 3, 1, 'C3');
insert into category( id, parent_id, name ) VALUES( 4, 1, 'C4');
insert into category( id, parent_id, name ) VALUES( 5, 4, 'C5');
insert into category( id, parent_id, name ) VALUES( 6, 4, 'C6');
insert into category( id, parent_id, name ) VALUES( 9, 6, 'C9');
insert into category( id, parent_id, name ) VALUES( 10, 6, 'C10');
insert into category( id, parent_id, name ) VALUES( 7, 1, 'C7');


insert into category_discount( user_id, category_id, discount ) VALUES( 1, 1, 30);
insert into category_discount( user_id, category_id, discount ) VALUES( 1, 4, 20);
insert into category_discount( user_id, category_id, discount ) VALUES( 1, 7, 25);

今、私がこのクエリを実行すると:

SELECT category.*, 
       category_discount.user_id, 
       category_discount.discount, 
       LEVEL 
FROM   category
LEFT JOIN category_discount 
       ON category.id = category_discount.category_id 
       AND category_discount.user_id = 1
START WITH parent_id is null
CONNECT BY PRIOR id = parent_id;

...この出力で問題ありません。

| ID | PARENT_ID | NAME | USER_ID | DISCOUNT | LEVEL |
|----|-----------|------|---------|----------|-------|
|  1 |    (null) | root |       1 |       30 |     1 |
|  2 |         1 |   C1 |  (null) |   (null) |     2 |
|  3 |         1 |   C3 |  (null) |   (null) |     2 |
|  4 |         1 |   C4 |       1 |       20 |     2 |
|  5 |         4 |   C5 |  (null) |   (null) |     3 |
|  6 |         4 |   C6 |  (null) |   (null) |     3 |
|  9 |         6 |   C9 |  (null) |   (null) |     4 |
| 10 |         6 |  C10 |  (null) |   (null) |     4 |
|  7 |         1 |   C7 |  (null) |   25     |     2 |
| 11 |         7 |  C11 |  (null) |   (null) |     3 |
| 12 |         7 |  C12 |  (null) |   (null) |     3 |

私の要件は、親に割引がある場合は常に、子が明示的な割引セットを持たない限り、階層を継承する必要があることです。したがって、私の最終出力は次のようになります。

| ID | PARENT_ID | NAME | USER_ID | DISCOUNT | LEVEL |
|----|-----------|------|---------|----------|-------|
|  1 |    (null) | root |       1 |       30 |     1 |
|  2 |         1 |   C1 |  (null) |       30 |     2 |
|  3 |         1 |   C3 |  (null) |       30 |     2 |
|  4 |         1 |   C4 |       1 |       20 |     2 |
|  5 |         4 |   C5 |  (null) |       20 |     3 |
|  6 |         4 |   C6 |  (null) |       20 |     3 |
|  9 |         6 |   C9 |  (null) |       20 |     4 |
| 10 |         6 |  C10 |  (null) |       20 |     4 |
|  7 |         1 |   C7 |  (null) |       25 |     2 |
| 11 |         7 |  C11 |  (null) |       25 |     3 |
| 12 |         7 |  C12 |  (null) |       25 |     3 |

これどうやってするの?

Oracle 11gおよびOracle 12cを使用しています。

4
Jay

これは、 再帰サブクエリファクタリング を使用せずに、 SYS_CONNECT_BY_PATH の魔法を使って実行できます。

SELECT category.*, 
       category_discount.user_id, 
       category_discount.discount, 
       sys_connect_by_path(category_discount.discount,':') discount_path, 
       regexp_substr(rtrim(sys_connect_by_path(category_discount.discount,':'),':'),'[0-9]+$') actual_discount, 
       LEVEL 
FROM   category
LEFT JOIN category_discount 
       ON category.id = category_discount.category_id 
       AND category_discount.user_id = 1
START WITH parent_id is null
CONNECT BY PRIOR id = parent_id
 ID | PARENT_ID | NAME | USER_ID |割引| DISCOUNT_PATH | ACTUAL_DISCOUNT |レベル
-:| --------:| :--- | ------:| -------:| :------------ | :-------------- | ----:
 1 | null|ルート| 1 | 30 | :30 | 30 | 1 
 2 | 1 | C1 | null| null| :30:| 30 | 2 
 3 | 1 | C3 | null| null| :30:| 30 | 2 
 4 | 1 | C4 | 1 | 20 | :30:20 | 20 | 2 
 5 | 4 | C5 | null| null| :30:20:| 20 | 3 
 6 | 4 | C6 | null| null| :30:20:| 20 | 3 
 9 | 6 | C9 | null| null| :30:20 :: | 20 | 4 
 10 | 6 | C10 | null| null| :30:20 :: | 20 | 4 
 7 | 1 | C7 | 1 | 25 | :30:25 | 25 | 2 
 11 | 7 | C11 | null| null| :30:25:| 25 | 3 
 12 | 7 | C12 | null| null| :30:25:| 25 | 3 

db <> fiddle ここに

出力のDISCOUNTおよびDISCOUNT_PATH列は、これがどのように機能するかを明確にするためにのみ含まれています— ACTUAL_DISCOUNTが必要なものです。

with a (id, parent_id, name, user_id, discount, lvl) as
(
  select
    c.id, c.parent_id, c.name, cd.user_id, cd.discount, 1 as lvl from category c
  LEFT JOIN category_discount cd
       ON c.id = cd.category_id AND cd.user_id = 1
  where parent_id is null
  union all
  select
    c2.id, c2.parent_id, c2.name, cd.user_id, 
    nvl(cd.discount, a.discount),
    lvl + 1
  from category c2
  LEFT JOIN category_discount cd 
       ON c2.id = cd.category_id AND cd.user_id = 1
  join a on c2.parent_id = a.id
)
search depth first by id set dummy
select id, parent_id, name, user_id, discount, lvl from a;

        ID  PARENT_ID NAME    USER_ID   DISCOUNT        LVL
---------- ---------- ---- ---------- ---------- ----------
         1            root          1         30          1
         2          1 C1                      30          2
         3          1 C3                      30          2
         4          1 C4            1         20          2
         5          4 C5                      20          3
         6          4 C6                      20          3
         9          6 C9                      20          4
        10          6 C10                     20          4
         7          1 C7            1         25          2
        11          7 C11                     25          3
        12          7 C12                     25          3

元の質問への回答:

あなたの例のデータに基づいて次のように仮定します(しかし、これがあなたが望むものかどうかはわかりません):

ルートによって定義されたブランチ全体の割引があります-私はこれをroot_discountと呼びます。

子(c1)に割引が設定されている場合、それはその子の割引です。

C2がc1の子で、c2に割引が設定されていない場合、c2はc1の割引を継承します。

C3がc2の子であり、c3に割引が設定されていない場合、c3はルート(root_discount)から割引を継承します。これにより、IDが9,10の行の割引が30になります。これがなければ、それはずっと簡単になります。

with a (id, parent_id, name, user_id, discount, prev_discount, root_discount, lvl) as
(
  select
    c.id, c.parent_id, c.name, cd.user_id, cd.discount, 1 as prev_discount,
    discount as root_discount, 1 as lvl 
  from category c
  LEFT JOIN category_discount cd
       ON c.id = cd.category_id AND cd.user_id = 1
  where parent_id is null
  union all
  select
    c2.id, c2.parent_id, c2.name, cd.user_id, 
    nvl(cd.discount, case when prev_discount = 1 then a.discount else root_discount end),
    case when cd.discount is not null then 1 else a.prev_discount - 1 end as prev_discount, 
    root_discount, lvl + 1
  from category c2
  LEFT JOIN category_discount cd
       ON c2.id = cd.category_id AND cd.user_id = 1
  join a on c2.parent_id = a.id
)
search depth first by id set dummy
select id, parent_id, name, user_id, discount, lvl from a;

        ID  PARENT_ID NAME    USER_ID   DISCOUNT        LVL
---------- ---------- ---- ---------- ---------- ----------
         1            root          1         30          1
         2          1 C1                      30          2
         3          1 C3                      30          2
         4          1 C4            1         20          2
         5          4 C5                      20          3
         6          4 C6                      20          3
         9          6 C9                      30          4
        10          6 C10                     30          4
         7          1 C7            1         25          2
        11          7 C11                     25          3
        12          7 C12                     25          3
2
Balazs Papp

私は再帰的ソリューションを使用して、子行の割引NULL値を親行の値に置き換えました。

WITH x (id, parent_id, name, user_id, discount)
AS
(
    SELECT 
        c.id, c.parent_id, c.name, cd.user_id, cd.discount
    FROM   
        category c
    LEFT JOIN 
        category_discount cd
        ON c.id = cd.category_id 
        AND cd.user_id = 1
    WHERE
        c.parent_id IS NULL
    UNION ALL
    SELECT 
        c.id, c.parent_id, c.name, cd.user_id, 
        CASE WHEN cd.discount IS NULL THEN x.discount ELSE cd.discount END discount
    FROM   
        x
    JOIN   
        category c
        ON c.parent_id = x.id
    LEFT JOIN 
        category_discount cd
        ON c.id = cd.category_id 
        AND cd.user_id = 1
)
SELECT 
    id, 
    parent_id, 
    name,
    user_id,
    discount
FROM   
    x;
 ID | PARENT_ID | NAME | USER_ID |割引
-:| --------:| : ------:| -------:
 1 | null|ルート| 1 | 30 
 2 | 1 | C1 | null| 30 
 3 | 1 | C3 | null| 30 
 4 | 1 | C4 | 1 | 20 
 7 | 1 | C7 | 1 | 25 
 11 | 7 | C11 | null| 25 
 12 | 7 | C12 | null| 25 
 5 | 4 | C5 | null| 20 
 6 | 4 | C6 | null| 20 
 9 | 6 | C9 | null| 20 
 10 | 6 | C10 | null| 20 

db <> fiddle ---(ここ

2
McNets