Finding closest expiration date - sql

I have a table with columns ItemCode , ItemCount, ExpiredDate in which the expired date of items are saved when stocks of items increase. I have a View which shows current stocks of my items and it's columns are ItemCode and ItemStock.
Table
ItemCode, ItemCount, ExpiredDate
1001 , 200 , 2010/01/01
1001 , 100 , 2020/02/01
1001 , 200 , 2021/03/01
1002 , 150 , 2020/03/01
View
ItemCode, ItemStock
1001 , 250
1002 , 40
1003 , 50
1004 , 60
I want a query that returns closest expired date according to items stock.
Result
ItemCode, ClosestExpirationDate
1001 , 2020/02/01
1002 , 2020/03/01
1003 , -----
1004 , -----

Try using the absolute difference of dates:
WITH cte AS (
SELECT t1.ItemCode, t2.ExpiredDate,
ROW_NUMBER() OVER (PARTITION BY t1.ItemCode
ORDER BY ABS(DATEDIFF(day, GETDATE(), COALESCE(t2.ExpiredDate, GETDATE())))) rn
FROM [yourView] t1
LEFT JOIN [yourTable] t2
ON t1.ItemCode = t2.ItemCode
)
SELECT ItemCode, ExpiredDate AS ClosestExpirationDate
FROM cte
WHERE rn = 1
ORDER BY ItemCode;
Demo
Note: I assumed you want expiry dates regardless of whether they occur in the past or future. If you only want future expiry dates, then the above query can be slightly modified.

Unfortunately, I cannot provide the exact query for you but I can tell you how I would solve that puzzle:
you need only your table. In the following lines, I will call that table 'Items'
numbering your items based on their expiration date with ROW_NUMBER(), PARTITION BY ItemCode ORDER BY ExpirationDate. MSDN: https://learn.microsoft.com/en-us/sql/t-sql/functions/row-number-transact-sql?view=sql-server-2017.
put the previous query into a CTE and filter out only the first rows. MSND: https://learn.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-2017
I cannot validate that right now but the solution is something like this:
;with numberedItems as (
select ItemCode, ExpirationDate,
row_number() over(partition by ItemCode order by ExpirationDate) as RowNo
from Items
)
select ItemCode, ExpirationDate
from numberedItems
where RowNo = 1
The benefit of this solution is the SQL server will read your table only once, you don't have to do two queries to get the single resultset.
I hope it helps.

Use outer apply. I think you want the next date in the future:
select v.*, t.ExpiredDate
from view v outer apply
(select top (1) t.*
from table t
where t.ExpiredDate > getdate()
order by t.ExpiredDate desc
) t;
If you want to include past dates as well, the structure is very similar:
select v.*, t.ExpiredDate
from view v outer apply
(select top (1) t.*
from table t
order by datediff(day, getdate(), t.ExpiredDate) asc
) t;

Try this:
Sample data:
declare #tbl1 table (ItemCode int, ItemCount int, ExpiredDate date);
insert into #tbl1 values
(1001, 200, '2010/01/01'),
(1001, 100, '2020/02/01'),
(1001, 200, '2021/03/01'),
(1002, 150, '2020/03/01');
declare #tbl2 table (ItemCode int, ItemStock int);
insert into #tbl2 values
(1001, 250),
(1002, 40),
(1003, 50),
(1004, 60);
T-SQL:
select t2.ItemCode, min(t1.ExpiredDate) ClosestExpirationDate from (
select ItemCode, ItemCount, ExpiredDate,
SUM(ItemCount) over (partition by ItemCode order by ExpiredDate) CumSum
from #tbl1
) t1 right join #tbl2 t2 on t1.ItemCode = t2.ItemCode and t1.CumSum > ItemStock
group by t2.ItemCode
For SQL Server versions earlier than 12:
select t2.ItemCode, min(t1.ExpiredDate) ClosestExpirationDate from (
select t1.ItemCode, t1.ItemCount, t1.ExpiredDate, SUM(t2.ItemCount) CumSum
from #tbl1 t1
join #tbl1 t2 on t1.ItemCode = t2.ItemCode and t1.ExpiredDate >= t2.ExpiredDate
group by t1.ItemCode, t1.ItemCount, t1.ExpiredDate
) t1 right join #tbl2 t2 on t1.ItemCode = t2.ItemCode and t1.CumSum > ItemStock
group by t2.ItemCode

Related

Get 'most recent' grouped record (with order by)

