Order rows by values - sql

I trying order table by rank, but rows which have position value - have to have position according to value in position field. It is possible do it without additional tables, views etc?
I have table like this:
rank | position | name
999 | 10 | txt1
200 | 4 | txt2
32 | 1 | txt3
1200 | 2 | txt4
123 | null | txt5
234 | null | txt6
567 | null | txt7
234 | null | txt8
432 | null | txt9
877 | null | txt10
Desired output have to look like this:
rank | position | name
32 | 1 | txt3
1200 | 2 | txt4
877 | null | txt10
200 | 4 | txt2
567 | null | txt7
432 | null | txt9
345 | null | txt8
234 | null | txt6
123 | null | txt5
999 | 10 | txt1

Here is an idea. Assign the proper ordering to each row. Then, if the position is available use that instead. When there are ties, put the position value first:
select t.*
from (select t.*, row_number() over (order by rank desc) as seqnum
from t
) t
order by (case when position is not null then position else seqnum end),
(case when position is not null then 1 else 2 end);
SQL Fiddle doesn't seem to be working these days, but this query demonstrates the results:
with t(rank, position, t) as (
select 999, 10, 'txt1' union all
select 200, 4, 'txt2' union all
select 32 , 1, 'txt3' union all
select 1200, 2, 'txt4' union all
select 123, null, 'txt5' union all
select 234, null, 'txt6' union all
select 567, null, 'txt7' union all
select 234, null, 'txt8' union all
select 432, null, 'txt9' union all
select 877, null , 'txt10'
)
select t.*
from (select t.*, row_number() over (order by rank desc) as seqnum
from t
) t
order by (case when position is not null then position else seqnum end),
(case when position is not null then 1 else 2 end);
EDIT;
When I wrote the above, I had a nagging suspicion of a problem. Here is a solution that should work. It is more complicated but it does produce the right numbers:
with t(rank, position, t) as (
select 999, 10, 'txt1' union all
select 200, 4, 'txt2' union all
select 32 , 1, 'txt3' union all
select 1200, 2, 'txt4' union all
select 123, null, 'txt5' union all
select 234, null, 'txt6' union all
select 567, null, 'txt7' union all
select 234, null, 'txt8' union all
select 432, null, 'txt9' union all
select 877, null , 'txt10'
)
select *
from (select t.*, g.*,
row_number() over (partition by t.position order by t.rank) gnum
from generate_series(1, 10) g(n) left join
t
on t.position = g.n
) tg left join
(select t.*,
row_number() over (partition by t.position order by t.rank) as tnum
from t
) t
on tg.gnum = t.tnum and t.position is null
order by n;
This is a weird sort of interleaving problem. The idea is to create slots (using generate series) for the positions. Then, assign the known positions to the slots. Finally, enumerate the remaining slots and assign the values there.
Note: I hard-coded 10, but it is easy enough to put in count(*) from the table there.

suppose you stored data in table1.
Then you should update column "position" as follows:
update a
set position = x.pos_null
from table1
a
inner join
(
select
a.name,
COUNT(a.rank) as pos_null
from
(
select
*
from table1
where position is null
)
a
left join
(
select
*
from table1
)
b
on a.rank <= b.rank
group by
a.name
)
x
on a.name = x.name
select * from table1 order by position
Bye,
Angelo.

Related

How to select and transfer specific string values into new column in SQL?

