QUALIFY-Like Function in SQL Server - sql-server

My table has 2 columns: Emp_Name and Emp_NR.
It can happen that one Emp_Name value has 2 different Emp_NR values.
I would like to create a SELECT statement that gets only a single value of Emp_Name and Emp_NR
The statement should be something like in Teradata:
SELECT
Emp_Name,
Emp_NR
FROM Table
QUALIFY Row_Number OVER (PARTITION BY Emp_Name ORDER BY Emp__NR DESC) = 1
In addition to that, I would like to get the highest Emp_NR that is assigned to a specific Emp_Name.

Another way is to use ORDER BY combined with TOP 1 WITH TIES:
SELECT TOP 1 WITH TIES Emp_Name, Emp_NR
FROM Table
ORDER BY ROW_NUMBER() OVER (PARTITION BY Emp_Name ORDER BY Emp_NR DESC);
Performance may be slight worse than the solution using subquery.

You did everything right.
SELECT Emp_NR, Emp_Name
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Emp_Name ORDER BY Emp_NR DESC) AS RN, Emp_Name, Emp_NR
FROM YourTable
) AS T
WHERE T.RN = 1;
This is correct syntax.

Related

3rd highest salary in each department

I have the following tables: https://pastebin.com/Js0Sm69S (CREATE and INSERT statements).
I would like to find the third-highest salary in each department if there is such.
I was able to achieve this:
Using the following query:
SELECT *,
DENSE_RANK() OVER
(PARTITION BY DepartmentId ORDER BY Salary DESC) AS DRank
FROM Employees
I am not sure if DENSE_RANK() is the best ranking function to use here. Maybe not, because WHERE DRank=3 may return more than one result (but we can say TOP(1)). What do you think about this? Now how to display the third-highest salary in each department if there is such?
Try this
Select EmployeeID,FirstName,DepartmentID,Salary
From (
Select *
,RN = Row_Number() over (Partition By DepartmentID Order By Salary)
,Cnt = sum(1) over (Partition By DepartmentID)
From Employees
) A
Where RN = case when Cnt<3 then Cnt else 3 end
You're almost there, but you can achieve this with ROW_NUMBER, instead of DENSE_RANK. I think following query should help.
WITH cte AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY DepartmentId ORDER BY Salary DESC) AS DRank
FROM Employees
)
SELECT *
FROM cte
WHERE DRank= 3

Filter with partition over in SQL Server

Here is that I would like to have
select
*,
rank() over (partition by col_a order by col_b) as some_rank
from
table_A
where
some_rank = 1
Obviously, this is not going to work. I can use CTE or temp table to get what I want, but I wonder if can do it all at once (without CTE or temp table). Something like having with group by
you can do this old fashioned way
SELECT *
FROM
(
select
*,
rank() over (partition by col_a order by col_b) as some_rank
from
table_A
) T
WHERE some_rank = 1
You can use top (1) with ties clause :
select top (1) with ties ta.*
from table_A ta
order by rank() over (partition by col_a order by col_b);
Only the sadness is, you can't filter out rows which are greater than 1 & this has some performance downgrades.

Query table and Select latest 2 rows (in SQL Server)

I have a table that logs all updates made to an application. I want to query the table and return the last update by [Timestamp] and the update before that for a different value [ITEM]. I'm struggling to figure out how to get what i need. I'm returning more than one record for each ID and don't want that.
;WITH cte AS
(
SELECT
ID,
LAG(ITEM) OVER (PARTITION BY ID ORDER BY timestamp DESC) AS ITEM,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY timestamp DESC) RN
FROM
MyLoggingTable
WHERE
accountid = 1234
)
SELECT
cte.ID,
dl.ITEM,
DL.timestamp
FROM
cte
JOIN
MyLoggingTable DL ON cte.ID = DL.ID
WHERE
rn = 1
AND cte.ID IN ('id here | Sub select :( ..')
Is ID unique? Because if it is, your code shouldn't return duplicates. If it isn't, you will get duplicates because you are joining back to the MyLoggingTable which isn't needed. You should just move those columns (dl.Item & dl.timestamp) into the cte and return them from the cte like you did cte.ID.
I removed the LAG since you didn't return that column in your final query.
;WITH cte AS
(
SELECT
ID,
ITEM,
[timestamp],
--LAG(ITEM) OVER (PARTITION BY ID ORDER BY timestamp DESC) AS ITEM,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY timestamp DESC) RN
FROM
MyLoggingTable
WHERE
accountid = 1234
)
SELECT
cte.ID,
cte.ITEM,
cte.timestamp
FROM
cte
WHERE
rn = 1
AND cte.ID IN ('id here | Sub select :( ..')
Note, if you wanted the second to the last item, as you stated in your comments, make rn=2

