SQL server Rank function by week - sql-server

I am still learning about SQL Server, and recently I encountered with issue with ranking (not sure if i should use rank).
I am trying to get the ranking to sort out as below, but i could not achieve by using row_number(over) or any ranking function in SQL server.
[Add in ]
the content in table1 has Cust_Code, Week and T_Mode, the one i display below is CustA. basically i want to know CustA, he is first using Air, then Water then switch to Air again in listed weeks.
**** Required Output ******
Week T_Mode Rank
201301 Air 1
201303 Water 2
201305 Water 2
201306 Water 2
201311 Air 3
i used Row_Number but it does not give what I want.
select *
, row_number()over(partition by T_Mode order by week) as Rank
from table1
the output returned
Week T_Mode Rank
201301 Air 1
201303 Water 1
201305 Water 2
201306 Water 3
201311 Air 2
any advice would be most welcome. Thank you!

Declare #t table(Week1 int,T_Mode varchar(20))
insert into #t values(201301,'Air'),(201303,'Water'),(201305,'Water'),(201306,'Water'),(201311,'Air')
;with cte as
(
select top 1 week1,t_mode,1 [Rank] from #t order by week1
union all
select b.week1,b.t_mode,case when a.T_Mode=b.T_Mode then a.Rank else a.Rank+1 end [Rank] from #t b
outer apply cte a
where b.Week1>a.week1 and b.T_Mode<>a.T_Mode
)
select distinct * from cte

Here's a solution that works in SQL Server 2012:
select
week, t_mode,
sum(change) over (order by week rows unbounded preceding) [rank]
from (
select *,
case when
(select top 1 t_mode
from table1
where week < t1.week
order by week desc) <> t1.t_mode then 1 else 0 end [change]
from table1 t1
) x
See this SQL Fiddle
Here is a less efficient version for SQL 2008:
;with cte as (
select *,
case when
(select top 1 t_mode
from table1
where week < t1.week
order by week desc) <> t1.t_mode then 1 else 0 end [change]
from table1 t1
)
select
week, t_mode,
(select sum(change) from cte where week <= x.week) [rank]
from cte x
And here's the fiddle.

Related

SQL Rows are not deleted from table when using where not exists and select distinct subquery

I am fairly new to SQL and have not been able to find what I'm doing wrong. I am trying to delete rows that have matching fields in 2 specific columns with other rows in the same table, for a temporary table so that only one row remains with the same ID and Year AND so that the row that remains is the one with the MAX date for those ID's in that Year.
My table:
ID Year Date
----------------------------------------------------
1 2017 01/05/2017
1 2017 11/17/2017
1 2017 08/07/2017
1 2016 03/22/2017
1 2016 04/01/2017
2 2017 03/12/2017
2 2016 02/03/2016
2 2016 04/19/2016
Desired results:
ID Year Date
----------------------------------------------------
1 2017 11/17/2017
1 2016 04/01/2016
2 2017 03/12/2017
2 2016 04/19/2016
What I have:
DELETE FROM #Temp
WHERE NOT EXISTS (
SELECT DISTINCT
t1.ID,
t1.Year
FROM #Temp AS t1,
#Temp AS t2
WHERE t1.ID = t2.ID
AND t1.Year <> t2.Year
GROUP BY t1.ID, t1.Year )
When I run it, nothing gets deleted but when I remove the first two lines to test what would be deleted, the results are correct so I'm really confused. I am working on the MAX function but want this part to work first.
Any help would be greatly appreciated! I have no idea what could be wrong with it.
one other way is to use row_number() function to find the duplicate records based on ID.. Year
delete t from
(
select *, row_number() over (partition by ID, [Year] order by date desc) rn from <table>
) t where t.rn > 1
with cte as
(
select row_number() over (partition by ID, Year order by date desc) as rn
)
delete * from cte where rn > 1;

SQL Server Query for account statement

How can I generate last three transactions from the below table?
Date Tran dr cr total
-------------------------------------
2017-04-13
2017-07-15
2017-07-15
2017-10-17
2017-10-17 abc 10 10
2017-11-12 def 10 20
2017-11-12 ghi 5 15
I'm using SQL Server 2012
Like this you should your expected result:
SELECT * FROM
(
SELECT TOP 3 *
FROM TransactionTable
ORDER BY [Date] DESC
) AS t
ORDER by t.[Date]
if your requirement is to get the 3 transactions with the latest date. you can use either of the following.
Simple Order by :
select
top 3
* from YourTable
where isnull(Tran,'')<>''
order by [Date] desc
using Row Number
;with cte
as
(
select
seqno = row_number() over(order by [date] desc),
*
from YourTable
where isnull(Tran,'')<>''
)
select
* from cte
where SeqNo <=3
order by SeqNo desc

SQL Server return records where the most recent matches the criteria

