カテゴリの表と、ユーザーごとのカテゴリーの割引の別の表があります。
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を使用しています。
これは、 再帰サブクエリファクタリング を使用せずに、 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
私は再帰的ソリューションを使用して、子行の割引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 ---(ここ