Average and sort by this based on other conditional columns in a table - sql

I have a table in SQL Server 2017 like below:
Name Rank1 Rank2 Rank3 Rank4
Jack null 1 1 3
Mark null 3 2 2
John null 2 3 1
What I need to do is to add an average rank column then rank those names based on those scores. We ignore null ranks. Expected output:
Name Rank1 Rank2 Rank3 Rank4 AvgRank FinalRank
Jack null 1 1 3 1.66 1
Mark null 3 2 2 2.33 3
John null 2 3 1 2 2
My query now looks like this:
;with cte as (
select *, AvgRank= (Rank1+Rank2+Rank3+Rank4)/#NumOfRankedBy
from mytable
)
select *, FinakRank= row_number() over (order by AvgRank)
from cte
I am stuck at finding the value of #NumOfRankedBy, which should be 3 in our case because Rank1 is null for all.
What is the best way to approach such an issue?
Thanks.

Your conumdrum stems from the fact your table in not normalised and you are treating data (Rank) as structure (columns).
You should have a table for Ranks where each rank is a row, then your query is easy.
You can unpivot your columns into rows and then make use of avg
select *, FinakRank = row_number() over (order by AvgRank)
from mytable
cross apply (
select Avg(r * 1.0) AvgRank
from (values(rank1),(rank2),(rank3),(rank4))r(r)
)r;

Related

SQL select 1 row out of several rows that have similar values

I have a table like this:
ID
OtherID
Date
1
z
2022-09-19
1
b
2021-04-05
2
e
2022-04-05
3
t
2022-07-08
3
z
2021-03-02
I want a table like this:
ID
OtherID
Date
1
z
2022-09-19
2
e
2022-04-05
3
t
2022-07-08
That have distinct pairs consisted of ID-OtherID based on the Date values which are the most recent.
The problem I have now is the relationship between ID and OtherID is 1:M
I've looked at SELECT DISTINCT, GROUP BY, LAG but I couldn't figure it out. I'm sorry if this is a duplicate question. I couldn't find the right keywords to search for the answer.
Update: I use Postgres but would like to know other SQL as well.
This works for many dbms (versions of postgres, mysql and others) but you may need to adapt if something else. You could use a CTE, or a join, or a subquery such as this:
select id, otherid, date
from (
select id, otherid, date,
rank() over (partition by id order by date desc) as id_rank
from my_table
)z
where id_rank = 1
id
otherid
date
1
z
2022-09-19T00:00:00.000Z
2
e
2022-04-05T00:00:00.000Z
3
t
2022-07-08T00:00:00.000Z
You can use a Common Table Expression (CTE) with ROW_NUMBER() to assign a row number based on the ID column (then return the first row for each ID in the WHERE clause rn = 1):
WITH cte AS
(SELECT ID,
OtherID,
Date,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Date DESC) AS rn
FROM sample_table)
SELECT ID,
OtherID,
Date
FROM cte
WHERE rn = 1;
Result:
ID
OtherID
Date
1
z
2022-09-19
2
e
2022-04-05
3
t
2022-07-08
Fiddle here.

How to produce a circular shift with postgreSQL

Let's suppose that I have this simple table:
ID
1
2
3
4
If I do:
WITH example AS
(
SELECT UNNEST(ARRAY[1,2,3,4]) as ID
)
SELECT ID, lAG(ID,1) over() as LAG_ID
FROM example
-- The shift is 1 in this case, but could be any integer in practice.
I got:
ID
LAG_ID
1
2
1
3
2
4
3
But I need a circular shift:
ID
LAG_ID
1
4
2
1
3
2
4
3
Is there an elegant way to produce this result ?
If ID is not nullable
WITH example AS
(
SELECT UNNEST(ARRAY[1,2,3,4]) as ID
)
SELECT ID, coalesce(lAG(ID,1) over(order by ID asc), (select max(ID) from example)) as LAG_ID
FROM example

split no. of rows into equal batches

I have a table and would like to split the rows into batches(adding additional column like batch_no ).
e.g I have a table with Single column cust_no and total=20 rows and split them into 5 batches like 1,2,3,4 and 5.
expected output like...
cust_no batch_no
23123 1
2313 1
23 1
323123 1
123 1
23 2
213 2
123 2
2123 2
2123 2
23123 3
2313 3
23 3
323123 3
123 3
23 4
...
...
...
...
If you know the number of batches then use ntile():
select t.*,
ntile(5) over (order by null) as batch
from t;
Note: The order by null means that you do not care about the ordering. It is arbitrary and not reproducible. You might want to include some criteria -- say an id column or creation date or something so the batches are more homogenous.
If you want batches of a certain size, then use row_number() and arithmetic:
select t.*,
(row_number() over (order by null) % 4) as batch
from t;
In some databases, this would be:
select t.*,
mod(row_number() over (order by null), 4) as batch
from t;

sql - select single ID for each group with the lowest value

Consider the following table:
ID GroupId Rank
1 1 1
2 1 2
3 1 1
4 2 10
5 2 1
6 3 1
7 4 5
I need an sql (for MS-SQL) select query selecting a single Id for each group with the lowest rank. Each group needs to only return a single ID, even if there are two with the same rank (as 1 and 2 do in the above table). I've tried to select the min value, but the requirement that only one be returned, and the value to be returned is the ID column, is throwing me.
Does anyone know how to do this?
Use row_number():
select t.*
from (select t.*,
row_number() over (partition by groupid order by rank) as seqnum
from t
) t
where seqnum = 1;

SQL: create a query with a column identifying each tuple of values

I've been trying to find a solution for this SQL query:
|A| |B| |Output|
===========================
1 A 1
1 A 1
1 B 2
1 C 3
2 C 1
2 D 2
2 F 3
2 H 4
3 D 1
3 D 1
3 I 2
As you can see, I want to create a column (output) that identifies, not counts or sums, the occurrence of each (A,B) tuple, without removing repeated occurences.
How would you do that?
Thanks in advance
Look up SQL Server Built-in DENSE_RANK() function.
SELECT *
,DENSE_RANK() OVER (PARTITION BY [A] ORDER BY [B] ASC) AS [OutPut]
FROM TableName
I think you want dense_rank():
select t.*,
dense_rank() over (partition by a order by b)
from table t;