How to use RANK() in SQL Server - sql-server

I have a problem using RANK() in SQL Server.
Here’s my code:
SELECT contendernum,
totals,
RANK() OVER (PARTITION BY ContenderNum ORDER BY totals ASC) AS xRank
FROM (
SELECT ContenderNum,
SUM(Criteria1+Criteria2+Criteria3+Criteria4) AS totals
FROM Cat1GroupImpersonation
GROUP BY ContenderNum
) AS a
The results for that query are:
contendernum totals xRank
1 196 1
2 181 1
3 192 1
4 181 1
5 179 1
What my desired result is:
contendernum totals xRank
1 196 1
2 181 3
3 192 2
4 181 3
5 179 4
I want to rank the result based on totals. If there are same value like 181, then two numbers will have the same xRank.

Change:
RANK() OVER (PARTITION BY ContenderNum ORDER BY totals ASC) AS xRank
to:
RANK() OVER (ORDER BY totals DESC) AS xRank
Have a look at this example:
SQL Fiddle DEMO
You might also want to have a look at the difference between RANK (Transact-SQL) and DENSE_RANK (Transact-SQL):
RANK (Transact-SQL)
If two or more rows tie for a rank, each tied rows receives the same
rank. For example, if the two top salespeople have the same SalesYTD
value, they are both ranked one. The salesperson with the next highest
SalesYTD is ranked number three, because there are two rows that are
ranked higher. Therefore, the RANK function does not always return
consecutive integers.
DENSE_RANK (Transact-SQL)
Returns the rank of rows within the partition of a result set, without
any gaps in the ranking. The rank of a row is one plus the number of
distinct ranks that come before the row in question.

To answer your question title, "How to use Rank() in SQL Server," this is how it works:
I will use this set of data as an example:
create table #tmp
(
column1 varchar(3),
column2 varchar(5),
column3 datetime,
column4 int
)
insert into #tmp values ('AAA', 'SKA', '2013-02-01 00:00:00', 10)
insert into #tmp values ('AAA', 'SKA', '2013-01-31 00:00:00', 15)
insert into #tmp values ('AAA', 'SKB', '2013-01-31 00:00:00', 20)
insert into #tmp values ('AAA', 'SKB', '2013-01-15 00:00:00', 5)
insert into #tmp values ('AAA', 'SKC', '2013-02-01 00:00:00', 25)
You have a partition which basically specifies grouping.
In this example, if you partition by column2, the rank function will create ranks for groups of column2 values. There will be different ranks for rows where column2 = 'SKA' than rows where column2 = 'SKB' and so on.
The ranks are decided like this:
The rank for every record is one plus the number of ranks that come before it in its partition. The rank will only increment when one of the fields you selected (other than the partitioned field(s)) is different than the ones that come before it. If all of the selected fields are the same, then the ranks will tie and both will be assigned the value, one.
Knowing this, if we only wanted to select one value from each group in column two, we could use this query:
with cte as
(
select *,
rank() over (partition by column2
order by column3) rnk
from t
) select * from cte where rnk = 1 order by column3;
Result:
COLUMN1 | COLUMN2 | COLUMN3 |COLUMN4 | RNK
------------------------------------------------------------------------------
AAA | SKB | January, 15 2013 00:00:00+0000 |5 | 1
AAA | SKA | January, 31 2013 00:00:00+0000 |15 | 1
AAA | SKC | February, 01 2013 00:00:00+0000 |25 | 1
SQL DEMO

You have to use DENSE_RANK rather than RANK. The only difference is that it doesn't leave gaps. You also shouldn't partition by contender_num, otherwise you're ranking each contender in a separate group, so each is 1st-ranked in their segregated groups!
SELECT contendernum,totals, DENSE_RANK() OVER (ORDER BY totals desc) AS xRank FROM
(
SELECT ContenderNum ,SUM(Criteria1+Criteria2+Criteria3+Criteria4) AS totals
FROM dbo.Cat1GroupImpersonation
GROUP BY ContenderNum
) AS a
order by contendernum
A hint for using StackOverflow, please post DDL and sample data so people can help you using less of their own time!
create table Cat1GroupImpersonation (
contendernum int,
criteria1 int,
criteria2 int,
criteria3 int,
criteria4 int);
insert Cat1GroupImpersonation select
1,196,0,0,0 union all select
2,181,0,0,0 union all select
3,192,0,0,0 union all select
4,181,0,0,0 union all select
5,179,0,0,0;

DENSE_RANK() is a rank with no gaps, i.e. it is “dense”.
select Name,EmailId,salary,DENSE_RANK() over(order by salary asc) from [dbo].[Employees]
RANK()-It contain gap between the rank.
select Name,EmailId,salary,RANK() over(order by salary asc) from [dbo].[Employees]

You have already grouped by ContenderNum, no need to partition again by it.
Use Dense_rank()and order by totals desc.
In short,
SELECT contendernum,totals, **DENSE_RANK()**
OVER (ORDER BY totals **DESC**)
AS xRank
FROM
(
SELECT ContenderNum ,SUM(Criteria1+Criteria2+Criteria3+Criteria4) AS totals
FROM dbo.Cat1GroupImpersonation
GROUP BY ContenderNum
) AS a

