Выбор первой и последней записей объединения при определенных допущениях - PullRequest
0 голосов
/ 21 февраля 2020

У меня есть игра, в которой каждый игрок может разместить различные in ордера, и за каждым in ордером должен следовать один или несколько out ордеров. В конце игры оценка игрока определяется разницей между ценами его первого in и последнего out. Определение таблиц для игры следующее, где последовательность мест задается автоматической c последовательностью вставок.

create table prices
(
   sequence int identity(1,1),
   price int
 );    
create table orders
(
   player int,
   sequence int identity(1,1),
   place varchar(30),
   operation varchar(3)
 );
 create table players
(
   player int,
   name varchar(30)
 );

Я делаю следующий запрос для вычисления оценка всех игроков

select cst.name, 
ord_in.place as 'first in' ,
ord_out.place as 'last out' ,
pr_in.price - pr_out.price as 'diff'
from players cst 
left join orders ord_in 
  on cst.player = ord_in.player 
  and ord_in.operation = 'in'
  and ord_in.sequence = (
    select min(sequence) from orders
    where player = cst.player 
  )
left join orders ord_out 
  on cst.player = ord_out.player 
  and ord_out.operation = 'out'
  and ord_out.sequence = (
    select max(sequence) from orders
    where player = cst.player 
  )
left join prices pr_out on pr_out.sequence = ord_out.sequence
left join prices pr_in on pr_in.sequence = ord_in.sequence

, и результат в порядке, за исключением следующего случая

 insert into players (player, name) values (1,'sandra');
 insert into orders (player, place, operation) values (1,'germany','in');
 insert into prices (price) values (10);
 insert into orders (player, place, operation) values (1,'france','out');
 insert into prices (price) values (300);
 insert into orders (player, place, operation) values (1,'italy','out');
 insert into prices (price) values (50);
 insert into orders (player, place, operation) values (1,'spain','in');
 insert into prices (price) values (200);
 insert into orders (player, place, operation) values (1,'russia','out');
 insert into prices (price) values (100);
 insert into orders (player, place, operation) values (1,'belgium','in');
 insert into prices (price) values (80);

Почему последний out возвращается как null, как показано в этом скрипка ? Что не так?

Ответы [ 2 ]

3 голосов
/ 21 февраля 2020

Вы можете использовать оконные функции и условное агрегирование:

select
    player,
    name,
    max(case when operation = 'in' and rn_asc = 1 then price end)
        - max(case when operation = 'out' and rn_desc = 1 then price end) diff
from (
    select
        p.player,
        p.name,
        o.place,
        o.operation,
        r.price,
        row_number() over(partition by operation order by o.sequence) rn_asc,
        row_number() over(partition by operation order by o.sequence desc) rn_desc
    from players p
    inner join orders o on o.player = p.player
    inner join prices r on r.sequence = o.sequence
) t
where 
    (operation = 'in' and rn_asc = 1)
    or (operation = 'out' and rn_desc = 1)
group by player, name
0 голосов
/ 21 февраля 2020

Мой подход к этой проблеме отличается в зависимости от точки зрения.

С функциональной / бизнес-точки зрения я бы ответил, что вам нужно изменить последовательность вставок, не касаясь запроса, поскольку «игровое» предположение состоит в том, что за каждым in должен следовать хотя бы один out, поэтому последняя вставка для этого игрока должна быть out, а не in.

Если я пришлось исправить запрос с минимальным изменением, я бы просто добавил строку в ограничение подзапроса, указав, что operation без псевдонима должно быть равно псевдониму operation. Это правильный подзапрос для out. То же самое исправление для in, если это необходимо

left join orders ord_out 
  on cst.player = ord_out.player 
  and ord_out.operation = 'out'
  and ord_out.sequence = (
    select max(sequence) from orders
    where player = cst.player 
    and operation = ord_out.operation 
  )
...