Finding Max of dates in SQL Query - sql

CType
Material
CoCd
Plnt
SOrg
SoldTo
ShipTo
Customer
Vendor
SerProv
POrg
Year
per
UoM
NSapRel
Amount
Crcy
ChangedOn
Time
Changedby
SAPdate
SAPTime
SAPUser
PB00
775515
100
GA
7500000678
PO04
2022
100
CAS
9,299.36
BRL
15.02.2022
13:55:16
User1
15.02.2022
13:56:15
User1
PB00
775515
100
GA
7500000678
PO04
2022
100
CAS
5,907.42
BRL
28.01.2022
18:45:45
02.02.2022
12:32:18
SAPPO_DEP_AZ
I have written a query for the above table, to get max date of these two rows, Unfortunately, it returns as '28.01.2022' every time.
Here is my query.
SELECT
CType,
Material,
CoCd,
Plnt,
[Customer],
SOrg,
[Vendor],
max_date = MAX(ChangedOn) OVER (
PARTITION BY
CType,
Material,
CoCd,
Plnt,
[Customer],
SOrg,
[Vendor])
FROM
Calc
WHERE
CType='PB00'
AND
Material='775515'
AND
Plnt='GA'
Expected output: for MAxDate column is '15.02.2022'
All columns in Varchar(500), so I tried following query as well,
SELECT
CType,
Material,
CoCd,
Plnt,
[Customer],
SOrg,
[Vendor],
MAX(cast(ltrim(rtrim(ChangedOn)) as date)) AS max_date
FROM
Calc
WHERE
CType='PB00'
AND
Material='775515'
AND
Plnt='GA'
GROUP BY
CType,
Material,
CoCd,
Plnt,
[Customer],
SOrg,
[Vendor]
Showing error 'Conversion failed when converting date and/or time from character string.'
Is anything wrong here?

You could use the CONVERT or TRY_CONVERT function to convert the varchar date to date datatype inside the max function, But it would be better to store date values as a date to avoid such problems.
Try the following:
SELECT CType,Material, CoCd, Plnt, [Customer], SOrg, [Vendor],
max_date = MAX(TRY_CONVERT(DATE, ChangedOn, 104)) OVER
(PARTITION BY CType,Material, CoCd, Plnt, [Customer], SOrg, [Vendor])
FROM Calc where CType='PB00' and Material='775515' and Plnt='GA'
See a demo.

Related

SQL query applying currency conversion rate

