TSQL - Rank Values but return Column Names - sql

I have a table of data points that I need ranked by column.
I have about 500k Id's and 25 columns (as of now).
I'd like to make the query dynamic so that any added columns will not require a code change.
For each ID, I want to find column names of the top 3 values.
The results should be:
[Id]
[Rank1]
[Rank2]
[Rank3]
27807745
Value3
Value2
Value8
96448378
Value6
Value5
Value1
etc
My first attempt was to create a joined table of Id's and column names:
[Id]
[Value1]
[Value]
27807745
Value1
NULL
27807745
Value2
NULL
27807745
Value3
NULL
27807745
Value4
NULL
27807745
Value5
NULL
Then run a looped update, then sequence by Id, Value DESC.
This gets me there but is taking over 2 hours to complete.
I looked at PIVOT and UNPIVOT but both want to return the values, not the column names.
Is there any other way to do this?

Here is an option that will dynamically unpivot your data WITHOUT using Dynamic SQL
Example or dbFiddle
Select A.ID
,B.*
From YourTable A
Cross Apply (
Select Rank1 = max(case when Rnk=1 then [Key] end)
,Rank2 = max(case when Rnk=2 then [Key] end)
,Rank3 = max(case when Rnk=3 then [Key] end)
From (
Select [Key]
,Value
,Rnk = row_number() over (order by convert(int,value) desc)
From OpenJson((Select A.* For JSON Path,Without_Array_Wrapper ))
Where [Key] Not IN( 'ID','Other','Columns','ToExclude')
) B1
) B

Related

SQL Server - Create subset table having 2 largest values in row

I have a table (11 columns) having only one row. It contains below records:
ID Data_Type Value1 Value2 Value3 Value4 Value5 Value6 Value7 Value8 Value9
1 A_1 08/03/2020 08/03/2020 08/03/2020 08/02/2021 08/02/2021 08/02/2021 08/09/2022 08/09/2021 08/09/2024
I need a subset table with below records (Only 2 columns having latest date values)
ID Data_Type Value9 Value7
1 A_1 08/09/2024 08/09/2022
Please help.
If possible, I'd first change your data structure and merge all value columns into one as values are a lot easier to compare with each other when they're in the same column (like trying to sort and filter values in an Excel spreadsheet - You would usually transpose the columns into one then sort/filter it).
One way that you can merge all value columns into one column is by using UNION ALL:
SELECT ID, Data_Type, Value1 AS NewValue INTO NewTable FROM Table UNION ALL
SELECT ID, Data_Type, Value2 FROM Table UNION ALL
SELECT ID, Data_Type, Value3 FROM Table UNION ALL
SELECT ID, Data_Type, Value4 FROM Table UNION ALL
SELECT ID, Data_Type, Value5 FROM Table UNION ALL
SELECT ID, Data_Type, Value6 FROM Table UNION ALL
SELECT ID, Data_Type, Value7 FROM Table UNION ALL
SELECT ID, Data_Type, Value8 FROM Table UNION ALL
SELECT ID, Data_Type, Value9 FROM Table
After you've done this and all the new data is in NewTable, you can use the following query to extract the top 2 values:
SELECT TOP 2 *
FROM NewTable
ORDER BY NewValue DESC
You can unpivot the data using APPLY and then use some aggregation logic:
select t.id, t.data_type, v.*
from t cross apply
(select max(case when seqnum = 1 then date end) as date_1,
max(case when seqnum = 1 then value end) as date_1,
max(case when seqnum = 2 then date end) as date_2,
max(case when seqnum = 2 then value end) as date_2
from (select v.*, row_number() over (order by value desc) as seqnum
from (values (t.value1, 'value1'),
(t.value2, 'value2'),
(t.value3, 'value3'),
. . .
) v(date, value)
) v
) v;
Very important note: This adds four columns, not two columns. The name of the column with the maximum and penultimate value is in a separate column. You can only control the names of the columns if you use dynamic SQL.
The above will work fine on one row. But it will also work on multiple rows as well.

