Returning First/Last Date and PIVOT - sql

I am trying to put a view together in SQL that uses transactional type data to find the first transaction date, last transaction date, and whether it was a credit or debit. This is what it looks like currently:
Account Number
Date
Credit/Debit
123
1-1-22
Debit
123
1-2-22
Credit
456
1-1-22
Debit
456
1-2-22
Credit
I want it to look like this:
Account Number
FirstDate
LastDate
First Credit/Debit
Last Credit/Debit
123
1-1-22
1-2-22
Debit
Credit
456
1-1-22
1-2-22
Debit
Credit
I have created something close with the following code, but am having trouble figuring out how to bring in the First/Last Credit/Debit columns.
SELECT * FROM
(
SELECT * FROM
(
SELECT 'Earliest' as [TransDate], [Account], [Date], [Credit/Debit],
ROW_NUMBER() OVER (PARTITION BY [Account] ORDER BY [Date]) as rn
FROM DataTable
) e
WHERE e.rn = 1
UNION ALL
SELECT * FROM
(
SELECT 'Latest' as [TransDate], [Account], [Date], [Credit/Debit],
ROW_NUMBER() OVER (PARTITION BY [Account] ORDER BY [Date] DESC) as rn
FROM DataTable
) l
WHERE l.rn = 1
) t1
PIVOT (min([Date])) FOR [TransDate] in ([Latest], [Earliest])
) P

You can use ROW_NUMBER() function to get the first and the last row for each account number
;with
dd as (
select *,
ROW_NUMBER() over (partition by [Account Number] order by [Date]) rFirst,
ROW_NUMBER() over (partition by [Account Number] order by [Date] desc) rLast
from DataTable
)
select
d1.[Account Number],
d1.[Date] FirstDate, d2.[Date] LastDate,
d1.[Credit/Debit] [First Credit/Debit], d2.[Credit/Debit] [Last Credit/Debit]
from dd d1
join dd d2 on d1.[Account Number] = d2.[Account Number]
and d1.rFirst=1 and d2.rLast=1
Pay attention, I think you have an error in your data, you wrote 455 instead of 456