I have a query like the below
SELECT
t1.Supplier,
t2.Product,
FROM
t1
INNER JOIN
t2 ON t1.ProductCode = t2.ProductCode
GROUP BY
t1.Supplier, t2.Product
On table t1, there are also columns called 'Timestamp' and 'Price' - I want to get the most recent price, i.e. SELECT Price ORDER BY Timestamp DESC. Can I do this with any aggregate functions, or would it have to be a subquery?
One standard way of doing this is to use ROW_NUMBER() to create an additional column in the source data, allowing you to identify which row is "first" within each "partition".
WITH
supplier_sorted AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY supplier, ProductCode
ORDER BY timestamp DESC
)
AS recency_id
FROM
supplier
)
SELECT
s.Supplier,
p.Product,
COUNT(*)
FROM
supplier_sorted AS s
INNER JOIN
product AS p
ON s.ProductCode = p.ProductCode
WHERE
s.recency_id = 1
GROUP BY
s.Supplier,
p.Product
You can use cross apply:
SELECT t2.*, t1.*
FROM t2 CROSS APPLY
(SELECT TOP (1) t1.*
FROM t1
WHERE t1.ProductCode = t2.ProductCode
ORDER BY t1.TimeStamp DESC
) t1;
So, GROUP BY is not necessary.
Can use the row_number() over the partiton of ProductCode and Supplier to by using Timestamp Order by desc to get the latest record by based on the partition. Then you can use in the same query without aggregation to get the desired result.
It is good to use Windows functions rather than Group by for these questions.
SELECT
A.Supplier
,A.Product
,A.Price
FROM
(
SELECT
t1.Supplier,
t2.Product,
T1.Price,
ROW_NUMBER () OVER ( PARTITION BY t1.Supplier,t2.Product ORDER BY T1.[Timestamp] DESC ) AS row_num
FROM t1
INNER JOIN t2
ON t1.ProductCode = t2.ProductCode
) AS A WHERE A.row_num = 1
Tested using below added data.
CREATE TABLE t1
( Supplier varchar(100)
,ProductCode int
, Price Decimal (10,2)
, [TimeStamp] datetime
)
CREATE TABLE t2
(
ProductCode int
,Product varchar(100)
)
insert into t1 values ('A', 1, 100.00, GetDate())
insert into t1 values ('A', 1, 80.00, GetDate())
insert into t1 values ('b', 2, 190.00, GetDate())
insert into t1 values ('b', 2, 500.00, GetDate())
insert into t2 values (1, 'Pro1')
insert into t2 values (2, 'Pro2')
insert into t2 values (3, 'Pro3')

Find most recent record by date

