トップページに戻る
次のSQLパズルへ
前のSQLパズルへ
9-62 製品の対象年齢の範囲
SQLパズル
PriceByAgeテーブル
product_id low_age high_age
---------- ------- --------
Product_1 5 15
Product_1 16 60
Product_1 65 150
Product_2 1 5
Product_2 6 70
Product_2 71 150
Product_3 1 15
Product_3 10 150
Product_3 20 150
Product_3 30 150
Product_4 1 150
Product_5 1 130
Product_5 131 150
Product_6 1 130
Product_6 135 150
product_idごとで、low_ageからhigh_ageまでの範囲が、
1才から150才までを網羅するproduct_idを出力する。
出力結果
product_id
----------
Product_2
Product_3
Product_4
Product_5
SQLパズル 第2版のパズル65 [製品の対象年齢の範囲] を参考にさせていただきました
データ作成スクリプト
create table PriceByAge(
product_id char(9),
low_age number(3),
high_age number(3) not null,
check(low_age < high_age),
primary key (product_id,low_age));
insert into PriceByAge values('Product_1', 5, 15);
insert into PriceByAge values('Product_1', 16, 60);
insert into PriceByAge values('Product_1', 65,150);
insert into PriceByAge values('Product_2', 1, 5);
insert into PriceByAge values('Product_2', 6, 70);
insert into PriceByAge values('Product_2', 71,150);
insert into PriceByAge values('Product_3', 1, 15);
insert into PriceByAge values('Product_3', 10,150);
insert into PriceByAge values('Product_3', 20,150);
insert into PriceByAge values('Product_3', 30,150);
insert into PriceByAge values('Product_4', 1,150);
insert into PriceByAge values('Product_5', 1,130);
insert into PriceByAge values('Product_5',131,150);
insert into PriceByAge values('Product_6', 1,130);
insert into PriceByAge values('Product_6',135,150);
commit;
SQL
--■■■連番テーブルを使う方法■■■
select a.product_id
from PriceByAge a,(select RowNum as Counter
from all_objects
where RowNum <= 150) b
where b.Counter between a.low_age and a.high_age
group by a.product_id
having count(distinct b.Counter) = 150
order by a.product_id;
--■■■再帰with句を使う方法(11gR2以降)■■■
with rec(product_id,low_age,high_age,ValAge) as(
select product_id,low_age,high_age,low_age
from PriceByAge
union all
select product_id,low_age,high_age,ValAge+1
from rec
where ValAge+1 <= high_age)
select product_id
from rec
where ValAge between 1 and 150
group by product_id
having count(distinct ValAge) = 150
order by product_id;
--■■■表関数を使う方法■■■
create or replace Package Pack09_62 Is
type PrintType is record(
product_id PriceByAge.product_id%type);
type PrintTypeSet is table of PrintType;
end;
/
create or replace function OutputPrice return Pack09_62.PrintTypeSet PipeLined is
out_rec Pack09_62.PrintType;
type AgeArrayType is table of boolean index by binary_integer;
AgeArray AgeArrayType;
willOut boolean;
procedure ArrayInit is
begin
for I in 1..150 Loop
AgeArray(I) := false;
end Loop;
end;
begin
ArrayInit;
for rec in (select product_id,low_age,high_age,
case Lead(product_id) over(order by product_id,low_age)
when product_id then 0 else 1 end as NextBreak
from PriceByAge
order by product_id,low_age) Loop
for I in rec.low_age..rec.high_age Loop
AgeArray(I) := true;
end Loop;
if rec.NextBreak = 1 then
willOut := true;
for I in 1..150 Loop
willOut := willOut and AgeArray(I);
end Loop;
ArrayInit;
if willOut then
out_rec.product_id := rec.product_id;
pipe row(out_rec);
end if;
end if;
end Loop;
end;
/
select product_id
from table(OutputPrice)
order by product_id;
解説
連番テーブルを使うと楽でしょう。
なお、年齢が
1才から150才ではなく
10才から40才という場合は、case式を使います。
select a.product_id
from PriceByAge a,(select RowNum as Counter
from all_objects
where RowNum <= 150) b
where b.Counter between a.low_age and a.high_age
group by a.product_id
having count(distinct case when b.Counter between 10 and 40
then b.Counter end) = (40-10)+1
order by a.product_id;