SELECT
DISTINCT
Account_Number,
FIRST_VALUE(DATE) OVER (PARTITION BY Account_Number ORDER BY DATE ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS FirstDate,
LAST_VALUE(DATE) OVER (PARTITION BY Account_Number ORDER BY DATE ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS LastDate,
FIRST_VALUE(Credit_Debit) OVER (PARTITION BY Account_Number ORDER BY DATE ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS FirstCreditDebit,
LAST_VALUE(Credit_Debit) OVER (PARTITION BY Account_Number ORDER BY DATE ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS LastCreditDebit
FROM t

We can achieve this result with group by. No need to pivot.
select Account_Number
,min(date) as FirstDate
,max(date) as LastDate
,min(fcd) as "First Credit/Debit"
,min(lcd) as "Last Credit/Debit"
from (
select *
,first_value(Credit_Debit) over(partition by Account_Number order by Date) as fcd
,last_value(Credit_Debit) over(partition by Account_Number order by Date) as lcd
from t
) t
group by Account_Number
Account_Number
FirstDate
LastDate
First Credit/Debit
Last Credit/Debit
123
2022-01-01
2022-01-02
Debit
Credit
456
2022-01-01
2022-01-02
Debit
Credit
Fiddle

Related

SQL - get start & end balance for each member each year

so I'd like to effectively get for each year the starting and end balance for each member for every year there is a record. for example the below would give me the latest balance for each member each year based on the date column
SELECT
T.MemberID,
T.DateCol,
T.Amount
FROM
(SELECT T.MemberID,
T.DateCol,
Amount,
ROW_NUMBER() OVER (PARTITION BY MemberID,
YEAR(DateCol)
ORDER BY
DateCol desc) AS seqnum
FROM
Tablet T
GROUP BY DateCol, MemberID, Amount
) T
WHERE
seqnum = 1 AND
MemberID = '1000009'
and the below would give me the earliest balance for each year
SELECT
T.MemberID,
T.DateCol,
T.Amount
FROM
(SELECT T.MemberID,
T.DateCol,
Amount,
ROW_NUMBER() OVER (PARTITION BY MemberID,
YEAR(DateCol)
ORDER BY
DateCol) AS seqnum
FROM
Tablet T
GROUP BY DateCol, MemberID, Amount
) T
WHERE
seqnum = 1 AND
MemberID = '1000009'
This would give me a result set like the below, column titles (MemberID, Date, Amount)
What I'm looking for is one query which is done by YEAR, MEMBERID, STARTBALANCE, ENDBALANCE as the columns. And would look like the below
What would be the best way to go about this?
commented above

How Can I Partition Rows in Cases When the Data is Greater than Other Values of the Same Category

I am using ROW NUMBER() OVER (PARTITION BY) to obtain a numerical index of the first occurring incident a customer purchased a product.
Using the SQL query of:
SELECT
ROW_NUMBER () OVER (PARTITION BY
[Customer Name]
ORDER BY
[Created Date] ) AS Partition,
[Customer Name],
[Created Date]
FROM Database
My data populates as such:
Current Table
My Question
I would like my data to partition additionally by the date. But only if the next date is greater than 60 days from the prior day. The numerical list would reset every 60 days. This Table would populate like this:
Ideal Data
Use lag() and a cumulative sum to define the groups:
select t.*,
sum(case when prev_createddate > dateadd(day, -60, createddate) then 0 else 1 end) over (partition by customername order by createddate) as grp
from (select t.*,
lag(createddate) over (partition by customername order by createddate) as prev_createddate
from t
) t;
Then use row_number() within each group:
select t.*,
row_number() over (partition by customername, grp order by createddate) as mypartition
from (select t.*,
sum(case when prev_createddate > dateadd(day, -60, createddate) then 0 else 1 end) over (partition by customername order by createddate) as grp
from (select t.*,
lag(createddate) over (partition by customername order by createddate) as prev_createddate
from t
) t
) t;
Note that partition is a very poor name for a column because it is a SQL key word.

How to get the validity date range of a price from individual daily prices in SQL

I have some prices for the month of January.
Date,Price
1,100
2,100
3,115
4,120
5,120
6,100
7,100
8,120
9,120
10,120
Now, the o/p I need is a non-overlapping date range for each price.
price,from,To
100,1,2
115,3,3
120,4,5
100,6,7
120,8,10
I need to do this using SQL only.
For now, if I simply group by and take min and max dates, I get the below, which is an overlapping range:
price,from,to
100,1,7
115,3,3
120,4,10
This is a gaps-and-islands problem. The simplest solution is the difference of row numbers:
select price, min(date), max(date)
from (select t.*,
row_number() over (order by date) as seqnum,
row_number() over (partition by price, order by date) as seqnum2
from t
) t
group by price, (seqnum - seqnum2)
order by min(date);
Why this works is a little hard to explain. But if you look at the results of the subquery, you will see how the adjacent rows are identified by the difference in the two values.
SELECT Lag.price,Lag.[date] AS [From], MIN(Lead.[date]-Lag.[date])+Lag.[date] AS [to]
FROM
(
SELECT [date],[Price]
FROM
(
SELECT [date],[Price],LAG(Price) OVER (ORDER BY DATE,Price) AS LagID FROM #table1 A
)B
WHERE CASE WHEN Price <> ISNULL(LagID,1) THEN 1 ELSE 0 END = 1
)Lag
JOIN
(
SELECT [date],[Price]
FROM
(
SELECT [date],Price,LEAD(Price) OVER (ORDER BY DATE,Price) AS LeadID FROM [#table1] A
)B
WHERE CASE WHEN Price <> ISNULL(LeadID,1) THEN 1 ELSE 0 END = 1
)Lead
ON Lag.[Price] = Lead.[Price]
WHERE Lead.[date]-Lag.[date] >= 0
GROUP BY Lag.[date],Lag.[price]
ORDER BY Lag.[date]
Another method using ROWS UNBOUNDED PRECEDING
SELECT price, MIN([date]) AS [from], [end_date] AS [To]
FROM
(
SELECT *, MIN([abc]) OVER (ORDER BY DATE DESC ROWS UNBOUNDED PRECEDING ) end_date
FROM
(
SELECT *, CASE WHEN price = next_price THEN NULL ELSE DATE END AS abc
FROM
(
SELECT a.* , b.[date] AS next_date, b.price AS next_price
FROM #table1 a
LEFT JOIN #table1 b
ON a.[date] = b.[date]-1
)AA
)BB
)CC
GROUP BY price, end_date

Return only the highest row number for a partitioned column

I'm trying to partition a list of submitted machining jobs by the date they were submitted and return a maximum row number for each partition.
I have tried using Group By, but I want to retain all rows in the result. Partition By does what I need, but I want to display all rows except the maximum row number as blank.
SELECT [Created Date]
,row_number() over(partition by format([Created Date],'d','en-gb') order by [Created Date] desc) AS [Jobs Submitted That Day]
FROM [UK_App].[dbo].[rvxDevMCRequests]
order by [Created Date] desc
Results:
Created Date Jobs Submitted That Day
31/12/2014 1
31/10/2019 1
31/10/2019 2
31/10/2019 3
31/10/2018 1
31/10/2018 2
The order by function is not working correctly, and I can't figure out how to display only the highest row number. I would like it to output this:
Created Date Jobs Submitted That Day
31/12/2014 1
31/10/2018
31/10/2018 2
31/10/2019
31/10/2019
31/10/2019 3
Not an elegant solution:
SELECT [Created Date]
, case when row_number() over(partition by format([Created Date],'d','en-gb') order by [Created Date] desc)
= count(*) over(partition by format([Created Date],'d','en-gb'))
then count(*) over(partition by format([Created Date],'d','en-gb'))
else null end AS [Jobs Submitted That Day]
FROM [UK_App].[dbo].[rvxDevMCRequests]
order by [Created Date] desc
Try this one:
SELECT
a.CreatedDate,
CASE
WHEN y.rnum IS NULL
THEN ''
ELSE
a.JobsSubmitted
END AS JobsSubmitted
FROM
input a
LEFT OUTER JOIN
(
SELECT
x.CreatedDate, x.JobsSubmitted, x.rnum
FROM (
SELECT
a.*,
ROW_NUMBER() OVER(PARTITION BY a.CreatedDate ORDER BY a.JobsSubmitted DESC) AS rnum
FROM
input a
) x
WHERE
x.rnum = 1
) y
ON (
a.CreatedDate = y.CreatedDate
AND a.JobsSubmitted = y.JobsSubmitted
);
SQL Fiddle link for demo: http://www.sqlfiddle.com/#!18/511abf/17
Why are you using format()? There is no reason to convert a date to a string, especially in this case.
One significant issue is that the column [Created Date] has duplicates. When you order by that column, the duplicates can be in any order. In fact, two different order bys on the column in the same query can result in different ordering.
The solution to that is to capture the ordering once in a subquery and then use that:
select [Created Date],
(case when cnt = seqnum then seqnum
end) as [Jobs Submitted That Day]
from (select r.*,
row_number() over (partition by [Created Date] order by [Created Date] desc) as seqnum,
count(*) over (partition by [Created Date]) as cnt
from [UK_App].[dbo].[rvxDevMCRequests]
) r
order by [Created Date] desc, seqnum;
In the above query, seqnum captures the ordering, so it is used for the outer order by.

T-SQL query to obtain the no of days an item was at the current price

Declare #sec_temp table
(
sec_no varchar(10),
amount money,
price_date date
)
insert #sec_temp
values
('123ABC', 25, '2011-01-20'),
('123ABC', 25, '2011-01-19'),
('123ABC', 25, '2011-01-18'),
('123ABC', 20, '2011-01-15'),
('123ABC', 22, '2011-01-13'),
('456DEF', 22, '2011-01-13')
Problem: To list out the distinct sec_no with the latest price (amount) and the number of days it was at the current price. In this case,
Result:
sec_no amount no_of_days_at_price
123ABC 25 3 e.g. 01-18 to 01-20
456DEF 22 1 e.g. 01-13
select
a.sec_no,
a.amount,
min(price_date) as FirstDateAtPrice,
No_of_days_at_price = COALESCE(DATEDIFF(d, c.price_date, a.price_date),0)
from (
select *, ROW_NUMBER() over (partition by sec_no order by price_date desc) rn
from #sec_temp) a
outer apply (
select top 1 *
from #sec_temp b
where a.sec_no=b.sec_no and a.amount <> b.amount
order by b.price_date desc
) c
where a.rn=1
The subquery A works out the greatest-1-per-group, which is to say the most recent price record for each sec_no. The subquery C finds the first prior record that holds a different price for the same sec_no. The difference in the two dates is the number of days sought. If you need it to be one for no prior date, change the end of the COALESCE line to 1 instead of 0.
EDITED for clarified question
To start counting from the first date equal to the current rate, use this query instead
select
sec_no,
amount,
No_of_days_at_price = 1 + DATEDIFF(d, min(price_date), max(price_date))
from (
select *,
ROW_NUMBER() over (partition by sec_no order by price_date desc) rn,
ROW_NUMBER() over (partition by sec_no, amount order by price_date desc) rn2
from #sec_temp
) X
WHERE rn=rn2
group by sec_no, amount
AND FINALLY If the required result is actually the days between
the first date on which the price is equal to current; and
today
Then the only part to change is this:
No_of_days_at_price = 1 + DATEDIFF(d, min(price_date), getdate())
Here's one approach, first looking up the latest price, and then the last price that was different:
select secs.sec_no
, latest.amount as price
, case when previous.price_date is null then 1
else datediff(day, previous.price_date, latest.price_date)
end as days_at_price
from (
select distinct sec_no
from #sec_temp
) secs
cross apply
(
select top 1 amount
, price_date
from #sec_temp
where sec_no = secs.sec_no
order by
price_date desc
) latest
outer apply
(
select top 1 price_date
from #sec_temp
where sec_no = secs.sec_no
and amount <> latest.amount
order by
price_date desc
) previous
This prints:
sec_no price days_at_price
123ABC 25,00 5
456DEF 22,00 1