This is my original data (anonymised):
id usage verified date
1 4000 Y 2015-03-20
2 5000 N 2015-06-20
3 6000 N 2015-07-20
4 7000 Y 2016-09-20
Original query:
SELECT
me.usage,
mes.verified,
mes.date
FROM
Table1 me,
Table2 mes,
Table3 m,
Table4 mp
WHERE
me.theFk=mes.id
AND mes.theFk=m.id
AND m.theFk=mp.id
How would I go about selecting the most recent verified and non-verified?
So I would be left with:
id usage verified date
1 6000 N 2015-07-20
2 7000 Y 2016-09-20
I am using Microsoft SQL Server 2012.
First, do not use implicit joins. This was discontinued more than 10 years ago.
Second, embrace the power of the CTE, the in clause and row_number:
with CTE as
(
select
me.usage,
mes.verified,
mes.date,
row_number() over (partition by Verified order by Date desc) as CTEOrd
from Table1 me
inner join Table2 mes
on me.theFK = mes.id
where mes.theFK in
(
select m.id
from Table3 m
inner join Table4 mp
on mp.id = m.theFK
)
)
select CTE.*
from CTE
where CTEOrd = 1
You can select the TOP 1 ordered by date for verified=N, union'd with the TOP 1 ordered by date for verified=Y.
Or in pseudo SQL:
SELECT TOP 1 ...fields ...
FROM ...tables/joins...
WHERE Verified = 'N'
ORDER BY Date DESC
UNION
SELECT TOP 1 ...fields ...
FROM ...tables/joins...
WHERE Verified = 'Y'
ORDER BY Date DESC
drop table #stack2
CREATE TABLE #stack2
([id] int, [usage] int, [verified] varchar(1), [date] datetime)
;
INSERT INTO #stack2
([id], [usage], [verified], [date])
VALUES
(1, 4000, 'Y', '2015-03-20 00:00:00'),
(2, 5000, 'N', '2015-06-20 00:00:00'),
(3, 6000, 'N', '2015-07-20 00:00:00'),
(4, 7000, 'Y', '2016-09-20 00:00:00')
;
;with cte as (select verified,max(date) d from #stack2 group by verified)
select row_number() over( order by s2.[verified]),s2.[usage], s2.[verified], s2.[date] from #stack2 s2 join cte c on c.verified=s2.verified and c.d=s2.date
As per the data shown i had written the query.
for your scenario this will be use full
WITH cte1
AS (SELECT me.usage,
mes.verified,
mes.date
FROM Table1 me,
Table2 mes,
Table3 m,
Table4 mp
WHERE me.theFk = mes.id
AND mes.theFk = m.id
AND m.theFk = mp.id),
cte
AS (SELECT verified,
Max(date) d
FROM cte1
GROUP BY verified)
SELECT Row_number()
OVER(
ORDER BY s2.[verified]),
s2.[usage],
s2.[verified],
s2.[date]
FROM cte1 s2
JOIN cte c
ON c.verified = s2.verified
AND c.d = s2.date
You can as the below Without join.
-- Mock data
DECLARE #Tbl TABLE (id INT, usage INT, verified CHAR(1), date DATETIME)
INSERT INTO #Tbl
VALUES
(1, 4000 ,'Y', '2015-03-20'),
(2, 5000 ,'N', '2015-06-20'),
(3, 6000 ,'N', '2015-07-20'),
(4, 7000 ,'Y', '2016-09-20')
SELECT
A.id ,
A.usage ,
A.verified ,
A.MaxDate
FROM
(
SELECT
id ,
usage ,
verified ,
date,
MAX(date) OVER (PARTITION BY verified) MaxDate
FROM
#Tbl
) A
WHERE
A.date = A.MaxDate
Result:
id usage verified MaxDate
----------- ----------- -------- ----------
3 6000 N 2015-07-20
4 7000 Y 2016-09-20
CREATE TABLE #Table ( ID INT ,usage INT, verified VARCHAR(10), _date DATE)
INSERT INTO #Table ( ID , usage , verified , _date)
SELECT 1,4000 , 'Y','2015-03-20' UNION ALL
SELECT 2, 5000 , 'N' ,'2015-06-20' UNION ALL
SELECT 3, 6000 , 'N' ,'2015-07-20' UNION ALL
SELECT 4, 7000 , 'Y' ,'2016-09-20'
SELECT ROW_NUMBER() OVER(ORDER BY usage) ID,usage , A.verified , A._date
FROM #Table
JOIN
(
SELECT verified , MAX(_date) _date
FROM #Table
GROUP BY verified
) A ON #Table._date = A._date

SQL Server Query_ToDelete_Records

Can anyone help me with the script which will select the latest date from the column dtUpdated_On if date is greater than last date and ID is less than last ID. I know the question is not clear but try to understand. In this example I want to delete ID 1003 (I know in this example we will say... Delete from tableName where ID=1003)
ID dtUpdated_On
-----------------------------------
1001 2009-12-11 20:08:16.857
1002 2012-03-31 02:35:16.650
1003 2012-09-01 00:00:00.000
1004 2012-03-31 02:35:16.650
Assuming that by "last" you mean the row with the highest id, then you can do:
select t.*
from t join
(select top 1 dtUpdated_On
from t
order by id desc
) last
on t.dtUpdated_On > last.dtUpdated_On;
You can also express this in the where clause, which is simpler for deletion (in my opinion):
delete t
where t.dtUpdated_On > (select top 1 t2.dtUpdated_On
from t
order by id desc
)
Try this,
DECLARE #MyTable TABLE(ID INT, dtUpdated_On DATETIME)
INSERT INTO #MyTable
VALUES (1001, '2009-12-11 20:08:16.857')
,(1002, '2012-03-31 02:35:16.650')
,(1003, '2012-09-01 00:00:00.000')
,(1004, '2012-03-31 02:35:16.650')
;WITH LatestDate AS
(
SELECT TOP 1 ID, dtUpdated_On
FROM #MyTable
ORDER BY dtUpdated_On DESC, ID DESC
),
LastestID AS
(
SELECT c.ID, c.dtUpdated_On, t.ID AS LatestID, t.dtUpdated_On AS LatestIDDate
FROM #MyTable t
INNER JOIN LatestDate c ON t.dtUpdated_On < c.dtUpdated_On
AND t.ID > c.ID
)
DELETE t
FROM #MyTable t
INNER JOIN LastestID c ON c.ID = t.ID
SELECT *
FROM #MyTable t

how to use SQL group to filter rows with maximum date value

I have the following table
CREATE TABLE Test
(`Id` int, `value` varchar(20), `adate` varchar(20))
;
INSERT INTO Test
(`Id`, `value`, `adate`)
VALUES
(1, 100, '2014-01-01'),
(1, 200, '2014-01-02'),
(1, 300, '2014-01-03'),
(2, 200, '2014-01-01'),
(2, 400, '2014-01-02'),
(2, 30 , '2014-01-04'),
(3, 800, '2014-01-01'),
(3, 300, '2014-01-02'),
(3, 60 , '2014-01-04')
;
I want to achieve the result which selects only Id having max value of date. ie
Id ,value ,adate
1, 300,'2014-01-03'
2, 30 ,'2014-01-04'
3, 60 ,'2014-01-04'
how can I achieve this using group by? I have done as follows but it is not working.
Select Id,value,adate
from Test
group by Id,value,adate
having adate = MAX(adate)
Can someone help with the query?
Select the maximum dates for each id.
select id, max(adate) max_date
from test
group by id
Join on that to get the rest of the columns.
select t1.*
from test t1
inner join (select id, max(adate) max_date
from test
group by id) t2
on t1.id = t2.id and t1.adate = t2.max_date;
Please try:
select
*
from
tbl a
where
a.adate=(select MAX(adate) from tbl b where b.Id=a.Id)
If you are using a DBMS that has analytical functions you can use ROW_NUMBER:
SELECT Id, Value, ADate
FROM ( SELECT ID,
Value,
ADate,
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Adate DESC) AS RowNum
FROM Test
) AS T
WHERE RowNum = 1;
Otherwise you will need to use a join to the aggregated max date by Id to filter the results from Test to only those where the date matches the maximum date for that Id
SELECT Test.Id, Test.Value, Test.ADate
FROM Test
INNER JOIN
( SELECT ID, MAX(ADate) AS ADate
FROM Test
GROUP BY ID
) AS MaxT
ON MaxT.ID = Test.ID
AND MaxT.ADate = Test.ADate;
I would try something like this
Select t1.Id, t1.value, t1.adate
from Test as t1
where t1.adate = (select max(t2.adate)
from Test as t2
where t2.id = t1.id)