I have a table of items that can be arranged into groups. The table contains an attribute column with string values, as an example I chose color. For some items, the color column is empty.
ItemID
Group
Color
001
A
'blue'
002
A
'blue'
003
A
'blue'
004
A
'blue'
005
A
101
B
'red'
102
B
'red'
103
B
104
B
105
B
'green'
From this table I want to select only the items that do not have a color assigned yet and display a proposed color in a new column. The proposed color should be taken from the item with the highest ID value within each group.
The desired output should look like this:
ItemID
Group
Proposed Color
005
A
'blue'
103
B
'green'
104
B
'green'
I have no idea how to select specific values from the color column based on values from another column (in this case ItemID) and assign them to a different row within my table.
Any help (also keywords describing the concept associated with my problem...) would be greatly appreciated!
(edit) This is what I've tried so far:
WITH temp_id AS (
SELECT max(ItemID), GroupName
FROM myTable
WHERE Color IS NOT NULL
GROUP BY GroupName
)
SELECT myTable.*, temp_id.*
FROM myTable
JOIN temp_id
ON temp_id.groupName = myTable.groupName;
But the output is missing the color column and I wasn't able to add a condition WHERE Color IS NOT NULL to my outer selection.
As you're close, I'll give you the answer, feel free to ask if you don't understand :
SQL Fiddle
Query 1:
-- full query
SELECT mt.itemID, mt.Group, tColor.Color as proposedColor
FROM myTable as mt
LEFT JOIN (
SELECT tMax.Group, tC.Color
FROM (
SELECT max(t.ItemID) ItemID, t.Group
FROM myTable as t
WHERE t.Color IS NOT NULL
GROUP BY t.Group
) tMax
LEFT JOIN myTable tC
ON tC.ItemID = tMax.ItemID
) tColor
ON tColor.Group = mt.Group
WHERE mt.Color IS NULL
Results:
| itemID | Group | proposedColor |
|--------|-------|---------------|
| 5 | A | blue |
| 103 | B | green |
| 104 | B | green |
Query 2:
-- details :
-- 1) the get max id per group
SELECT max(t.ItemID) ItemID, t.Group
FROM myTable as t
WHERE t.Color IS NOT NULL
GROUP BY t.Group
Results:
| ItemID | Group |
|--------|-------|
| 4 | A |
| 105 | B |
Query 3:
-- 2) the color per group
SELECT tMax.Group, tC.Color
FROM (
SELECT max(t.ItemID) ItemID, t.Group
FROM myTable as t
WHERE t.Color IS NOT NULL
GROUP BY t.Group
) tMax
LEFT JOIN myTable tC
ON tC.ItemID = tMax.ItemID
Results:
| Group | Color |
|-------|-------|
| A | blue |
| B | green |
Query 4:
-- 3) the row that need a proposed color
SELECT mt.itemID, mt.Group, null as proposedColor
FROM myTable as mt
-- LEFT JOIN...
WHERE mt.Color IS NULL
Results:
| itemID | Group | proposedColor |
|--------|-------|---------------|
| 5 | A | (null) |
| 103 | B | (null) |
| 104 | B | (null) |
You have to join again with myTable in order to get the Color of the MaxID you calculated in the temp_id table:
WITH temp_id AS (
SELECT max(ItemID) AS MaxId, GroupName
FROM myTable
WHERE Color IS NOT NULL
GROUP BY GroupName
)
SELECT myTable.*, T2.Color
FROM myTable
JOIN temp_id
ON temp_id.groupName = myTable.groupName
JOIN myTable T2
ON T2.ItemID = temp_id.MaxID;
Fiddle here.
This could work as well:
WITH test_data AS
(
SELECT '001' AS "item_id", 'A' AS "group", 'blue' AS "color" FROM DUAL UNION ALL
SELECT '002', 'A', 'blue' FROM DUAL UNION ALL
SELECT '003', 'A', 'blue' FROM DUAL UNION ALL
SELECT '004', 'A', 'blue' FROM DUAL UNION ALL
SELECT '005', 'A', NULL FROM DUAL UNION ALL
SELECT '101', 'B', 'red' FROM DUAL UNION ALL
SELECT '102', 'B', 'red' FROM DUAL UNION ALL
SELECT '103', 'B', NULL FROM DUAL UNION ALL
SELECT '104', 'B', NULL FROM DUAL UNION ALL
SELECT '105', 'B', 'green' FROM DUAL
)
SELECT items."item_id", items."group", colors."proposed color"
FROM (SELECT td."group", MIN(td."color") AS "proposed color"
FROM test_data td
GROUP BY td."group") colors
INNER JOIN (SELECT td."item_id", td."group"
FROM test_data td
WHERE td."color" IS NULL) items
ON items."group" = colors."group"
ORDER BY items."item_id";
You can use a MAX window function to retrieve the value of "Color" with respect to each ItemID, then get the rows that had "Color" assigned null:
SELECT ItemID,
Group_,
MaxColor AS Color
FROM (SELECT *,
MAX(Color) OVER(
PARTITION BY Group_
ORDER BY CAST(ItemID AS UNSIGNED)) AS MaxColor
FROM tab) cte
WHERE Color IS NULL
Check the demo here.
Note: If you don't care about selecting only those specific rows with NULL values, you can just use the window function as follows.
SELECT ItemID,
Group,
COALESCE(Color, MAX(Color) OVER(
PARTITION BY Group
ORDER BY CAST(ItemID AS UNSIGNED))) AS Color
FROM tab
The COALESCE function will give priority to the Color field, and when it is null, it will catch the MAX window function output.