Latest Customer Donation And Amount

I have two tables:
Customer which has an Id column representing the customer Id.
CustomerDonation that contains CustomerId (FK), Amount and DatePayed
I'd like have all the customers together with their latest donation and the amount of that donation.
I am receiving duplicate values on my query so I will not paste it here.
You could also use the WITH TIES option
Select Top 1 With Ties *
From YourTable
Order By Row_Number() over (Partition By CustomerId Order By DatePayed Desc)
WITH
SortedDonation AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY DatePayed DESC) AS SeqID,
*
FROM
CustomerDonation
)
SELECT
*
FROM
Customer
LEFT JOIN
SortedDonation
ON SortedDonation.CustomerId = Customer.Id
AND SortedDonation.SeqId = 1
If the same customer can make multiple donations with the same DatePayed, then this will arbitrarily pick just one of them.
If you add additional fields to the ORDER BY you can deterministically pick which one you want.
Or, if you want all of them use DENSE_RANK() instead of ROW_NUMBER()
Use Row_Number() Analytic function .
Select * from (
Select customerId,Amount,DatePayed, row_number() over (partition by CustomerId order by DatePayed desc) as rowN)
as tab where rowN = 1
You only need the CustomerDonation table for this. You can join with the Customer table if you want other information of the customer.
WITH cte AS (
SELECT
CustomerId
, MAX(DatePayed) AS LastDate
FROM
CustomerDonation
)
SELECT
cd.CustomerId
, cd.Amount
, cd.DatePayed
FROM
CustomerDonation cd
JOIN cte ON cd.CustomerId = cte.CustomerId
AND cd.DatePayed = cte.LastDate

Filter first then select page

How to first filter the result based on params then to apply where-between?
Some thing like
With Results as
(
Select colName,Title, Row_Number(Over...) as row from a table where colName=5
)
Select * from Results
where
row between #first and #last
But it does not works. I need to move my where colName=5 from with clause to outside then I got wrong data as It first get rows between #first n #last then search for colName=5.
Also I want count of Results.
Any idea?
You can use COUNT(*) OVER() to get the count of the unfiltered results
WITH cte as
(
select *,
ROW_NUMBER() over (order by name desc) AS RN,
count(*) over() AS [Count]
from master..spt_values
)
SELECT name, number,[Count]
FROM cte
WHERE RN BETWEEN 20 AND 24
Returns
name number Count
----------------------------------- ----------- -----------
VIEW 8278 2506
VIEW 8278 2506
view 2 2506
varchar 3 2506
varbinary 1 2506
This has performance implications though. You might want to just calculate the COUNT up front and cache it somewhere rather than recalculating it for every page request.
Your ROW_NUMBER syntax is incorrect. It should be this:
With Results as
(
SELECT colName, Title, ROW_NUMBER() OVER (ORDER BY ...) AS RN
FROM your_table
WHERE colName = 5
)
SELECT * FROM Results
WHERE rn BETWEEN #first AND #last
ORDER BY rn
See the documentation for more information.
I use approach very similar to Martin Smiths (currently selected answer) and at least in the tests I've made it gives better performance results.
; WITH cte as
(
select *,
ROW_NUMBER() over (order by name desc) AS RN
from master..spt_values
)
SELECT name, number, (SELECT COUNT(*) FROM cte) AS [Count]
FROM cte
WHERE RN BETWEEN 20 AND 24
Run this and his queries side by side and compare execution plans.

Resources