SELECT contendernum,totals, RANK() OVER (ORDER BY totals ASC) AS xRank FROM
(
SELECT ContenderNum ,SUM(Criteria1+Criteria2+Criteria3+Criteria4) AS totals
FROM dbo.Cat1GroupImpersonation
GROUP BY ContenderNum
) AS a

RANK() is good, but it assigns the same rank for equal or similar values. And if you need unique rank, then ROW_NUMBER() solves this problem
ROW_NUMBER() OVER (ORDER BY totals DESC) AS xRank

Select T.Tamil, T.English, T.Maths, T.Total, Dense_Rank()Over(Order by T.Total Desc) as Std_Rank From (select Tamil,English,Maths,(Tamil+English+Maths) as Total From Student) as T
enter image description here

Related

SQL Server: selecting distinct values per one column

I was wandering if it's possible to filter select results removing values that partially overlap
For example below, i have thousands of records, but i need the 'week date' value to be unqiue, and in case of duplicates the one with the highest value should remain.
emplo project_id Value week_Date week_ActualStart week_ActualEnd
A0001 project001 100 2015-12-28 2015-12-28 2016-01-03
A0001 project001 60 2015-12-28 2016-01-01 2016-01-03
So only the first row should remain.
I could really use someone's advice
Try something like the following:
;WITH WeekDateCte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY emplno, week_Date ORDER BY Value DESC) RowNo
FROM employee
)
SELECT *
FROM WeekDateCte
WHERE RowNo = 1
For more information about ROW_NUMBER function, check here.
NOTE: ROW_NUMBER() returns BIGINT.
You can use ROW_NUMBER for this:
SELECT emplno, project_id, Value, week_Date,
week_ActualStart, week_ActualEnd
FROM (
SELECT emplno, project_id, Value, week_Date,
week_ActualStart, week_ActualEnd,
ROW_NUMBER() OVER (PARTITION BY emplno, week_Date
ORDER BY Value DESC) AS rn
FROM mytable) AS t
WHERE t.rn = 1
The query picks the row having the greatest Value per emplno, week_Date slice.

Select a random row from each group SQL Server