Select rows when a value appears multiple times

I have a table like this one:
+------+------+
| ID | Cust |
+------+------+
| 1 | A |
| 1 | A |
| 1 | B |
| 1 | B |
| 2 | A |
| 2 | A |
| 2 | A |
| 2 | B |
| 3 | A |
| 3 | B |
| 3 | B |
+------+------+
I would like to get the IDs that have at least two times A and two times B. So in my example, the query should return only the ID 1,
Thanks!
In MySQL:
SELECT id
FROM test
GROUP BY id
HAVING GROUP_CONCAT(cust ORDER BY cust SEPARATOR '') LIKE '%aa%bb%'
In Oracle
WITH cte AS ( SELECT id, LISTAGG(cust, '') WITHIN GROUP (ORDER BY cust) custs
FROM test
GROUP BY id )
SELECT id
FROM cte
WHERE custs LIKE '%aa%bb%'
I would just use two levels of aggregation:
select id
from (select id, cust, count(*) as cnt
from t
where cust in ('A', 'B')
group by id, cust
) ic
group by id
having count(*) = 2 and -- both customers are in the result set
min(cnt) >= 2 -- and there are at least two instances
This is one option; lines #1 - 13 represent sample data. Query you might be interested in begins at line #14.
SQL> with test (id, cust) as
2 (select 1, 'a' from dual union all
3 select 1, 'a' from dual union all
4 select 1, 'b' from dual union all
5 select 1, 'b' from dual union all
6 select 2, 'a' from dual union all
7 select 2, 'a' from dual union all
8 select 2, 'a' from dual union all
9 select 2, 'b' from dual union all
10 select 3, 'a' from dual union all
11 select 3, 'b' from dual union all
12 select 3, 'b' from dual
13 )
14 select id
15 from (select
16 id,
17 sum(case when cust = 'a' then 1 else 0 end) suma,
18 sum(case when cust = 'b' then 1 else 0 end) sumb
19 from test
20 group by id
21 )
22 where suma = 2
23 and sumb = 2;
ID
----------
1
SQL>
You can use group by and having for the relevant Cust ('A' , 'B')
And query twice (I chose to use with to avoid multiple selects and to cache it)
with more_than_2 as
(
select Id, Cust, count(*) c
from tab
where Cust in ('A', 'B')
group by Id, Cust
having count(*) >= 2
)
select *
from tab
where exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'A')
and exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'B')
What you want is a perfect candidate for match_recognize. Here you go:
select id_ as id from t
match_recognize
(
order by id, cust
measures id as id_
pattern (A {2, } B {2, })
define A as cust = 'A',
B as cust = 'B'
)
Output:
Regards,
Ranagal

Count Top 5 Elements spread over rows and columns