SQL Server - minimum value on row

I have a table with a single row and two columns. Can I obtain the minimum value from the row using an SQL query?
value1 value2
1 43 39
The query should return the value 39.
The simplest method is probably apply:
select t.*, v.min_val
from t cross apply
(select min(val) as min_val
from (values (value1), (value2)) v(val)
) v;
For just two values that are not-null, you a case expression is also simple:
select t.*,
(case when value1 < value2 then value1 else value2 end) as min_val
from t;
However, this does not ignore null values. And it does not generalize quite as easily as one would like.
Certainly Gordon's answer (+1) is more performant and would be my first choice, but if by chance you are looking for a more "generalized" version, here is a simplified JSON approach.
This may be helpful if you have numerous or variable columns.
Example
Declare #YourTable table (ID int,value1 int,value2 int)
Insert into #YourTable values
(1,43,39)
Select A.*
,B.*
From #YourTable A
Cross Apply (
Select RowMin = min(Value)
From ( Select Value=try_convert(int,Value) -- << Set desired Datatype
From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) )
Where [Key] not in ('ID')
) J
) B
Returns
ID value1 value2 RowMin
1 43 39 39

select row values as column based on GROUP BY column

I have a table with data like this:
Id Value
-------------------------
01 Id01-Value1
01 Id01-Value2
02 Id02-Value1
02 Id02-Value2
03 Id03-Value1
What I want is
Id Value1 Value2
--------------------------------------
01 Id01-Value1 Id01-Value2
02 Id02-Value1 Id02-Value2
03 Id03-Value1
I tried sql PIVOT but it is not for this type of problem I think.
I think you can just use min() and max():
select id, min(value) as value1,
(case when min(value) <> max(value) then max(value) end) as value2
from t
group by id;
Try this Answer,
SELECT ID
,MAX(CASE WHEN RN=1 THEN Value ELSE '' END)Value1
,MAX(CASE WHEN RN=2 THEN Value ELSE '' END)Value2
FROM(
SELECT ID,Value
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Value)RN
FROM Your_Table
)D
GROUP BY ID
ORDER BY ID
On the contrary, PIVOT is exactly what you need - if you can bear it's damned syntax! It is relatively flexible but also unwieldy.
SELECT
id
,[1] AS Value1
,[2] AS Value2
FROM
(
SELECT
id
,value
,ROW_NUMBER() OVER (PARTITION BY id ORDER BY value ASC) AS column_number
FROM
YOUR_TABLE_NAME
) AS src
PIVOT
(
MAX(value)
FOR column_number IN ([1],[2])
) AS pvt
ORDER BY
id
This will sort the rows in the value column alphabetically, and assigns column numbers accordingly in sequence (but you could include different logic, for example to nip the column number off the right-hand side of the value, or name the columns according to the value itself rather than numbering them). NULL values will be returned for any column that doesn't have a value.

SQL - sort of SUM with varchar