I have a table like below
ID Code Age
----------------
1 4758 21
1 7842 14
1 9821 23
1 6842 9
2 8472 24
2 7558 31
2 7841 28
3 7881 38
3 8794 42
3 4871 43
For each ID, I want to select one of the rows at random like so
ID Code Age
----------------
1 7842 14
2 7841 28
3 4871 43
Is this possible in SQL Server?
select top 1 with ties id,code,age
from
table
order by row_number() over (partition by id order by rand())
Update: as per this Return rows in random order, you have to use NEWId,since RAND() is fixed for the duration of the SELECT on MS SQL Server.
select top 1 with ties id,code,age
from
table
order by row_number() over (partition by id order by NEWID())
Use Newid() in order by clause of Row_number()
SELECT [ID], [Code], [Age]
FROM (SELECT *,
Row_number()
OVER(
PARTITION BY ID
ORDER BY Newid()) RNO
FROM #Table1)A
WHERE RNO = 1
with cte as
(
select *,rank() over ( partition by id order by Newid()) as rn from #c
)
select id,code,age from cte where rn=1
To select different sets each time, use checksum(newid()) in the order by clause.
Query
;with cte as(
select *, rn = row_number() over(
partition by ID
order by abs(checksum(newid())) % 15
)
from [your_table_name]
)
select * from cte
where rn = 1;

SQL Server query should return max value records

I have table like this:
id_Seq_No emp_name Current_Property_value
-----------------------------------------------
1 John 100
2 Peter 200
3 Pollard 50
4 John 500
I want the max record value of particular employee.
For example, John has 2 records seq_no 1, 4. I want 4th seq_no Current_Property_Value in single query.
Select
max(id_Seq_No)
from
t1
where
emp_name = 'John'
To get the Current_Property_value, just order the results by id_Seq_No and get the first one:
SELECT
TOP 1 Current_Property_value
FROM
table
WHERE
emp_name = 'John'
ORDER BY
id_Seq_No DESC
this will give highest for all tied employees
select top 1 with ties
id_Seq_No,emp_name,Current_Property_value
from
table
order by
row_number() over (partition by emp_name order by Current_Property_value desc)
You can use ROW_NUMBER with CTE.
Query
;WITH CTE AS(
SELECT rn = ROW_NUMBER() OVER(
PARTITION BY emp_name
ORDER BY id_Seq_No DESC
), *
FROM your_table_name
WHERE emp_name = 'John'
)
SELECT * FROM CTE
WHERE rn = 1;

how to to get first top 6 records indifferent columns T-sql?

I got a situation to display first top 6 records. first 3 records in FirstCol and next 3 in SecondCol. My query is like this:
select top 6 [EmpName]
from [Emp ]
order by [Salary] Desc
Result:
[EmpName]
----------------------
Sam
Pam
Oliver
Jam
Kim
Nixon
But I want the result to look like this:
FirstCol SecondCol
Sam Jam
Pam Kim
Oliver Nixon
; WITH TOP_3 AS
(
select TOP 3 [EmpName]
,ROW_NUMBER() OVER (ORDER BY [Salary] Desc) rn
from [Emp ]
order by [Salary] Desc
),
Other3 AS
(
SELECT [EmpName]
,ROW_NUMBER() OVER (ORDER BY [Salary] Desc) rn
FROM Employees
ORDER BY [Salary] DESC OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY
)
SELECT T3.[EmpName] , O3.[EmpName]
FROM TOP_3 T3 INNER JOIN Other3 O3
ON T3.RN = O3.RN
ORDER BY T3.RN ASC
You can do this using several windowing functions, this is kind of ugly but it will get you the result that you want:
;with data as
(
-- get your Top 6
select top 6 empname, salary
from emp
order by salary desc
),
buckets as
(
-- use NTILE to split the six rows into 2 buckets
select empname,
nt = ntile(2) over(order by salary desc),
salary
from data
)
select
FirstCol = max(case when nt = 1 then empname end),
SecondCol = max(case when nt = 2 then empname end)
from
(
-- create a row number for each item in the buckets to return multiple rows
select empname,
nt,
rn = row_number() over(partition by nt order by salary desc)
from buckets
) d
group by rn;
See SQL Fiddle with Demo. This uses the function NTILE, this takes your dataset of six rows and splits it into two buckets - 3 rows in bucket 1 and 3 rows in bucket 2. The (2) inside the NTILE is used to determine the number of buckets.
Next I used row_number() to create a unique value for each row within each bucket, this allows you to return multiple rows for each column.

SQL Find pairs of data in rows and convert to columns

I'm trying to setup a query to pull employee tenure reports. I have an employee status table that tracks information for each employee (e.g. -Hire Date, Term Date, Salary Change, etc.) The table looks like this:
EmployeeID | Date | Event
1 | 1/1/99 | 1
2 | 1/2/99 | 1
1 | 1/3/99 | 2
1 | 1/4/99 | 1
I used a pivot table to move the table from a vertical layout to a horizontal layout
SELECT [FK_EmployeeID], MAX([1]) AS [Hire Date], ISNULL(MAX([2]), DATEADD(d, 1, GETDATE())) AS [Term Date]
FROM DT_EmployeeStatusEvents PIVOT (MAX([Date]) FOR [EventType] IN ([1], [2])) T
GROUP BY [FK_EmployeeID]
I get a result like this:
EmployeeID | 1 | 2
1 | 1/4/99 | 1/3/99
2 | 1/2/99 | *null*
However, the problem I run into is that I need both sets of values for each employee. (We hire a lot of recurring seasonals) What I would like is a way to convert the columns to rows selecting the hire date (1) and the very next term date (2) for each employee like this:
EmployeeID | 1 | 2
1 | 1/1/99 | 1/3/99
2 | 1/2/99 | *null*
1 | 1/4/99 | *null*
Is this possible? I've looked at a lot of the PIVOT examples and they all show an aggregate function.
The problem is that you are attempting to pivot a datetime value so you are limited to using either max or min as the aggregate function. When you use those you will only return one row for each employeeid.
In order to get past this you will need to have some value that will be used during the grouping of your data - I would suggest using a windowing function like row_number(). You can make your subquery:
select employeeid, date, event
, row_number() over(partition by employeeid, event
order by date) seq
from DT_EmployeeStatusEvents
See SQL Fiddle with Demo. This creates a unique value for each employeeId and event combination. This new number will then be grouped on so you can return multiple rows. You full query will be:
select employeeid, [1], [2]
from
(
select employeeid, date, event
, row_number() over(partition by employeeid, event
order by date) seq
from DT_EmployeeStatusEvents
) d
pivot
(
max(date)
for event in ([1], [2])
) piv
order by employeeid;
See SQL Fiddle with Demo
This should get you started...
DECLARE #EMP TABLE (EMPID INT, dDATE DATETIME, EVENTTYPE INT)
INSERT INTO #EMP
SELECT 1,'1/1/99',1 UNION ALL
SELECT 2,'1/2/99',1 UNION ALL
SELECT 1,'1/3/99',2 UNION ALL
SELECT 1,'1/4/99',1
SELECT EMPID, HIRE, TERM
FROM (SELECT EMPID, dDATE, 'HIRE' AS X, ROW_NUMBER() OVER(PARTITION BY EMPID, EVENTTYPE ORDER BY DDATE) AS INSTANCE FROM #EMP WHERE EVENTTYPE=1
UNION ALL
SELECT EMPID, dDATE, 'TERM' AS X, ROW_NUMBER() OVER(PARTITION BY EMPID, EVENTTYPE ORDER BY DDATE) AS INSTANCE FROM #EMP WHERE EVENTTYPE=2) DATATABLE
PIVOT (MIN([DDATE])
FOR X IN ([HIRE],[TERM])) PIVOTTABLE

Resources