Please help to compose an SQL query having a table with currency conversion rates that should be applied to the other table with business data, for example:
currency_rates (conversion rate is given for every beginning of the month, starting from some moment)
currency_code
rate against USD
date
CAD
1.354
2022-11-01
CAD
1.3445
2022-12-01
CAD
1.3573
2023-01-01
business_data (fees are in USD, aggregated by name and date)
name
sum(fee) in USD
date
aaa
92.52
2021-10-10
bbb
76.18
2022-11-11
ccc
113.79
2022-12-12
ddd
133.42
2023-02-02
The expected result should be the following, assuming that aggregated fee dates lay in the range of conversion rate dates (the actual result of the multiplication isn't matter here, I show x*y just to describe an idea):
name
sum(fee) in USD * CAD conversion rate
date
aaa
92.52 * 1.354 (applied first existing rate)
2021-10-10
bbb
76.18 * 1.354
2022-11-11
ccc
113.79 * 1.3445
2022-12-12
ddd
133.42 * 1.3573 (applied last existing rate)
2023-02-02
We use Snowflake DB.
Don't hesitate to ask questions or add suggestions, thank you!
You can join the tables on year and month
select a.*, b.currency_rate --multiply as you wish
from business_data a
join currency_date b on
year(a.date)=year(b.date) and
month(a.date)=month(b.date)
Or more concisely,
select a.*, b.currency_rate --multiply as you wish
from business_data a
join currency_date b on trunc(a.date,'month')=b.date
As Adrian notes, my solution above doesn't account for the fact that your currency_data table does not have all the months that exist in your business_data table so let's fix that separately instead of complicating the code above.
Your sample data doesn't establish any relationship between currency_code and business data. For that reason I am going to use hardcoded 'CAD' for currency. If your business_data table has a column that ties to currency_code, you can easily modify this solution by incorporating that column in the full outer join and select statement. Also, you can switch up the order of columns inside coalesce to handle situations where you have both "last existing" and "first existing" rate to choose from.
with cte as
(select 'CAD' as currency_code, --ideally, this would a column that joins to currency data table even though your sample data doesn't indicate such a relationship
b.rate,
coalesce(trunc(a.business_date, 'month'),currency_date) as currency_date
from business_data a
full outer join currency_data b on b.currency_date=trunc(a.business_date, 'month') )
select currency_code ,
currency_date,
coalesce(rate,
lead(rate) ignore nulls over (partition by currency_code order by currency_date),
lag(rate) ignore nulls over (partition by currency_code order by currency_date)) as rate
from cte
order by currency_date
UNION ALL
LAG()
LEAD()
COALESCE()
JOIN
BETWEEN
WITH CURRENCY_RATES AS(
SELECT 'CAD' CURRENCY_CODE, 1.354 RATE, '2022-11-01'::DATE CURRENCY_DATE
UNION ALL SELECT 'CAD' CURRENCY_CODE, 1.3445 RATE, '2022-12-01'::DATE CURRENCY_DATE
UNION ALL SELECT 'CAD' CURRENCY_CODE, 1.3573 RATE, '2023-01-01'::DATE CURRENCY_DATE)
,CURRENCY_RATES_ENHANCED AS (
SELECT
CURRENCY_CODE
, RATE
, CURRENCY_DATE
, IFF(LAG(CURRENCY_DATE)OVER(PARTITION BY CURRENCY_CODE ORDER BY CURRENCY_DATE) IS NULL,'1900-01-01',CURRENCY_DATE) CURRENCY_START_DATES
, COALESCE(LEAD(CURRENCY_DATE)OVER(PARTITION BY CURRENCY_CODE ORDER BY CURRENCY_DATE ASC),CURRENT_DATE+999)-INTERVAL '1 SEC' CURRENCY_END_DATES
FROM CURRENCY_RATES
)
,BUSINSESS_DATA AS ( SELECT 'aaa' NAME, 92.52 FEE_IN_USD, '2021-10-10'::DATE BUSINESS_DATE
UNION ALL SELECT 'bbb' NAME, 76.18 FEE_IN_USD, '2022-11-11'::DATE BUSINESS_DATE
UNION ALL SELECT 'ccc' NAME, 113.79 FEE_IN_USD, '2022-12-12'::DATE BUSINESS_DATE
UNION ALL SELECT 'ddd' NAME, 133.42 FEE_IN_USD, '2023-02-02'::DATE BUSINESS_DATE)
SELECT
NAME,
FEE_IN_USD||' * '||RATE||
IFF(CURRENCY_START_DATES='1900-01-01'::DATE AND DATE_TRUNC('MONTH',B.BUSINESS_DATE)<>C.CURRENCY_DATE ,' (applied first existing rate)'
,IFF(CURRENCY_END_DATES=CURRENT_DATE+999-INTERVAL '1 SEC',' (applied last existing rate)',null)) VOLIA
,B.BUSINESS_DATE
FROM
BUSINSESS_DATA B
JOIN CURRENCY_RATES_ENHANCED C
ON BUSINESS_DATE BETWEEN CURRENCY_START_DATES AND CURRENCY_END_DATES
;

SQL Server group by date

SELECT [DATE], [AMOUNT], SUM(AMOUNT) OVER (ORDER BY DATE) AS 'Running Total'
FROM PeopleActi
WHERE INSTANCE = 'Bank'
AND DATE IS NOT NULL
GROUP BY [DATE], [AMOUNT];
In the code above I selecting a user's date, amount and the "SUM(AMOUNT) OVER (ORDER BY DATE) AS 'Running Total'" is the running total of their costs over a period of dates. When I run this code I get the following results:
DATE AMOUNT Running Total
2018-10-05 100 100
2018-10-06 1000 1100
2018-10-07 5000 6100
2018-10-08 2000 8100
2018-10-09 1000 9100
2018-10-10 5000 14100
2018-10-11 3000 25100
2018-10-11 8000 25100
This works nicely but my issue is the last two rows. I wanted them to be grouped by their date and have the total amount for both same days, so it should be:
Date Amount Running Total
2018-10-11 11000 25100
Does anyone have an idea of how this can achieved? My [DATE] is of type DATE.
UPDATE!!!!
I've seen some of your solutions and they are good but its important I display the AMOUNT and the Running Total amount as well, so the final result should be...
DATE AMOUNT Running Total
2018-10-05 100 100
2018-10-06 1000 1100
2018-10-07 5000 6100
2018-10-08 2000 8100
2018-10-09 1000 9100
2018-10-10 5000 14100
2018-10-11 11000 25100
Thank you everyone for the help so far!
Group up the amounts and then do your cumulative total
WITH CTE
AS
(
SELECT A.Dt,
SUM(A.Amount) AS Amount
FROM (
VALUES ('2018-10-05',100),
('2018-10-06',1000),
('2018-10-07',5000),
('2018-10-08',2000),
('2018-10-09',1000),
('2018-10-10',5000),
('2018-10-11',3000),
('2018-10-11',8000)
) AS A(Dt,Amount)
GROUP BY A.Dt
)
SELECT C.Dt,
C.Amount,
SUM(C.Amount) OVER (ORDER BY C.Dt) AS CumTotal
FROM CTE AS C;
Try like below
SELECT [DATE],sum( [AMOUNT]), SUM(AMOUNT) OVER (ORDER BY DATE) AS 'Running Total'
FROM PeopleActi
WHERE INSTANCE = 'Bank'
AND DATE IS NOT NULL
GROUP BY [DATE]
If you need groping sum then why you are using window function, only aggregation is enough :
SELECT [DATE], SUM([AMOUNT])
FROM PeopleActi
WHERE INSTANCE = 'Bank' AND DATE IS NOT NULL
GROUP BY [DATE];
Try this
;WITH CTe([DATE],AMOUNT)
AS
(
SELECT '2018-10-05', 100 UNION ALL
SELECT '2018-10-06', 1000 UNION ALL
SELECT '2018-10-07', 5000 UNION ALL
SELECT '2018-10-08', 2000 UNION ALL
SELECT '2018-10-09', 1000 UNION ALL
SELECT '2018-10-10', 5000 UNION ALL
SELECT '2018-10-11', 3000 UNION ALL
SELECT '2018-10-11', 8000
)
SELECT DISTINCT [DATE],SUM(AMOUNT)OVER(PARTITION BY [DATE] ORDER BY [DATE]) AMOUNT , SUM(AMOUNT)OVER( ORDER BY [DATE]) AS RuningTot FROM CTe
Script
SELECT DISTINCT [DATE],
SUM(AMOUNT)OVER(PARTITION BY [DATE] ORDER BY [DATE]) AS AMOUNT,
SUM(AMOUNT) OVER (ORDER BY DATE) AS 'Running Total'
FROM PeopleActi
WHERE INSTANCE = 'Bank'
AND DATE IS NOT NULL
I would use a CTE to first group by Date, and then do your running total ..
So something like
with myAmounts AS
(
SELECT [DATE], SUM([AMOUNT]) AS Amount
FROM PeopleActi
WHERE INSTANCE = 'Bank'
AND DATE IS NOT NULL
GROUP BY [DATE]
)
SELECT [DATE], [AMOUNT], SUM(AMOUNT) OVER (ORDER BY DATE) AS 'Running Total'
FROM myAmounts
GROUP BY [DATE], [AMOUNT]
;
HTH,
B
ps; just saw that its the same answer as another .. democoding in action
Every field in a group by is going to cause it to potentially create new lines. If you SUM the amount field and remove it from your grouping, that should solve the issue. EDIT: I see the issue, I provided a fully stand alone example of the query below that you can adapt.
DECLARE #PeopleActi TABLE ([DATE] DATE,[AMOUNT] MONEY)
INSERT INTO #PeopleActi SELECT '2018-10-05',100
INSERT INTO #PeopleActi SELECT '2018-10-06',1000
INSERT INTO #PeopleActi SELECT '2018-10-07',5000
INSERT INTO #PeopleActi SELECT '2018-10-08',2000
INSERT INTO #PeopleActi SELECT '2018-10-09',1000
INSERT INTO #PeopleActi SELECT '2018-10-10',5000
INSERT INTO #PeopleActi SELECT '2018-10-11',3000
INSERT INTO #PeopleActi SELECT '2018-10-11',8000
SELECT *, SUM(AMOUNT) OVER (ORDER BY DATE) AS 'Running Total'
FROM (
SELECT [DATE], SUM([AMOUNT]) AS AMOUNT
FROM #PeopleActi
WHERE DATE IS NOT NULL
GROUP BY [DATE]
) a
GROUP BY [DATE],Amount
Try Subselect:
SELECT p.[DATE], p.[AMOUNT], SUM(AMOUNT) OVER (ORDER BY DATE) AS 'Running Total'
FROM
(
select [date], sum([amount]) as Amount from PeopleActi
WHERE INSTANCE = 'Bank'
AND DATE IS NOT NULL
group by [date]
) p
GROUP BY p.[DATE], p.[AMOUNT]

Trying to get Month to date sales value for each date

I have Shipdate and Totaldue column in my salesOrderheader table.
My current query is like this
select
[ShipDate], SUM([TotalDue]) Total
from
Sales.SalesOrderHeader
where
[ShipDate] between '2005-08-1' and '2005-08-31'
group by
[ShipDate]
This returns the result for total sales for each day, I want a result where each date column corresponds to a value which shows month to date sale (i.e. totaldue here). Like if total due for 2005-08-1 is $1000 ,total due for 2005-08-2 is $3000 and total due for 2005-08-3 is $6000 then result should be,
ShipDate MTD Value
-----------------------
2005-08-1 1000
2005-08-2 4000
2005-08-3 11000
And so on till 2008-08-31. I am kind of new to database and can't seem to figure this out. If anybody has idea how do to it, please help me out. Thanks
You could do it like this:
select soh1.ShipDate, SUM(soh2.TotalDue) from
(select distinct ShipDate from sales.SalesOrderHeader
where ShipDate < '2005-09-01' and ShipDate >= '2005-08-01') as soh1
join sales.SalesOrderHeader soh2
on soh2.ShipDate <= soh1.ShipDate and soh2.ShipDate >= '2005-08-01'
group by soh1.ShipDate order by soh1.ShipDate
The join will create the following resultset:
ShipDate MTD Value
2005-08-1 1000
2005-08-2 1000
2005-08-2 4000
2005-08-3 1000
2005-08-3 4000
2005-08-3 11000
and the Sum and Group By joins the dates togheter.
This works and performs reasonably well:
;with soh as (
select ShipDate, sum(TotalDue) as TotalDue
from Sales.SalesOrderHeader
where ShipDate between '20050801' and '20050831'
group by ShipDate
)
select ShipDate, sum(TotalDue) over(order by ShipDate) as Total
from soh

Debit, Credit not showing correct result on Same Date

Scenario : I am working on Users Accounts where Users add amount to there account (Credit) and they withdraw their desire amount from their account (Debit), all is going correct but when User credit or debit on same dates it gives me wrong result (Balance). here refno is reference of user. here is my Query
declare #startdate date='2013-01-02',
#enddate date='2013-01-12';
With summary(id,refno,transdate,cr,dr,balance)
as
(
select id,
RefNo,
Cast(TransDate as Varchar),
cr,
dr,
(cr-dr)+( Select ISNULL(Sum(l.Cr-l.Dr) ,0)
From Ledger l
Where l.TransDate<Ledger.TransDate and refno=001 ) as balance
from Ledger
),
openingbalance(id,refno,transdate,cr,dr,balance)
As (
select top 1 '' id,'OPENING BAL','','','',balance
from summary
where transdate<#startdate
order by transdate desc
)
select *
from openingbalance
union
Select *
From summary
where transdate between #startdate and #enddate and refno=001 order by transdate
If you are using SQL 2012 or above, then instead of
SELECT id, RefNo, TransDate,cr, dr, (cr-dr) + (Select ISNULL(Sum(l.Cr-l.Dr) ,0)
FROM Ledger l
WHERE Cast(l.TransDate as datetime) < Cast(Ledger.TransDate as datetime)
AND refno=001) as balance from Ledger
Use:
SELECT id, RefNo, TransDate, cr, dr, SUM(cr- dr) OVER(ORDER BY TransDate ROWS
BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance
The issue is because when you query for the previous balance you are only looking at records that have a transdate earlier than the current record, so this means any records that have the same date will not be included.
The solution here would be to use a more unique sequential value, in your example you could use the ID value as the sequential identifier instead. However, ID values are not always the best for ensuring sequence. I would recommend extending your transdate column to use a more precise value and include the time of the transactions. Seconds would likely be enough precision if you can guarantee that there will never be multiple transactions made within a given second, but whatever you decide you need to be confident there will not be any duplicates.
In an attempt to provide a code change solution that will work with your existing data you can try the following, which uses the id value to determine if a record is prior to the current record:
Change the following line:
Where l.TransDate<Ledger.TransDate and refno=001 ) as balance
to this:
Where l.ID<Ledger.ID and refno=001 ) as balance
After hint by #musefan i made changes to query and it is working as i want. here is query for Date Base
declare #startdate date='2013-01-02',
#enddate date='2013-01-12';
With summary(id,refno,transdate,cr,dr,balance)
as
(
select id,
RefNo,
TransDate,
cr,
dr,
(cr-dr)+( Select ISNULL(Sum(l.Cr-l.Dr) ,0)
From Ledger l
Where Cast(l.TransDate as datetime)< Cast(Ledger.TransDate as datetime) and refno=001 ) as balance
from Ledger
),
openingbalance(id,refno,transdate,cr,dr,balance)
As (
select top 1 '' id,'OPENING BAL','','','',balance
from summary
where transdate<#startdate
order by transdate desc
)
select id,refno,Cast(TransDate as varchar) as datetime,cr,dr,balance
from openingbalance
union
Select id,refno,Cast(TransDate as varchar)as datetime,cr,dr,balance
From summary
where transdate between #startdate and #enddate and refno=001 order by Cast(TransDate as varchar)
and Another Query Id Based
declare #startdate date='2013-01-02',
#enddate date='2013-01-12';
With summary(id,refno,transdate,cr,dr,balance)
as
(
select id,
RefNo,
TransDate,
cr,
dr,
(cr-dr)+( Select ISNULL(Sum(l.Cr-l.Dr) ,0)
From Ledger l
Where l.id < Ledger.id and refno=001 ) as balance
from Ledger
),
openingbalance(id,refno,transdate,cr,dr,balance)
As (
select top 1 '' id,'OPENING BAL','','','',balance
from summary
where transdate<#startdate
order by transdate desc
)
select id,refno,Cast(TransDate as varchar) as datetime,cr,dr,balance
from openingbalance
union
Select id,refno,Cast(TransDate as varchar)as datetime,cr,dr,balance
From summary
where transdate between #startdate and #enddate and refno=001 order by id