Using T-SQL for this table:
+-----+------+------+------+-----+
| No. | Col1 | Col2 | Col3 | Age |
+-----+------+------+------+-----+
| 1 | e | a | o | 5 |
| 2 | f | b | a | 34 |
| 3 | a | NULL | b | 22 |
| 4 | b | c | a | 55 |
| 5 | b | a | b | 19 |
+-----+------+------+------+-----+
I need to count the TOP 3 names (Ordered by TotalCount DESC) across all rows and columns, for 3 Age groups: 0-17, 18-49, 50-100. Also, how do I ignore the NULLS from my results?
If it's possible, how I can also UNION the results for all 3 age groups into one output table to get 9 results (TOP 3 x 3 Age groups)?
Output for only 1 Age Group: 18-49 would look like this:
+------+------------+
| Name | TotalCount |
+------+------------+
| b | 4 |
| a | 3 |
| f | 1 |
+------+------------+
You need to unpivot first your table and then exclude the NULLs. Then do a simple COUNT(*):
WITH CteUnpivot(Name, Age) AS(
SELECT x.*
FROM tbl t
CROSS APPLY ( VALUES
(col1, Age),
(col2, Age),
(col3, Age)
) x(Name, Age)
WHERE x.Name IS NOT NULL
)
SELECT TOP 3
Name, COUNT(*) AS TotalCount
FROM CteUnpivot
WHERE Age BETWEEN 18 AND 49
GROUP BY Name
ORDER BY COUNT(*) DESC
ONLINE DEMO
If you want to get the TOP 3 for each age group:
WITH CteUnpivot(Name, Age) AS(
SELECT x.*
FROM tbl t
CROSS APPLY ( VALUES
(col1, Age),
(col2, Age),
(col3, Age)
) x(Name, Age)
WHERE x.Name IS NOT NULL
),
CteRn AS (
SELECT
AgeGroup =
CASE
WHEN Age BETWEEN 0 AND 17 THEN '0-17'
WHEN Age BETWEEN 18 AND 49 THEN '18-49'
WHEN Age BETWEEN 50 AND 100 THEN '50-100'
END,
Name,
COUNT(*) AS TotalCount
FROM CteUnpivot
GROUP BY
CASE
WHEN Age BETWEEN 0 AND 17 THEN '0-17'
WHEN Age BETWEEN 18 AND 49 THEN '18-49'
WHEN Age BETWEEN 50 AND 100 THEN '50-100'
END,
Name
)
SELECT
AgeGroup, Name, TotalCount
FROM(
SELECT *,
rn = ROW_NUMBER() OVER(PARTITION BY AgeGroup, Name ORDER BY TotalCount DESC)
FROM CteRn
) t
WHERE rn <= 3;
ONLINE DEMO
The unpivot technique using CROSS APPLY and VALUES:
An Alternative (Better?) Method to UNPIVOT (SQL Spackle) by Dwain Camps
You can check below multiple-CTE SQL select statement
Row_Number() with Partition By clause is used ordering records within each group categorized by ages
/*
CREATE TABLE tblAges(
[No] Int,
Col1 VarChar(10),
Col2 VarChar(10),
Col3 VarChar(10),
Age SmallInt
)
INSERT INTO tblAges VALUES
(1, 'e', 'a', 'o', 5),
(2, 'f', 'b', 'a', 34),
(3, 'a', NULL, 'b', 22),
(4, 'b', 'c', 'a', 55),
(5, 'b', 'a', 'b', 19);
*/
;with cte as (
select
col1 as col, Age
from tblAges
union all
select
col2, Age
from tblAges
union all
select
col3, Age
from tblAges
), cte2 as (
select
col,
case
when age < 18 then '0-17'
when age < 50 then '18-49'
else '50-100'
end as grup
from cte
where col is not null
), cte3 as (
select
grup,
col,
count(grup) cnt
from cte2
group by
grup,
col
)
select * from (
select
grup, col, cnt, ROW_NUMBER() over (partition by grup order by cnt desc) cnt_grp
from cte3
) t
where cnt_grp <= 3
order by grup, cnt

Difference between the values of multiple rows in SQL

