web-dev-qa-db-ja.com

外部テーブルエイリアスを使用してトップ1サブクエリをOracleに変換する方法

次のSQL Serverクエリがあります

select
    (select top 1 b2 from  BB b where b.b1 = a.a1 order by b2) calc,
    a1,
    a2
from AA a
where a2 = 2;

分析関数を使用して書き換えることができます

select
    (select b2 from 
    (select 
        row_number() over (order by b2) lfd, 
     b2 from  BB b where b.b1 = a.a1
    ) as t where lfd = 1
    ) calc,
    a1,
    a2
from AA a
where a2 = 2;

これをOracleに変換すると

create table AA ( a1 NUMBER(10), a2 NUMBER(10) );
insert into AA values ( 1, 1);
insert into AA values ( 1, 2);
insert into AA values ( 1, 3);
insert into AA values ( 2, 2);

create table BB ( b1 NUMBER(10), b2 NUMBER(10) );
insert into BB values ( 1, 1);
insert into BB values ( 2, 4);
insert into BB values ( 2, 5);


select * from AA;
select * from BB;


select
    (select b2 from 
    (select 
        row_number() over (order by b2) lfd, 
     b2 from  BB b where b.b1 = a.a1
    )  where lfd = 1
    ) calc,
    a1,
    a2
from AA a
where a2 = 2;

次のエラーが表示されます

Error at line 5
ORA-00904: "A"."A1": invalid column name
4
bernd_k

最初にOracleで結合を実行します。

SELECT a1, a2, b2
  FROM (SELECT a1, a2, b2, 
               row_number() over(PARTITION BY a.a1 ORDER BY b.b2) lfd
           FROM AA a
           LEFT JOIN BB b ON b.b1 = a.a1
          WHERE a2 = 2)
 WHERE lfd = 1

クエリの問題は、現在、Oracleのサブクエリが2レベルより深い親クエリの値にアクセスできないことです。

内部SELECTを含むPL/SQL関数を使用することもできます。

6
Vincent Malgrat

これらがあなたが探している結果である場合:

CODE QTY  FRUIT_PRICE  FRUIT_NAME  DRINK_PRICE   DRINK_NAME  
---- ---- ------------ ----------- ------------- ------------
A    1    2.4          Apple       5.4           aperol      
B    1    1.3          banana      4.3           bear        
C    1                                                       

あなたはこれでそれを得ることができます:

SELECT o.code, o.qty, f.fruit_price, f.fruit_name, d.drink_price, d.drink_name 
FROM want_to_eat o
LEFT JOIN (
   SELECT Row_Number() OVER (PARTITION BY Fruit_Code ORDER BY f.datetime desc) 
        FruitRow
      , fruit_price, fruit_name, fruit_code
   FROM Fruit f
   ) f ON f.fruit_code = o.code AND f.FruitRow = 1
LEFT JOIN (
   SELECT Row_Number() OVER (PARTITION BY Drink_Code ORDER BY d.datetime desc) 
        DrinkRow
      , Drink_price, Drink_name, Drink_code
   FROM Drink d
   ) d ON d.Drink_code = o.code AND d.DrinkRow = 1
WHERE qty = 1;
2
Leigh Riffel

ここでは、Vincent Malgratのアプローチを使用したときに得られたいくつかの結果を示します。

最初に、さまざまなテーブルまたは順序に基づいてこのような上位1サブクエリを複数使用する場合、ROW_NUMBER()ではなくRANK()関数を使用する必要があることを学びました=関数。

2番目に、rank()を使用するときは、結合の問題があります。 SQL Serverの上位1は、rank = 1の行の1つを任意に選択しますが、rank()を使用すると、複数の行を返すことができます。

これらのケースでSQL Server top 1を使用するのは悪い設計だと思います。設計を修正するには、いくつかの一意の制約(一意のインデックスなど)を見つけて、このあいまいさを防ぐ必要があります。

自分で試してみたい場合のSQL Serverの例を次に示します。

以下の最後の2つのselectステートメントの実行プランを比較すると、Vincent Malgratのアプローチがトップ1のソリューションよりも優れていることがわかります。

SET NOCOUNT ON

begin try drop table fruit       end try begin catch end catch; 
begin try drop table want_to_eat end try begin catch end catch; 
begin try drop table drink       end try begin catch end catch; 

create table fruit (
 fruit_code char(1),
 fruit_name varchar(20),
 date   datetime,
 fruit_price  money
);
go

create table drink  (
 drink_code char(1),
 drink_name varchar(20),
 date   datetime,
 drink_price  money
);
go
create table want_to_eat (
 code char(1),
 qty  integer,
);
go
insert into want_to_eat values ( 'A', 1);
insert into want_to_eat values ( 'B', 2);
insert into want_to_eat values ( 'B', 1);
insert into want_to_eat values ( 'C', 1);


