SQL Query to get 2nd most recent results with multiple columns - sql

I am trying to obtain the 2nd most recent results for all distinct part_id's(based off of order_date) to go into a report I am working on making to compare it to the most recent results.
The commented sections are from another approach I was trying but unsuccessful with.
Any help is greatly appreciated!
(Side note: I am new to posting on SO, and I apologize in advance if this is answered elsewhere, but I was unable to find anything that pertained to this issue)
I am using the following query:
SELECT
PURCHASE_ORDER.ORDER_DATE
, PURC_ORDER_LINE.PART_ID
, PURCHASE_ORDER.VENDOR_ID
, PURC_ORDER_LINE.LINE_STATUS
, PURC_ORDER_LINE.ORDER_QTY
, PURC_ORDER_LINE.UNIT_PRICE
--, ROW_NUMBER() over (ORDER BY PURCHASE_ORDER.ORDER_DATE DESC)AS ROW
, CAST (PURC_ORDER_LINE.ORDER_QTY * PURC_ORDER_LINE.UNIT_PRICE AS VARCHAR) AS TOTAL_COST
FROM
PURCHASE_ORDER
INNER JOIN
PURC_ORDER_LINE
ON
PURCHASE_ORDER.ID = PURC_ORDER_LINE.PURC_ORDER_ID
WHERE PURCHASE_ORDER.ORDER_DATE < (SELECT MAX(ORDER_DATE) FROM PURCHASE_ORDER) AND PURC_ORDER_LINE.PART_ID = 'XXXX'
ORDER BY ORDER_dATE DESC
--WHERE PURC_ORDER_LINE.PART_ID = 'XXXX' and PURCHASE_ORDER.ORDER_DATE = (SELECT MAX(ORDER_DATE) FROM PURCHASE_ORDER WHERE ORDER_DATE < (SELECT MAX(ORDER_DATE) FROM PURCHASE_ORDER))
EDIT 5/28 LATE NIGHT:
Lets say below is the data set I need the 2nd result from each part_id (2nd based off of ORDER_DATE DESC)
+-------------+---------+-----------+
| ORDER_DATE | PART_ID | VENDOR_ID |
+-------------+---------+-----------+
| 2020-05-29 | XXXX | CVVB |
| 2020-05-27 | XXXX | CVVB |
| 2020-05-28 | XXXX | CVVA |
| 2020-05-28 | YYYY | GGNB |
| 2020-04-12 | YYYY | GGNB |
| 2020-02-08 | YYYY | GGNB |
| 2020-05-28 | ZZZZ | LLNB |
| 2019-10-28 | ZZZZ | LLNB |
| 2019-05-27 | ZZZZ | OKIJ |
+-------------+---------+-----------+
I am looking to receive the following output (for more than 3 different part id's):
+------------+---------+-----------+
| ORDER_DATE | PART_ID | VENDOR_ID |
+------------+---------+-----------+
| 2020-05-28 | XXXX | CVVA |
| 2020-04-12 | YYYY | GGNB |
| 2019-10-28 | ZZZZ | LLNB |
+------------+---------+-----------+
There are also additional columns in the query but formatting them as a table would have taken much longer. I have left off a few of the columns on the examples.
ANOTHER EDIT
I am not sure if this information helps, but I am trying to compare the most recent results to the previous results to show pricing and vendor differences. We are compiling the data into Report Builder; My approach here was to create 2 separate datasets one with the most recent and the other with the 2nd most recent and combine the data from the datasets in Report Builder. If there is an easier approach and I am heading in the wrong direction please let me know!
Example:
+------------+---------+-----------+-------------+----------+------------+
| ORDER_DATE | PART_ID | VENDOR_ID | Porder_Date | Ppart_ID | pVendor_id |
+------------+---------+-----------+-------------+----------+------------+
| 2020-05-29 | XXXX | CVVB | 2020-05-28 | XXXX | CVVA |
| 2020-05-28 | YYYY | GGNB | 2020-04-12 | YYYY | GGNB |
| 2020-05-28 | ZZZZ | LLNB | 2019-10-28 | ZZZZ | LLNB |
+------------+---------+-----------+-------------+----------+------------+
EDIT THE NEXT MORNING
Thanks everyone for all the help! After Harry posted his solution I went ahead and made some tiny edits to get the columns I needed added on. I swapped his union portion with the original select statement. Everything here seems to be exactly what I am looking for!
Code:
;
WITH mycte AS
(
SELECT
PURCHASE_ORDER.ORDER_DATE
, PURC_ORDER_LINE.PART_ID
, PURCHASE_ORDER.VENDOR_ID
, PURC_ORDER_LINE.LINE_STATUS
, PURC_ORDER_LINE.ORDER_QTY
, PURC_ORDER_LINE.UNIT_PRICE
, CAST (PURC_ORDER_LINE.ORDER_QTY * PURC_ORDER_LINE.UNIT_PRICE AS VARCHAR) AS TOTAL_COST
FROM
PURCHASE_ORDER
INNER JOIN
PURC_ORDER_LINE
ON
PURCHASE_ORDER.ID = PURC_ORDER_LINE.PURC_ORDER_ID
)
, mycte2 AS
(
SELECT
CONVERT(DATE,order_date) AS order_date
, part_id
, vendor_id
, order_qty
, unit_price
, total_cost
, ROW_NUMBER() over(
PARTITION BY part_id
ORDER BY
CONVERT(DATE,order_date) DESC) AS row_num
FROM
mycte
)
SELECT
mycte2.order_date
, mycte2.part_id
, mycte2.vendor_id
, mycte2.order_qty
, mycte2.unit_price
, mycte2.total_cost
, previous.order_date porder_date
, previous.part_id ppart_id
, previous.vendor_id pvendor_id
, previous.order_qty poqrder_qty
, previous.unit_price punit_price
, previous.total_cost ptotal_cost
FROM
mycte2
LEFT JOIN
mycte2 previous
ON
previous.row_num = mycte2.row_num +1
AND mycte2.part_id = previous.part_id
WHERE
mycte2.row_num = 1

Based on the data you have provided, you can do this with a cte and row number function.
Note - it always helps to show the whole picture rather than just ask for the part you want help with (usually).. as it is easier to answer if we can understand the entire issue!
See code below
;with mycte as (
select
'2020-05-29' as order_date , 'XXXX' as part_id , 'CVVB' as vendor_id
union all select
'2020-05-27' , 'XXXX' , 'CVVB'
union all select
'2020-05-28' , 'XXXX' , 'CVVA'
union all select
'2020-05-28' , 'YYYY' , 'GGNB'
union all select
'2020-04-12' , 'YYYY' , 'GGNB'
union all select
'2020-02-08' , 'YYYY' , 'GGNB'
union all select
'2020-05-28' , 'ZZZZ' , 'LLNB'
union all select
'2019-10-28' , 'ZZZZ' , 'LLNB'
union all select
'2019-05-27' , 'ZZZZ' , 'OKIJ'
)
, mycte2 as (
Select
convert(date,order_date) as order_date
,part_id
,vendor_id
,ROW_NUMBER() over( partition by part_id order by convert(date,order_date) desc) as row_num
from mycte
)
Select
mycte2.order_date
,mycte2.part_id
,mycte2.vendor_id
,previous.order_date porder_date
,previous.part_id ppart_id
,previous.vendor_id pvendor_id
from mycte2
left join mycte2 previous
on previous.row_num = mycte2.row_num +1
and mycte2.part_id = previous.part_id
where mycte2.row_num = 1
result

I think something like this would work to get the second most recent order :
;WITH cteOrders AS (
SELECT ROW_NUMBER() OVER (ORDER BY Order_Date DESC) AS row_num,
PURCHASE_ORDER.ORDER_DATE
, PURC_ORDER_LINE.PART_ID
, PURCHASE_ORDER.VENDOR_ID
, PURC_ORDER_LINE.LINE_STATUS
, PURC_ORDER_LINE.ORDER_QTY
, PURC_ORDER_LINE.UNIT_PRICE
FROM PURCHASE_ORDER
INNER JOIN PURC_ORDER_LINE ON PURCHASE_ORDER.ID = PURC_ORDER_LINE.PURC_ORDER_ID
WHERE PURCHASE_ORDER.ORDER_DATE < (SELECT MAX(ORDER_DATE) FROM PURCHASE_ORDER) AND PURC_ORDER_LINE.PART_ID = 'XXXX'
)
SELECT * FROM cteOrders WHERE row_num = 2

Related

Joining two tables in SQL to get the SUM between two dates

I'm new to SQL and this website so apologies if anything is unclear.
Basically, I got two separate tables:
Table A:
CustomerID | PromoStart | PromoEnd
1 | 2020-05-01 | 2020-05-30
2 | 2020-06-01 | 2020-07-30
3 | 2020-07-01 | 2020-10-15
Table B:
CustomerID | Date | Payment |
1 | 2020-02-15 | 5000 |
1 | 2020-05-04 | 200 |
1 | 2020-05-28 | 100 |
1 | 2020-06-05 | 1000 |
2 | 2020-06-10 | 20 |
2 | 2020-07-25 | 500 |
2 | 2020-08-02 | 1000 |
3 | 2020-09-05 | 580 |
3 | 2020-12-01 | 20 |
What I want is to get the sum of all payments that fall between PromoStart and PromoEnd for each customer.
so the desired result would be :
CustomerID | TotalPayments
1 | 300
2 | 520
3 | 580
I guess this would involve an inner (left?) join and a where clause however I just can't figure it out.
A LATERAL join would do it:
SELECT a.customer_id, b.total_payments
FROM table_a a
LEFT JOIN LATERAL (
SELECT sum(payment) AS total_payments
FROM table_b
WHERE customer_id = a.customer_id
AND date BETWEEN a.promo_start AND a.promo_end
) b ON true;
This assumes inclusive lower and upper bounds, and that you want to include all rows from table_a, even without any payments in table_b.
You can use a correlated subquery or join with aggregation. The correlated subquery looks like:
select a.*,
(select sum(b.payment)
from b
where b.customerid = a.customerid and
b.date >= a.promostart and
b.date <= a.promoend
) as totalpayments
from a;
You don't mention your database, but this can take advantage of an index on b(customerid, date, payment). By avoiding the outer aggregation, this would often have better performance than an alternative using group by.
I hope I didn't overlook something important but it seems to me simple join on range matching condition should be sufficient:
with a (CustomerID , PromoStart , PromoEnd) as (values
(1 , date '2020-05-01' , date '2020-05-30'),
(2 , date '2020-06-01' , date '2020-07-30'),
(3 , date '2020-07-01' , date '2020-10-15')
), b (CustomerID , d , Payment ) as (values
(1 , date '2020-02-15' , 5000 ),
(1 , date '2020-05-04' , 200 ),
(1 , date '2020-05-28' , 100 ),
(1 , date '2020-06-05' , 1000 ),
(2 , date '2020-06-10' , 20 ),
(2 , date '2020-07-25' , 500 ),
(2 , date '2020-08-02' , 1000 ),
(3 , date '2020-09-05' , 580 ),
(3 , date '2020-12-01' , 20 )
)
select a.CustomerID, sum(b.Payment)
from a
join b on a.CustomerID = b.CustomerID and b.d between a.PromoStart and PromoEnd
group by a.CustomerID
Db fiddle here.

how to compare two dates in same column in SQL

I have to compare tow dates that they are in one column of the table, I need this comparing to find the date before and after the specific date that I need, also I have to show them in the 3 different columns
I wrote this code but it's totally wrong:
CREATE VIEW
AS
SELECT (CASE
WHEN T1.BuyDate > T2.BuyDate THEN T1.BuyDate END)
AS PreviousBuyDate, T1.ItemName, T1.BuyDate,
(CASE
WHEN T1.BuyDate > T2.BuyDate THEN T1.BuyDate END)
AS NextDate
FROM FoodSara_tbl T1 , FoodSara_tbl T2
GO
input:
|ItemName | BuyDate | ItemOrigin |
|---------|---------|------------|
| cake |2020-10-2| UK |
| coca |2020-5-2 | US |
| cake |2019-10-6| UK |
| coca |2020-12-2| US |
Output:
|PreviousDate | ItemName | BuyDate |NextDate |
|-------------|----------|---------|---------|
| NULL |cake |2019-10-6|2020-10-2|
| NULL |coca |2020-5-2 |2020-12-2|
|2019-10-6 |cake |2020-10-2| NULL |
| 2020-5-2 |coca |2020-12-2| NULL |
PS: I have to make a date be in order.
Try this with LAG function:
select LAG(BuyDate,1) OVER (PARTITION BY ItemName ORDER BY BuyDate asc) previous_date
, ItemName
, BuyDate
, LAG(BuyDate,1) OVER (PARTITION BY ItemName ORDER BY BuyDate desc) next_date
from FoodSara_tbl
See the final result: sqlfiddle
OR
Use LAG and LEAD function:
select LAG(BuyDate,1) OVER (PARTITION BY ItemName order by BuyDate) previous_date
, ItemName
, BuyDate
, LEAD(BuyDate,1) OVER (PARTITION BY ItemName order by BuyDate) next_date
from FoodSara_tbl
See the final result; sqlfiddle

Cumulated sum based on condition in other column

I would like to create a view based on data in following structure:
CREATE TABLE my_table (
date date,
daily_cumulative_precip float4
);
INSERT INTO my_table (date, daily_cumulative_precip)
VALUES
('2016-07-28', 3.048)
, ('2016-08-04', 2.286)
, ('2016-08-11', 5.334)
, ('2016-08-12', 0.254)
, ('2016-08-13', 2.794)
, ('2016-08-14', 2.286)
, ('2016-08-15', 3.302)
, ('2016-08-17', 3.81)
, ('2016-08-19', 15.746)
, ('2016-08-20', 46.739998);
I would like to accumulate the precipitation for consecutive days only.
Below is the desired result for a different test case - except that days without rain should be omitted:
I have tried window functions with OVER(PARTITION BY date, rain_on_day) but they do not yield the desired result.
How could I solve this?
SELECT date
, dense_rank() OVER (ORDER BY grp) AS consecutive_group_nr -- optional
, daily_cumulative_precip
, sum(daily_cumulative_precip) OVER (PARTITION BY grp ORDER BY date) AS cum_precipitation_mm
FROM (
SELECT date, t.daily_cumulative_precip
, row_number() OVER (ORDER BY date) - t.rn AS grp
FROM (
SELECT generate_series (min(date), max(date), interval '1 day')::date AS date
FROM my_table
) d
LEFT JOIN (SELECT *, row_number() OVER (ORDER BY date) AS rn FROM my_table) t USING (date)
) x
WHERE daily_cumulative_precip > 0
ORDER BY date;
db<>fiddle here
Returns all rainy days with cumulative sums for consecutive days (and a running group number).
Basics:
Select longest continuous sequence
Here's a way to calculate cumulative precipitation without having to explicitly enumerate all dates:
SELECT date, daily_cumulative_precip, sum(daily_cumulative_precip) over (partition by group_num order by date) as cum_precip
FROM
(SELECT date, daily_cumulative_precip, sum(start_group) over (order by date) as group_num
FROM
(SELECT date, daily_cumulative_precip, CASE WHEN (date != prev_date + 1) THEN 1 ELSE 0 END as start_group
FROM
(SELECT date, daily_cumulative_precip, lag(date, 1, '-infinity'::date) over (order by date) as prev_date
FROM my_table) t1) t2) t3
yields
| date | daily_cumulative_precip | cum_precip |
|------------+-------------------------+------------|
| 2016-07-28 | 3.048 | 3.048 |
| 2016-08-04 | 2.286 | 2.286 |
| 2016-08-11 | 5.334 | 5.334 |
| 2016-08-12 | 0.254 | 5.588 |
| 2016-08-13 | 2.794 | 8.382 |
| 2016-08-14 | 2.286 | 10.668 |
| 2016-08-15 | 3.302 | 13.97 |
| 2016-08-17 | 3.81 | 3.81 |
| 2016-08-19 | 15.746 | 15.746 |
| 2016-08-20 | 46.74 | 62.486 |

SQL Server Query to find records with aggregate funct on one column but multiple columns in select clause

Here is the minimized version of the Customer table. There can be customers having same account number mapped to different Group . I am looking to find out customer numbers which are mapped to more than one group. As I was using sybase my query below was working fine. Same query does not work in SQL Server.
Can I get both custAccnt and corresponding custId in one query as below.
select DISTINCT lt.custAccnt, lt.custId from VAL_CUSTOMERS lt
where lt.eligible = 'Y' group by lt.custAccnt
having count(distinct lt.custId) > 1
+----------+-----------+---------+----------+
| custName | custAccnt | custId | eligible |
+----------+-----------+---------+----------+
| Joe | AB1VU1235 | 43553 | Y |
| Joe | AB1VU1235 | 525577 | Y |
| Lucy | CDNMY4568 | 332875 | Y |
| Lucy | CDNMY4568 | 211574 | Y |
| Lucy | CDNMY4568 | 211345 | Y |
| Manie | TZMM7S009 | 123890 | Y |
| Tom | YFDU1235 | 1928347 | Y |
| Tom | YFDU1235 | 204183 | Y |
| Chef | TNOTE6573 | 734265 | Y |
+----------+-----------+---------+----------+
Result :-
+-----------+---------+
| AB1VU1235 | 43553 |
| AB1VU1235 | 525577 |
| CDNMY4568 | 332875 |
| CDNMY4568 | 211574 |
| CDNMY4568 | 211345 |
| YFDU1235 | 1928347 |
| YFDU1235 | 204183 |
+-----------+---------+
There are many ways to tackle this. Here are a couple of them that should work.
select lt.custAccnt
, lt.custId
from VAL_CUSTOMERS lt
cross apply
(
select c.custAccnt
from VAL_CUSTOMERS c
where c.custAccnt = lt.custAccnt
group by c.custAccnt
having count(*) > 1
) x
where lt.eligible = 'Y'
select lt.custAccnt
, lt.custId
from VAL_CUSTOMERS lt
where lt.eligible = 'Y'
AND lt.custAccnt IN
(
select c.custAccnt
from VAL_CUSTOMERS c
group by c.custAccnt
having count(*) > 1
)
In case of duplicates custAccnt and custId in the table, #Sean query won't work.
WITH cte AS(SELECT *
, COUNT (custId) OVER (PARTITION BY custAccnt) AS CntcustId
, ROW_NUMBER () OVER (PARTITION BY custAccnt, custId ORDER BY custName) AS Rownum
FROM VAL_CUSTOMERS
WHERE eligible = 'Y'
)
SELECT custAccnt, custId
FROM cte
WHERE CntcustId>1
AND Rownum = 1;
Using row number to eliminate the duplicates.
I think this might work...
"...customer numbers which are mapped to more than one group..." , <-- group is custAcct?
select t.custAccnt, t.custId
from VAL_CUSTOMERS t
where (Select count(distinct custAccnt )
from VAL_CUSTOMERS
Where custId = t.custId) > 1
The statement "...customer numbers which are mapped to more than one group..." does not say anything about "eligibility", so I did not mention it. If you really meant to say:
"...eligible customer numbers which are mapped to more than one group...", then try this:
select t.custAccnt, t.custId
from VAL_CUSTOMERS t
where eligible = 'Y'
and (Select count(distinct custAccnt )
from VAL_CUSTOMERS
Where custId = t.custId) > 1
or, this might be faster... it answers a slightly different, but, (I think) equivalent question,
"find ...eligible customer numbers where there is another row for the same customer number mapped to a different custAccnt ..."
select t.custAccnt, t.custId
from VAL_CUSTOMERS t
where eligible = 'Y'
and exists
(Select * from VAL_CUSTOMERS
Where custId = t.custId
and custAccnt != t.custAccnt )
;WITH cte1
( custName , custAccnt , custId , eligible )
As
(
SELECT 'Joe' ,'AB1VU1235' , 43553 , 'Y' UNION ALL
SELECT 'Joe' ,'AB1VU1235' , 525577 , 'Y' UNION ALL
SELECT 'Lucy' ,'CDNMY4568' , 332875 , 'Y' UNION ALL
SELECT 'Lucy' ,'CDNMY4568' , 211574 , 'Y' UNION ALL
SELECT 'Lucy' , 'CDNMY4568' , 211345 , 'Y' UNION ALL
SELECT 'Manie' ,'TZMM7S009' , 123890 , 'Y' UNION ALL
SELECT 'Tom' ,'YFDU1235' , 1928347 , 'Y' UNION ALL
SELECT 'Tom' ,'YFDU1235' , 204183 , 'Y' UNION ALL
SELECT 'Chef' ,'TNOTE6573' , 734265 , 'Y'
)
,cte2 AS (
SELECT custName
,custAccnt
,count(custName) cnt
FROM cte1
GROUP BY custName,custAccnt
)
,cte3 AS (
SELECT custName
,cnt
FROM cte2 WHERE cnt <> 1
)
SELECT custAccnt
,custId
FROM cte1
WHERE custName IN (
SELECT custName
FROM cte3
)

How to create a condition for this case?

Sample Table:
Id |Acc_Code|Description |Balance | Acclevel| Acctype| Exttype|
--- -------- ----------------- |-------- |-------- | -------| -------|
1 |SA |Sales | 0.00 | 1 | SA | |
2 |CS |Cost of Sales | 0.00 | 1 | CS | |
3 |5000/001|Revenue | 94.34 | 2 | SA | |
4 |5000/090|Sales(Local) | 62.83 | 2 | SA | |
5 |7000/000|Manufacturing Acc |-250.80 | 2 | CS | MA |
6 |7000/200|Manufacturing Acc | 178.00 | 2 | CS | |
This is a sample data of a temporary table which would be used to be inserted into another temporary table that would calculate the data for Profit and Loss Statement (For Manufacturing related Accounts only).
In this case, the acc_code for Manufacturing accounts start from 7000/000 and separated/partitioned for each following Exttype.
Eg: We start from the exttype of MA and based on its acclevel (could be 2 or more) until the next exttype.
The idea is we get the manufacturing accounts by SELECT FROM tmp_acc_list WHERE acc_code BETWEEN #start_acc_code (7000/000 in this case) AND #end_acc_code (the data before the next exttype)
I don't know what the exttype is, I'm still learning the tables.
How do we create the #end_acc_code part out from this sample table?
So here is a all in one script.
I created Your table for test:
create table #tmp_acc_list(
Id numeric,
Acc_Code nvarchar(100),
Acclevel numeric,
Acctype nvarchar(100),
Exttype nvarchar(100));
GO
insert into #tmp_acc_list(Id, Acc_Code, Acclevel, Acctype, Exttype)
select 1 , 'SA', 1,'SA', null union all
select 2 , 'CS', 1,'CS', null union all
select 3 , '5000/001', 2,'SA', null union all
select 4 , '5000/090', 2,'SA', null union all
select 5 , '7000/000', 2,'CS', 'MA' union all
select 6 , '7000/200', 2,'CS', null
;
Then comes the query:
with OrderedTable as -- to order the table is Id is not an order
(
select
t.*, ROW_NUMBER() over (
order by id asc --use any ordering You need here
)
as RowNum
from
#tmp_acc_list as t
),
MarkedTable as -- mark with common number
(
select
t.*,
Max(case when t.Exttype is null then null else t.RowNum end)
over (order by t.RowNum) as GroupRownum
from OrderedTable as t
),
GroupedTable as -- add group Exttype
(
select
t.Id, t.Acc_Code, t.Acclevel, t.Acctype, t.Exttype,
max(t.Exttype) over (partition by t.GroupRownum) as GroupExttype
from MarkedTable as t
)
select * from GroupedTable where GroupExttype = 'MA'
Is this what You need?
select *
from
(
select Id, Acc_Code
from tmp_acc_list
where Acc_Code = '7000/000'
) s
cross join tmp_acc_list a
cross apply
(
select top 1 x.Id, x.Acc_Code
from tmp_acc_list x
where x.Id >= a.Id
and x.AccLevel = a.AccLevel
and x.Acctype = a.Acctype
and x.Exttype = ''
order by Id desc
) e
where a.Id between s.Id and e.Id