This is a question using SQL Server. Is there a more elegant way of doing this?
Considering a table mytable (I'm using Unix timestamps, I've converted these to readable dates for ease of reading)
ID Foreign_ID Date
-------------------------
1 1 01-Jul-15
2 2 01-Sep-16
3 3 05-Aug-16
4 2 01-Sep-15
I would like to extract the Foreign_ID where the most recent record's (highest ID) date is in a range, which is this example is the 1st January 2016 to 31st December 2016. The following works if substituting the dates for timestamps:
select distinct
Foreign_ID
from
mytable l1
where
(select top 1 Date
from mytable l2
where l2.Foreign_ID = l1.Foreign_ID
order by ID desc) >= **1 Jan 2016**
and
(select top 1 Date
from mytable l2
where l2.Foreign_ID = l1.Foreign_ID
order by ID desc) <= **31 Dec 2016**
That should only return Foreign_ID = 3. A simpler query would also return Foreign_ID 2 which would be wrong, as it has a more recent record dated out of the above ranges
This will all form part of a bigger query
Assuming SQL Server 2005+, you can use ROW_NUMBER:
WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER( PARTITION BY Foreign_ID
ORDER BY ID DESC)
FROM dbo.YourTable
WHERE [Date] >= '01-Jan-2016' -- you need to use the right
AND [Date] <= '31-Dec-2016' -- date format here
)
SELECT Foreign_ID
FROM CTE
WHERE RN = 1;
If it's SQL Server 2008+, you can use this:
select foreign_id
from (
select foreign_id, row_number() over (order by id desc) as rseq
from myTable
where Date >= value1 and Date <= value2
) as x
where rseq = 1
Just fill in the date values, and you might have to put brackets or quotes around the column named "Date", since it is also a keyword.

SQL SMS 2008 -Count column ids and count duplicate ids if createddate is greater than 3 months between ids

*Edit (Hopefully to be more clear)
Table below, I would like to count ids and count duplicate ids where the createddate has a gap of 3 months or more for that ID.
Query I have so far...
if object_id('tempdb..#temp') is not null
begin drop table #temp end
select
top 100
a.id, a.CreatedDate
into #temp
from tbl a
where 1=1
--and year(CreatedDate) = '2015'
if object_id('tempdb..#temp2') is not null
begin drop table #temp2 end
select t.id, count(t.id) as Total_Cnt
into #temp2
from #temp t
group by id
select distinct #temp2.Total_Cnt, #temp2.id, #temp.CreatedDate, DENSE_RANK() over (partition by #temp.id order by createddate) RK
from #temp2
inner join #temp on #temp2.id = #temp.id
where 1=1
order by Total_Cnt desc
Results:
Total_cnt id createddate rk
3 1 01-01-2015 1
3 1 03-02-2015 2
3 1 01-02-2015 3
2 2 05-01-2015 1
2 2 05-02-2015 2
1 3 06-01-2015 1
1 4 07-01-2015 1
Count ids and only count duplicate ids when the createddate from the id is greater than 3 months.
Something like this...
Total_cnt id Countwith3monthgap
3 1 2
2 2 1
1 3 1
1 4 1
You can use a cte and ROW_NUMBER to get your order and self join the cte based on the order..
WITH cte AS
( SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CreatedDate) Rn
FROM
Test
)
SELECT
c1.ID,
COUNT(CASE WHEN c2.CreatedDate IS NULL THEN 1
WHEN c1.CreatedDate >= DATEADD(month,3,c2.CreatedDate) THEN 1
END)
FROM
cte c1
LEFT JOIN cte c2 ON c1.ID = c2.ID
AND c1.RN = c2.RN + 1
GROUP BY
c1.ID
You also need to use a conditional count where the Previous CreatedDate is null or if the Current CreatedDate is >= the Previous CreatedDate + 3 months
If you happen to be using SQL 2012+ you can also use LAG here to get the same result
SELECT
ID,
COUNT(*)
FROM
(SELECT
ID,
CreatedDate CurrentDate,
LAG(CreatedDate) OVER (PARTITION BY ID ORDER BY CreatedDate) PreviousDate
FROM
Test
) T
WHERE
PreviousDate IS NULL
OR CurrentDate >= DATEADD(month, 3, PreviousDate)
GROUP BY
ID
You can use a lag to get the previous date, Null for the first in the list
SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t
You can use that as a subquery and get the difference in months using DATEDIFF
SELECT sub.id,DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate)
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
You can then take your totals
SELECT sub.id,COUNT(sub.id) as cnt
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDIFF(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
GROUP BY sub.id
Note that using datediff the last day of january is three months before the first day of march. That appears to be the logic you were after.
You might want to define your three month gap criteria as
WHERE sub.PreviousCreateDate <= DATEADD(month, -3, sub.CreatedDate)
OR sub.PreviousCreateDate IS NULL
or
WHERE sub.CreatedDate >= DATEADD(month, +3, sub.PreviousCreateDate )
OR sub.PreviousCreateDate IS NULL
I'm guessing that your desired definition of three-month gap doesn't coincide with datediff()'s. Most of the logic here is to look back at the previous date and decide if the gap is big enough to qualify.
When datediff() counts three months difference we still need to make sure the day of month is later than the first one (per example and ID 5). If difference is more than three months then we're good automatically.
But I'm also assuming that you would want to treat the distance from November 30th to February 28th (or 29th in a leap year) as a full three months because the end date falls on the final day of the month. By adjusting the end date by an extra day this is an easy scenario to snag as it will bump the date into the following month and increase the month difference by one as well. If that's not what you want then just remove the dateadd(day, 1, ...) portion and use only the raw CreatedDate value.
You sample data is limited so I'm also making the assumption that the gaps are measure between consecutive dates. If you're wanting to find blocks of runs that don't span more than three months across the set, then that's a different problem and you should clarify with more information.
Since you've indicated that you're probably on SQL Server 2008 you'll have to do without the lag() function. Although the first query could be adjusted for that it's likely easier to go with the second approach at the end.
with diffs as (
select
ID,
row_number() over (partition by ID order by CreatedDate) as RN,
case when
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
CreatedDate
) = 3
and
datepart(
day,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate)
) <= datepart(day, CreatedDate)
or
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
/* adding one day to handle gaps like Nov30 - Feb28/29 and Jan31 - Apr30 */
dateadd(day, 1, CreatedDate)
) >= 4
then 1
else 0
end as GapFlag
from <T> /* <--- your table name here */
), gaps as (
select
ID, RN,
sum(1 + GapFlag) over (partition by ID order by RN) as Counter
from diffs
)
select ID, count(distinct Counter - RN) as "Count"
from gaps
group by ID
The rest of the logic is a typical gaps and islands scenario looking for holes in the sum(1 + GapCount) sequence with the offset of 1 acting pretty much like row_number().
http://sqlfiddle.com/#!6/61b12/3
JamieD77's approach is also valid. I was originally thinking your problem involved more than looking at the rows in sequence. Here's how I would tweak it for the gap definition I've been running with:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select ID, count(*) as "Count"
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null
group by ID
Edit: You have changed the question since yesterday.
Change this line in the first query to include the total count:
...
select count(*) as TotalCnt, ID, count(distinct Counter - RN) as GapCount
...
Second would look like:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select
count(*) as TotalCnt, ID,
count(case when
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null then 1 end
) as GapCount
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
group by ID

