SQL Server Get newest 5 entries for each joined row

ee7vknir  于 2023-04-19  发布在  其他
关注(0)|答案(3)|浏览(75)

I'm not sure whether this is complicated or I am lost about it.

Lets take this given table orders:
| ORDERNO | ORDER_DATE | MAT1 | Menge |
| ------------ | ------------ | ------------ | ------------ |
| 3912 | 09-09-1996 00:00:00 | 1 | 1020 |
| 3039 | 17-07-1995 00:00:00 | 1 | 30000 |
| 2985 | 27-06-1995 00:00:00 | 1 | 100000 |
| 2879 | 20-04-1995 00:00:00 | 1 | 100000 |
| 2735 | 06-02-1995 00:00:00 | 1 | 100000 |
| 3000 | 29-06-1995 00:00:00 | 2 | 30000 |
| 2986 | 27-06-1995 00:00:00 | 2 | 100000 |
| 2927 | 18-05-1995 00:00:00 | 2 | 100000 |
| 2794 | 08-03-1995 00:00:00 | 2 | 100000 |
| 2738 | 07-02-1995 00:00:00 | 2 | 100000 |
| 2652 | 06-01-1995 00:00:00 | 2 | 30000 |
| 3082 | 09-08-1995 00:00:00 | 3 | 30000 |
| 2717 | 31-01-1995 00:00:00 | 3 | 30000 |
| 806 | 28-10-1991 00:00:00 | 3 | 20000 |
| 693 | 02-07-1991 00:00:00 | 3 | 15000 |
| 29008 | 13-02-2023 09:02:02 | 4 | 324000 |
| 28871 | 07-12-2022 10:27:12 | 4 | 580000 |
| 28787 | 03-11-2022 13:46:42 | 4 | 300000 |
| 28726 | 12-10-2022 09:28:18 | 4 | 580000 |
| 28676 | 07-09-2022 11:33:53 | 4 | 580000 |
| 28661 | 31-08-2022 15:26:44 | 4 | 360000 |

and a table materials

nrpriceweight
10.97970.0740
20.00000.0000
30.09190.0740
40.00000.0850

How is it possible to get the newest 3 orders for each material on a SQL Server 2017 so it would be like this:

nrpriceweightORDERNOORDER_DATEMAT1Menge
10.97970.0740391209-09-1996 00:00:0011020
10.97970.0740303917-07-1995 00:00:00130000
10.97970.0740298527-06-1995 00:00:001100000
20.00000.0000300029-06-1995 00:00:00230000
20.00000.0000298627-06-1995 00:00:002100000
20.00000.0000292718-05-1995 00:00:002100000

I tried serveral variants.
What worked without a problem is displaying all orders of each material with a join or display the newest order for each material. But how to get the 3 newest orders for a material? SQL for newest order:

select
   *
from
    Material m
join (
    select
        o.ORDERNO,
        max(o.ORDER_DATE) as ORDER_DATE,
        o.MAT1, 
        o.Menge
    from
        orders o
    group by
        o.ORDERNO,
        o.MAT1,
        o.Menge) as t1 on
    t1.MAT1 = m.NR
ewm0tg9j

ewm0tg9j1#

I'd use a cross apply for that:

select * 
  from materials m
 cross apply (select top 3 * 
                from orders o 
               where o.MAT1 = m.NR
               order by order_date desc) o
order by m.nr, o.order_date desc;

cross apply allows you to filter a sub-query with values from the outer part.

UPD: you're asking for 5 newest record in the description and for 3 newest in the query's text. If 5 newest records is what you need, just update the top part of the query.

smdncfj3

smdncfj32#

You can use WINDOW FUNCTION(ROW_NUMBER) to solve the problem

SELECT 
         nr,    
         price, 
         weight,    
         ORDERNO,   
         ORDER_DATE,    
         MAT1,  
         Menge
    FROM (
            SELECT 
                     nr,    
                     price, 
                     weight,    
                     ORDERNO,   
                     ORDER_DATE,    
                     MAT1,  
                     Menge
                     ,ROW_NUMBER() over(partition by o.MAT1 order by ORDER_DATE desc)as rw
            FROM orders  o
            INNER JOIN  materials m on  o.MAT1 = m.NR
    )d
    WHERE d.rw<=5

You can create insert Base data with the following statements:

drop table if exists orders
drop table if exists materials

create table orders(
ORDERNO bigint, ORDER_DATE datetime,    MAT1 bigint,    Menge bigint)

create table materials( nr int, price   decimal(18,2),weight decimal(18,2))

insert into orders
(ORDERNO ,  ORDER_DATE ,    MAT1 ,  Menge )
      select 3912   ,    cast('09-09-1996 00:00:00'as datetime) ,1, 1020
union all select 3039   ,cast('07-17-1995 00:00:00'as datetime) ,1, 30000
union all select 2985   ,cast('06-27-1995 00:00:00'as datetime) ,1, 100000
union all select 2879   ,cast('04-20-1995 00:00:00'as datetime) ,1, 100000
union all select 2735   ,cast('02-06-1995 00:00:00'as datetime) ,1, 100000
union all select 3000   ,cast('06-29-1995 00:00:00'as datetime) ,2, 30000
union all select 2986   ,cast('06-27-1995 00:00:00'as datetime) ,2, 100000
union all select 2927   ,cast('05-18-1995 00:00:00'as datetime) ,2, 100000
union all select 2794   ,cast('03-08-1995 00:00:00'as datetime) ,2, 100000
union all select 2738   ,cast('02-07-1995 00:00:00'as datetime) ,2, 100000
union all select 2652   ,cast('01-06-1995 00:00:00'as datetime) ,2, 30000
union all select 3082   ,cast('08-09-1995 00:00:00'as datetime) ,3, 30000
union all select 2717   ,cast('01-31-1995 00:00:00'as datetime) ,3, 30000
union all select 806    ,cast('10-28-1991 00:00:00'as datetime) ,3, 20000
union all select 693    ,cast('07-02-1991 00:00:00'as datetime) ,3, 15000
union all select 29008  ,cast('02-13-2023 09:02:02'as datetime) ,4, 324000
union all select 28871  ,cast('12-07-2022 10:27:12'as datetime) ,4, 580000
union all select 28787  ,cast('11-03-2022 13:46:42'as datetime) ,4, 300000
union all select 28726  ,cast('10-12-2022 09:28:18'as datetime) ,4, 580000
union all select 28676  ,cast('09-07-2022 11:33:53'as datetime) ,4, 580000
union all select 28661  ,cast('08-31-2022 15:26:44'as datetime) ,4, 360000

insert into materials
(nr ,   price , weight )
        
        select 1,   0.9797, 0.0740
union all select 2, 0.0000, 0.0000
union all select 3, 0.0919, 0.0740
union all select 4, 0.0000, 0.0850
kr98yfug

kr98yfug3#

You can get a row number by mat1 entry in the orders using the row_number window function.

select *, ROW_NUMBER() over(partition by mat1 order by order_date desc) from orders

To extent it to your scenario, just filter by row number

with data as (
select *, ROW_NUMBER() over(partition by mat1 order by order_date desc) as 
[rowno] from orders)
select * from data join materials on data.MAT1=materials.nr
where rowno<=3

Using a window function uses "query cost" than a cross apply, although the cross apply is simpler to understand

相关问题