have a (weird) table looking like this
ID Version Value1 Value2 Value3
1 1 Shaft
1 2 steel xy
2 1 Knife somethins
2 3 Super
Want to merge, need to have this result, by using Value from the highest Version, that has content:
ID Value1 Value2 Value3
1 Shaft steel xy
2 Super Knife somethin
as far as I know Group using Max(Version) would bring the NULL values of highest Version row.
something like SUM?
Second try... There are probably shorter and nicer solutions, but it should work:
with
v1 as
(
select w1.id, w1.value1 from weird w1
where w1.value1 is not null
and w1.version=(select max(w11.version) from weird w11 where w11.id=w1.id and w11.value1 is not null)
),
v2 as
(
select w2.id, w2.value2 from weird w2
where w2.value2 is not null
and w2.version=(select max(w22.version) from weird w22 where w22.id=w2.id and w22.value2 is not null)
),
v3 as
(
select w3.id, w3.value3 from weird w3
where w3.value3 is not null
and w3.version=(select max(w33.version) from weird w33 where w33.id=w3.id and w33.value3 is not null)
)
select v1.id, v1.value1, v2.value2, v3.value3
from v1, v2, v3
where v1.id=v2.id and v1.id=v3.id;
We can use UNPIVOT and PIVOT creatively to construct the data you want:
declare #t table (ID int not null, Version int not null, Value1 varchar(20) null,
Value2 varchar(20) null, Value3 varchar(20) null)
insert into #t(ID,Version,Value1,Value2,Value3) values
(1,1,'Shaft',null,null),
(1,2,null,'steel','xy'),
(2,1,null,'Knife','somethins'),
(2,3,'Super',null,null)
;With Numberable as (
select *,ROW_NUMBER() OVER (PARTITION BY ID,Val ORDER BY Version desc) rn
from #t t
unpivot (tdata for Val in (Value1,Value2,Value3)) u
), Selected as (
select ID,tdata,Val
from Numberable where rn = 1
)
select
*
from Selected s
pivot (MAX(tdata) for Val in (Value1,Value2,Value3)) u
The UNPIVOT automatically removes the NULLs. The ROW_NUMBER() identifies the values we want to keep. The Selected CTE hides the columns we no longer need so that the PIVOT creates the final result we want:
ID Value1 Value2 Value3
----------- -------------------- -------------------- --------------------
1 Shaft steel xy
2 Super Knife somethins
(I'm using MAX in the pivot but that's just to satisfy the optimizer. Because we've only selected one row for each ID, Val combination, we know that at most one value will be selected to appear in a final position in the grid formed by the pivot)
The above does make the assumption that Value1,Value2 and Value3 all have the same, or at least compatible, data types.
You can rank the values with row_number. The following query first builds such ranks. rn1 is built per id and value1 is null/not null in the descending order of the version. So per ID we get #1 for the last null value and the last filled value. Later we use rn1 = 1 to get the maximum of the two, which is the last filled value. Same for rn2/value2 and rn3/value3.
select
id,
min(case when rn1 = 1 then value1 end) as value1,
min(case when rn2 = 1 then value2 end) as value2,
min(case when rn3 = 1 then value3 end) as value3
from
(
select
id, value1, value2, value3,
row_number() over (partition by id, case when value1 is null then 0 else 1 end order by version desc) as rn1,
row_number() over (partition by id, case when value2 is null then 0 else 1 end order by version desc) as rn2,
row_number() over (partition by id, case when value3 is null then 0 else 1 end order by version desc) as rn3
from mytable
) ranked
group by id
order by id;
Used CASE WHEN to SELECT max(version) where value is not null and not blank and then joinedwith the original table on those versions. You can see it in action in link provided below the query
Use this query.
Select distinct a.*, b.value1, c.value2, d.value3
from
(
Select id, max(case when (value1 is not null and value1 <> ' ') then version else 0 end) as ver1,
max(case when (value2 is not null and value2 <> ' ') then version else 0 end) as ver2,
max(case when (value3 is not null and value3 <> ' ') then version else 0 end) as ver3
from
your_table
group by id
) a
inner join
your_table b,
your_table c,
your_table d
where (a.ver1=b.version and a.id=b.id)
and (a.ver2=c.version and a.id=c.id)
and (a.ver3=d.version and a.id=d.id)
See it in action here at this link

How to Get row values as columns in SQL?

I have a table Test with two columns.
Id Value
1 A
1 B
1 C
I want to get the result like below,
Id Value1 Value2 value3
1 A B C
How can I done this in SQL Server.
This is a pivot, but you don't have a column for the pivoting. row_number() can provide that. I usually use conditional aggregations for this.
select id,
max(case when seqnum = 1 then value end) as value1,
max(case when seqnum = 2 then value end) as value2,
max(case when seqnum = 3 then value end) as value3
from (select t.*,
row_number() over (partition by id order by (select null)) as seqnum
from t
) t
group by id;
Note that SQL tables represent unordered sets. So, there is no information about ordering and the values could be in any order. If a column does specify the ordering, then include that in the order by rather than select null.