insert into fruit values ( 'A', 'Apple',  '20100101', '2.20');
insert into fruit values ( 'A', 'Apple',  '20110101', '2.40');
insert into fruit values ( 'B', 'banana', '20100101', '1.40');
insert into fruit values ( 'B', 'banana', '20110101', '1.30');
insert into fruit values ( 'B', 'banana', '20110101', '1.35');

insert into drink values ( 'A', 'aperol',  '20100101', '5.20');
insert into drink values ( 'A', 'aperol',  '20110101', '5.40');
insert into drink values ( 'B', 'bear',    '20100101', '4.40');
insert into drink values ( 'B', 'bear',    '20110101', '4.30');

create unique index iu_drink on drink(drink_code, date);
-- create unique index iu_fruit on fruit(fruit_code, date); -- Error


Select top 1 fruit_price from fruit where fruit_code = 'A' order by date desc;
Select top 1 fruit_price from fruit where fruit_code = 'B' order by date desc;
Select top 1 fruit_price from fruit where fruit_code = 'C' order by date desc;

SELECT
qty,
(Select top 1 fruit_price from fruit where fruit_code = code order by date desc) fruit_price,
(Select top 1 fruit_name  from fruit where fruit_code = code order by date desc) fruit_name,
(Select top 1 drink_price from drink where drink_code = code order by date desc) drink_price,
(Select top 1 drink_name  from drink where drink_code = code order by date desc) fruit_name
FROM want_to_eat
WHERE qty = 1;

SELECT qty, fruit_price, fruit_name , drink_price,drink_name from (
-- SELECT * FROM (
SELECT
RANK() OVER (PARTITION BY o.CODE ORDER BY f.date desc) f_lfd,
RANK() OVER (PARTITION BY o.CODE ORDER BY d.date desc) d_lfd,
f.date f_date,
code,
qty, 
fruit_price,
fruit_name,
drink_price,
drink_name
from want_to_eat o
left join fruit f on o.code = fruit_code
left join drink d on o.code = drink_code
WHERE qty = 1

) t 
where f_lfd = 1
and d_lfd = 1;

リー・リフェルへの回答:

次のIDが1と2の行では、どちらもrank()とdensity_rankが1です。

create table rank_test (
id int,
grp  int,
val varchar(10)
);

insert into rank_test values (1, 1, 'a');
insert into rank_test values (2, 1, 'a');
insert into rank_test values (3, 1, 'b');
insert into rank_test values (4, 2, 'b');

select * from rank_test;

select r.*,
RANK() OVER (PARTITION BY grp ORDER BY val) rank,
DENSE_RANK() OVER (PARTITION BY grp ORDER BY val) d_rank
from rank_test r
order by id;

結果:

id          grp         val        rank                 d_rank
----------- ----------- ---------- -------------------- --------------------
1           1           a          1                    1
2           1           a          1                    1
3           1           b          3                    2
4           2           b          1                    1

Leigh Riffelへの新しい回答:

ここの例では、私はあなたのパターンで変形することはできません。記事と価格の表と、価格変更の履歴のある2番目の表があります。一部の当局は、価格が変更されたときに古い価格を知りたいと考えています。

create table artikel (id int, price int);
create table price_history (id int,price int,v_date datetime);

insert into artikel values (1, 11), (2, 22);
insert into price_history values (1, 11, '20110101'), (2, 20,'20110101');
insert into price_history values (1, 11, '20110201'), (2, 20,'20110201'); -- the true table has more columns, which values might change while price stays the same
insert into price_history values (1, 11, '20110301'), (2, 22,'20110301');

Select id,
       (SELECT TOP 1 price_history.price FROM price_history WHERE price_history.id = artikel.id AND artikel.price <> price_history.price ORDER by v_date DESC ) priceOld
from artikel 
ORDER BY id;

Select artikel.id, y.price priceOld
from    artikel
LEFT JOIN (
   SELECT Row_Number() OVER (PARTITION BY id ORDER BY v_date desc) 
        yRow
      , id, price
   FROM price_history
   ) y ON y.id = artikel.id AND y.yRow = 1 and artikel.price <> y.price
ORDER BY id;   

私は手に入れたい

id          priceOld
----------- -----------
1           NULL
2           20

しかし、2番目は

id          priceOld
----------- -----------
1           NULL
2           NULL

ビンセント・マルグラットのアプローチは正しい結果をもたらします

Select id, priceOld from (
Select 
    Row_Number() OVER (PARTITION BY a.id ORDER BY v_date desc) vRow, 
    a.id, h.price priceOld
from artikel a
LEFT JOIN
   price_history h ON a.id = h.id and a.price <> h.price
) t 
where vRow = 1   
ORDER BY id;   
1
bernd_k