Next/previous record based on current

I have a table which is not sorted by any of column. Is there any way to select next/previous record if I know only Id of current? (I'm using mssql)
Id Label Date
---------------------
1 label1 2011-01-10
7 label2 2011-01-15 -- how to get previous?
5 label3 2011-01-12 -- I know id of this record
10 label10 2011-01-25 -- how to get next?
12 label8 2011-01-13
2 label5 2011-01-29
Thanks in advance!
try this:
VALUES (1, 'label1', '2011-01-10'), (7, 'label2', '2011-01-15'),
(5, 'label3', '2011-01-12'), (10, 'label10', '2011-01-25'),
(12, 'label8', '2011-01-13'), (2, 'label5', '2011-01-29')
select * from table007;
Declare #inptID int=12;
;WITH CTE
as
(
select *, ROW_NUMBER() over (order by (select 0)) as rn
from table007
)
select *
from CTE
where rn in( select rn-1 from CTE where id = #inptID)
union all
select * from CTE where rn in(select rn + 1 from CTE where id = #inptID);
SQL Fiddle Demo
DEMO
If it is not sorted by any column, there is no definitive next or previous record. Data in SQL Server has no order, other than that specified by an ORDER BY clause.
If you really want the previous from the list you enclosed, here is a way.
declare #t table(Id int, Label varchar(10), Date date, s int identity(1,1))
insert #t (id, label, date)
values(1,'label1','2011-01-10'),(7,'label2','2011-01-15'),
(5,'label3','2011-01-12'),(10,'label10','2011-01-25'),
(12,'label8','2011-01-13'),(2,'label5','2011-01-29')
--select the data with a self join
select t1.id as previous_id, t2.id, t2.Label, t2.Date, t3.id, t3.id as next_id
from #t t1
right join
#t t2 on t1.s + 1 = t2.s
left join
#t t3 on t2.s = t3.s - 1