SQL Select only products and prices that have changed in price since a specified date

The tables look like this:
tblProducts (around 200k records)
SKU,Title,CategoryID
100,Apple,0
101,Orange,0
102,Carrot,1
tblCategories
CategoryID,CategoryName
0,Fruit
1,Vegetables
tblPrices (around 10m records)
SKU,BuyPrice,SellPrice,Timestamp
100,1,2,2013-1-1 23:04
100,3,6,2013-1-2 19:04
100,4,8,2013-1-3 21:04
100,4,8,2013-1-4 20:04
100,4,8,2013-1-5 22:04
I need to get the current BuyPrice of all products (the most recent one from tblPrices) and compare it to the latest BuyPrice at the time of X days ago from NOW(). I need only the products that changed in BuyPrice.
This is so I can answer the question, 'what products changed in price over the last X days?'. Given the small set of data above, I would get an empty table for 1 days or 2 days, but for 3 days, I would want to retrieve:
SKU,Title,CategoryName,OldBuyPrice,OldSellPrice,NewBuyPrice,NewSellPrice, NBP/OBP
100,Apple,Fruit, 3, 6, 4, 8, 2.00
and for 4 days:
SKU,Title,CategoryName,OldBuyPrice,OldSellPrice,NewBuyPrice,NewSellPrice, NBP/OBP
100,Apple,Fruit, 1, 2, 4, 8, 4.00
I've been searching for similar solutions on the net, but haven't found one. Any ordering is fine. Thanks in advance!
Sure, this is doable. There's a decent windowing-function version, although there may still be better ways to do this:
WITH Current_Price (sku, buyPrice, sellPrice) as
(SELECT sku, buyPrice, sellPrice
FROM (SELECT sku, buyPrice, sellPrice,
ROW_NUMBER() OVER(PARTITION BY sku
ORDER BY timestamp DESC) as rownum
FROM price) t
WHERE rownum = 1),
Price_Back_Previous_Days (sku, buyPrice, sellPrice) as
(SELECT sku, buyPrice, sellPrice
FROM (SELECT sku, buyPrice, sellPrice,
ROW_NUMBER() OVER(PARTITION BY sku
ORDER BY timestamp DESC) as rownum
FROM price
WHERE timestamp < DATEADD(DAY, -3, CONVERT(DATE, GETDATE()))) t
WHERE rownum = 1)
SELECT p.sku, p.title, c.categoryName,
prev.buyPrice as oldBuyPrice, prev.sellPrice as oldSellPrice,
curr.buyPrice as newBuyPrice, curr.sellPrice as newSellPrice,
CASE WHEN prev.buyPrice = 0
THEN curr.buyPrice
ELSE 1.0 * curr.buyPrice / prev.buyPrice END as 'NBP/OBP'
FROM Product p
JOIN Category c
ON c.categoryId = p.categoryId
JOIN Current_Price curr
ON curr.sku = p.sku
JOIN Price_Back_Previous_Days prev
ON prev.sku = p.sku
AND (prev.buyPrice <> curr.buyPrice
OR prev.sellPrice <> curr.sellPrice)
Which yields the expected
SKU TITLE CATEGORYNAME OLDBUYPRICE OLDSELLPRICE NEWBUYPRICE NEWSELLPRICE NBP/OBP
100 Apple Fruit 1 2 4 8 4
(Have a working SQL Fiddle Example, with an specific date substituted for GETDATE() for future-reasons.)
You'll notice that I'm using -3 (and not -4, as might be expected), because I'm attempting to retrieve the same value for 'as of this date', regardless of when (during the day) the query is run. Obviously the 'current price' can still change (although adding a timestamp parameter there as well could fix that); however, this should make sure that you're looking at a consistent price throughout a given day.
Try this: SQL Fiddle
Select
tt.SKU,tt.BuyPrice,tt.SellPrice,tr.BuyPrice As NewBuyPrice,tr.SellPrice As NewSellPrice, tr.BuyPrice/tt.BuyPrice NNBP
From
(
select SKU, Max(Timestamps) timestamps
from t
Group by t.SKU
) t
Join t tr on t.SKU = tr.SKU AND t.timestamps = tr.Timestamps
Join t tt ON t.SKU = tt.SKU
AND DATEDIFF(D, tt.timestamps, t.timestamps) = 4
AND tt.BuyPrice <> tr.BuyPrice
I tried to create the table oldtpc which will get the products which has maximun of date after X number of days. And another newtpc which has prices with most recent date. And in the on condition between 'oldtpc' and and 'newtpc' I am checking that those dates do not match
select tp.SKU, tp.Title, tc.CategoryName, oldtpc.BuyPrice, oldtpc.Sellprice, newtpc.buyprice, newtpc.Sellprice
from tblProducts tp
join tblCategories tc
on tp.CategoryId= tc.CateogryId
join (select SKU, BuyPrice, SellPrice, max(TimeStamp) as TimeStamp
from tblPrices new
where DATEDIFF ( dd, timestamp, getdate()) < #xdays
group by SKU, BuyPrice, SellPrice ) as newtpcnewtpc
on tp.SKU = newtpc .sku
join (select SKU, BuyPrice, SellPrice, max(TimeStamp) as TimeStamp
from tblPrices old
where DATEDIFF ( dd, timestamp, getdate()) >= #xdays
group by SKU, BuyPrice, SellPrice ) as oldtpc
on oldtpc.SKU = tp.SKU and oldtpc.timestamp <> newtpc.timestamp
PS: some syntax might be wrong, but I think the general idea should work fine