My table in SQL is like:-
RN Name value1 value2 Timestamp
1 Mark 110 210 20160119
1 Mark 106 205 20160115
1 Mark 103 201 20160112
2 Steve 120 220 20151218
2 Steve 111 210 20151210
2 Steve 104 206 20151203
Desired Output:-
RN Name value1Lag1 value1lag2 value2lag1 value2lag2
1 Mark 4 3 5 4
2 Steve 9 7 10 4
The difference is calculated from the most recent to the second recent and then from second recent to the third recent for RN 1
value1lag1 = 110-106 =4
value1lag2 = 106-103 = 3
value2lag1 = 210-205 = 5
value2lag2 = 205-201 = 4
similarly for other RN's also.
Note: For each RN there are 3 and only 3 rows.
I have tried in several ways by taking help from similar posts but no luck.
I've assumed that RN and Name are linked here. It's a bit messy, but if each RN always has 3 values and you always want to check them in this order, then something like this should work.
SELECT
t1.Name
, AVG(CASE WHEN table_ranked.Rank = 1 THEN table_ranked.value1 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value1 ELSE NULL END) value1Lag1
, AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value1 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 3 THEN table_ranked.value1 ELSE NULL END) value1Lag2
, AVG(CASE WHEN table_ranked.Rank = 1 THEN table_ranked.value2 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value2 ELSE NULL END) value2Lag1
, AVG(CASE WHEN table_ranked.Rank = 2 THEN table_ranked.value2 ELSE NULL END) - AVG(CASE WHEN table_ranked.Rank = 3 THEN table_ranked.value2 ELSE NULL END) value2Lag2
FROM table t1
INNER JOIN
(
SELECT
t1.Name
, t1.value1
, t1.value2
, COUNT(t2.TimeStamp) Rank
FROM table t1
INNER JOIN table t2
ON t2.name = t1.name
AND t1.TimeStamp <= t2.TimeStamp
GROUP BY t1.Name, t1.value1, t1.value2
) table_ranked
ON table_ranked.Name = t1.Name
GROUP BY t1.Name
There are other answers here, but I think your problem is calling for analytic functions, specifically LAG():
select
rn,
name,
-- calculate the differences
value1 - v1l1 value1lag1,
v1l1 - v1l2 value1lag2,
value2 - v2l1 value2lag1,
v2l1 - v2l2 value2lag2
from (
select
rn,
name,
value1,
value2,
timestamp,
-- these two are the values from the row before this one ordered by timestamp (ascending)
lag(value1) over(partition by rn, name order by timestamp asc) v1l1,
lag(value2) over(partition by rn, name order by timestamp asc) v2l1
-- these two are the values from two rows before this one ordered by timestamp (ascending)
lag(value1, 2) over(partition by rn, name order by timestamp asc) v1l2,
lag(value2, 2) over(partition by rn, name order by timestamp asc) v2l2
from (
select
1 rn, 'Mark' name, 110 value1, 210 value2, '20160119' timestamp
from dual
union all
select
1 rn, 'Mark' name, 106 value1, 205 value2, '20160115' timestamp
from dual
union all
select
1 rn, 'Mark' name, 103 value1, 201 value2, '20160112' timestamp
from dual
union all
select
2 rn, 'Steve' name, 120 value1, 220 value2, '20151218' timestamp
from dual
union all
select
2 rn, 'Steve' name, 111 value1, 210 value2, '20151210' timestamp
from dual
union all
select
2 rn, 'Steve' name, 104 value1, 206 value2, '20151203' timestamp
from dual
) data
)
where
-- return only the rows that have defined values
v1l1 is not null and
v1l2 is not null and
v2l1 is not null and
v2l1 is not null
This approach has the benefit that Oracle does all the necessary buffering internally, avoiding self-joins and the like. For big data sets this can be important from a performance viewpoint.
As an example, the explain plan for that query would be something like
-------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 150 | 13 (8)| 00:00:01 |
|* 1 | VIEW | | 6 | 150 | 13 (8)| 00:00:01 |
| 2 | WINDOW SORT | | 6 | 138 | 13 (8)| 00:00:01 |
| 3 | VIEW | | 6 | 138 | 12 (0)| 00:00:01 |
| 4 | UNION-ALL | | | | | |
| 5 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 8 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 9 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 10 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("V1L1" IS NOT NULL AND "V1L2" IS NOT NULL AND "V2L1" IS
Note that there are no joins, just a WINDOW SORT that buffers the necessary data from the "data source" (in our case, the VIEW 3 that is the UNION ALL of our SELECT ... FROM DUAL) to partition and calculate the different lags.
if just in this case, it's not that difficult.you need 2 steps
self join and get the result of minus
select t1.RN,
t1.Name,
t1.rm,
t2.value1-t1.value1 as value1,
t2.value2-t1.value2 as value2
from
(select RN,Name,value1,value2,
row_number(partition by Name order by Timestamp desc) as rm from table)t1
left join
(select RN,Name,value1,value2,
row_number(partition by Name order by Timestamp desc) as rm from table) t2
on t1.rm = t2.rm-1
where t2.RN is not null.
you set this as a table let's say table3.
2.you pivot it
select * from (
select t3.RN, t3.Name,t3.rm,t3.value1,t3.value2 from table3 t3
)
pivot
(
max(value1)
for rm in ('1','2')
)v1
3.you get 2 pivot table for value1 and value2 join them together to get the result.
but i think there may be a better way and i m not sure if we can just join pivot when we pivot it so i ll use join after i get the pivot result that will make 2 more tables. its not good but the best i can do
-- test data
with data(rn,
name,
value1,
value2,
timestamp) as
(select 1, 'Mark', 110, 210, to_date('20160119', 'YYYYMMDD')
from dual
union all
select 1, 'Mark', 106, 205, to_date('20160115', 'YYYYMMDD')
from dual
union all
select 1, 'Mark', 103, 201, to_date('20160112', 'YYYYMMDD')
from dual
union all
select 2, 'Steve', 120, 220, to_date('20151218', 'YYYYMMDD')
from dual
union all
select 2, 'Steve', 111, 210, to_date('20151210', 'YYYYMMDD')
from dual
union all
select 2, 'Steve', 104, 206, to_date('20151203', 'YYYYMMDD') from dual),
-- first transform value1, value2 to value_id (1,2), value
data2 as
(select d.rn, d.name, 1 as val_id, d.value1 as value, d.timestamp
from data d
union all
select d.rn, d.name, 2 as val_id, d.value2 as value, d.timestamp
from data d)
select * -- find previous row P of row D, evaluate difference and build column name as desired
from (select d.rn,
d.name,
d.value - p.value as value,
'value' || d.val_id || 'Lag' || row_number() over(partition by d.rn, d.val_id order by d.timestamp desc) as col
from data2 p, data2 d
where p.rn = d.rn
and p.val_id = d.val_id
and p.timestamp =
(select max(pp.timestamp)
from data2 pp
where pp.rn = p.rn
and pp.val_id = p.val_id
and pp.timestamp < d.timestamp))
-- pivot
pivot(sum(value) for col in('value1Lag1',
'value1Lag2',
'value2Lag1',
'value2Lag2'));

Using Case in a select statement

Consider the following table
create table temp (id int, attribute varchar(25), value varchar(25))
And values into the table
insert into temp select 100, 'First', 234
insert into temp select 100, 'Second', 512
insert into temp select 100, 'Third', 320
insert into temp select 101, 'Second', 512
insert into temp select 101, 'Third', 320
I have to deduce a column EndResult which is dependent on 'attribute' column. For each id, I have to parse through attribute values in the order
First, Second, Third and choose the very 1st value which is available i.e. for id = 100, EndResult should be 234 for the 1st three records.
Expected result:
| id | EndResult |
|-----|-----------|
| 100 | 234 |
| 100 | 234 |
| 100 | 234 |
| 101 | 512 |
| 101 | 512 |
I tried with the following query in vain:
select id, case when isnull(attribute,'') = 'First'
then value
when isnull(attribute,'') = 'Second'
then value
when isnull(attribute,'') = 'Third'
then value
else '' end as EndResult
from
temp
Result
| id | EndResult |
|-----|-----------|
| 100 | 234 |
| 100 | 512 |
| 100 | 320 |
| 101 | 512 |
| 101 | 320 |
Please suggest if there's a way to get the expected result.
You can use analytical function like dense_rank to generate a numbering, and then select those rows that have the number '1':
select
x.id,
x.attribute,
x.value
from
(select
t.id,
t.attribute,
t.value,
dense_rank() over (partition by t.id order by t.attribute) as priority
from
Temp t) x
where
x.priority = 1
In your case, you can conveniently order by t.attribute, since their alphabetical order happens to be the right order. In other situations you could convert the attribute to a number using a case, like:
order by
case t.attribute
when 'One' then 1
when 'Two' then 2
when 'Three' then 3
end
In case the attribute column have different values which are not in alphabetical order as is the case above you can write as:
with cte as
(
select id,
attribute,
value,
case attribute when 'First' then 1
when 'Second' then 2
when 'Third' then 3 end as seq_no
from temp
)
, cte2 as
(
select id,
attribute,
value,
row_number() over ( partition by id order by seq_no asc) as rownum
from cte
)
select T.id,C.value as EndResult
from temp T
join cte2 C on T.id = C.id and C.rownum = 1
DEMO
Here is how you can achieve this using ROW_NUMBER():
WITH t
AS (
SELECT *
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY (CASE attribute WHEN 'First' THEN 1
WHEN 'Second' THEN 2
WHEN 'Third' THEN 3
ELSE 0 END)
) rownum
FROM TEMP
)
SELECT id
,(
SELECT value
FROM t t1
WHERE t1.id = t.id
AND rownum = 1
) end_result
FROM t;
For testing purpose, please see SQL Fiddle demo here:
SQL Fiddle Example
keep it simple
;with cte as
(
select row_number() over (partition by id order by (select 1)) row_num, id, value
from temp
)
select t1.id, t2.value
from temp t1
left join cte t2
on t1.Id = t2.id
where t2.row_num = 1
Result
id value
100 234
100 234
100 234
101 512
101 512