web-dev-qa-db-fra.com

Comment convertir une deuxième sous-requête en utilisant un alias de table externe vers Oracle?

J'ai la requête SQL Server suivante

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;

que je peux réécrire à l'aide de fonctions analytiques

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;

mais quand je convertit cela en 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;

Je reçois l'erreur suivante

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

vous effectueriez la première jointure à 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

Le problème avec votre requête est que, actuellement une sous-requête à Oracle, vous ne pouvez pas accéder à une valeur d'une requête mère plus de deux niveaux de niveau.

Vous pouvez également utiliser une fonction PL/SQL qui contiendrait la sélection interne.

6
Vincent Malgrat

Si ce sont les résultats que vous recherchez:

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                                                       

Vous pouvez l'obtenir avec ceci:

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

Ici, je vais montrer quelques résultats que j'ai obtenus lors de l'approche de Vincent Malgrat.

J'ai d'abord appris que lorsque vous utilisez plus d'une telle sous-requête top 1 sur différentes tables ou commandes, je dois utiliser la fonction rang () fonction et non le Row_Number () = fonction.

Deuxièmement, lors de l'utilisation d'un rang (), il y a le problème des liens. Le top 1 de SQL Server sélectionne arbitrairement l'une des lignes avec rang = 1 lors de l'utilisation de Rank () peut revenir plus d'une ligne.

Je pense que c'est une mauvaise conception d'utiliser SQL Server Top 1 dans ces cas. Pour que la conception correcte certaines contraintes uniques (par exemple, des index uniques) doivent être trouvés, qui empêchent cette ambiguïté.

Voici un exemple de serveur SQL si vous voulez l'essayer vous-même.

La comparaison des plans d'exécution des deux dernières déclarations de sélection ci-dessous montre que l'approche de Vincent Malgrat est meilleure que la solution top 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;

Réponse à Leigh Riffel :

Dans les lignes suivantes avec ID 1 et 2 ont les deux rang () et dense_rank de 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;

Résultat:

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

nouvelle réponse à Leigh Riffel :

Voici un exemple, je ne peux pas transformer avec votre motif. J'ai une table avec des articles un prix et une deuxième table avec l'historique des changements de prix. Une certaine autorité veut connaître l'ancien prix, lorsque le prix a changé.

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;   

Je veux obtenir

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

mais la seconde donne

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

L'approche de Vincent Malgrat donne le résultat correct

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