Get latest record for each day for last n days using MS Sql Server

CurrencyId LeftCurrencyId RightCurrencyId ExchangeRateAt ExchangeRate
1 1 5 2013-06-27 00:51:00.000 39.0123
2 3 5 2013-06-26 01:54:00.000 40.0120
3 1 5 2013-06-26 00:51:00.000 49.0143
4 3 5 2013-06-25 14:51:00.000 33.3123
5 3 5 2013-06-25 06:51:00.000 32.0163
6 1 5 2013-06-25 00:08:00.000 37.0123
I need latest record for each day for last n days based on combination of leftcurrencyid and rightcurrencyid.
Here's one option:
with TopPerDay as
(
select *
, DayRank = row_number() over (partition by LeftCurrencyId, RightCurrencyId, cast(ExchangeRateAt as date)
order by ExchangeRateAt desc)
from ExchangeRate
)
select CurrencyId,
LeftCurrencyId,
RightCurrencyId ,
ExchangeRateDay = cast(ExchangeRateAt as date),
ExchangeRateAt ,
ExchangeRate
from TopPerDay
where DayRank = 1
order by LeftCurrencyId,
RightCurrencyId,
ExchangeRateDay
SQL Fiddle with demo.
It groups by LeftCurrencyId, RightCurrencyId, and ExchangeRateAt day without the time component, then takes the latest record in the day for all those groups.
You don't mention whether you want N days back is from the present day or an unspecified date, but you can add this using a WHERE clause when selecting from the ExchangeRate table in the CTE definition.
Here are my two cents
Select ExchangeRateAt , * from Table1 where ExchangeRateAt in (Select max(ExchangeRateAt) from Table1 Group by cast( ExchangeRateAt as Date))
Order by ExchangeRateAt
Here 7 in the end is the last N days parameter (7 in this example)
with T1 as
(
select t.*,
cast(floor(cast([ExchangeRateAt] as float)) as datetime) as DatePart,
ROW_NUMBER() OVER (
PARTITION BY [LeftCurrencyId],
[RightCurrencyId],
cast(floor(cast([ExchangeRateAt] as float)) as datetime)
ORDER BY [ExchangeRateAt] DESC
) RowNumber
from t
), T2 as
(
select *,
ROW_NUMBER() OVER (PARTITION BY [LeftCurrencyId],
[RightCurrencyId]
ORDER BY DatePart DESC
) as RN
from T1 where RowNumber=1
)
select [CurrencyId],
[LeftCurrencyId],
[RightCurrencyId],
[ExchangeRateAt],
[ExchangeRate],
DatePart
from T2 where RN<=7
SQLFiddle demo

Resources