Select column value where other column is max of group - sql

I am trying to select two columns into a table (ID and state). The table should show the state with the maximum value for each ID. I've tried a few other examples but nothing seems to work.
Original data structure:
ID state value (FLOAT)
1 TX 921,294,481
1 SC 21,417,296
1 FL 1,378,132,290
1 AL 132,556,895
1 NC 288,176
1 GA 1,270,986,631
2 FL 551,374,452
2 LA 236,645,530
2 MS 2,524,536,050
2 AL 4,128,682,333
2 FL 1,503,991,028
The resulting data structure should therefore look like this:
ID STATE (Max Value)
1 FL
2 AL
Florida and Alabama having the largest values in their ID groups.
Any help would be greatly appreciated on this. I did find a SO answer here already, but could not make the answers work for me.

For SQL Server (and other products with windowed functions):
SELECT *
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY value desc) as rn
FROM
UnnamedTable
) t
WHERE
t.rn = 1

You can use a subquery to get this result:
select t1.id, t1.[state] MaxValue
from yourtable t1
inner join
(
select id, max(value) MaxVal
from yourtable
group by id
) t2
on t1.id = t2.id
and t1.value = t2.maxval
order by t1.id
See SQL Fiddle with Demo

A solution, based on the assumption that value is numeric:
SELECT
[ID],
[State],
[Value]
FROM
(
SELECT
[ID],
[State],
[Value],
Rank() OVER (PARTITION BY [ID] ORDER BY [Value] DESC) AS [Rank]
FROM [t1]
) AS [sub]
WHERE [sub].[Rank] = 1
ORDER BY
[ID] ASC,
[State] ASC
If multiple States with the same ID have the same Value, they would all get the same Rank. This is different from using Row_Number, which return unique row numbers, but the order is chosen arbitrarily. (See also: SQL RANK() versus ROW_NUMBER())

Related

Return two rows from SQL table with a difference in values [duplicate]

This question already has answers here:
How to request a random row in SQL?
(30 answers)
Closed 6 years ago.
iam trying to return 2 rows from table that have a difference in values, not being an SQL wise man i am stuck any help would be appreciated :-)
TABLE A:
NAME DATA
Oscar HOME1
Jens HOME2
Will HOME1
Jeremy HOME2
Al HOME1
Result, should be 2 random rows with a difference in DATA value
NAME DATA
Oscar HOME1
Jeremy HOME2
Anyone?
Easy way to have random data.
;with tblA as (
select name,data,
row_number() over(partition by data order by newid()) rn
from A
)
select name,data
from tblA
where rn = 1
Couuld be you need
select * from my_table a
inner join my_table b on a.data !=b.data
where a.data in ( SELECT data FROM my_table ORDER BY RAND() LIMIT 1);
For your code
SELECT *
FROM [dbo].[ComputerState] as a
INNER JOIN [dbo].[ComputerState] as b ON a.ServiceName != b.ServiceName
WHERE a.ServiceName IN (
SELECT top 1 [ServiceName] FROM [dbo].[ComputerState]
);
If the question is really this simple, you can use an aggregate such as MAX() or MIN() to grab one row for each different DATA:
SELECT MAX(NAME), DATA
FROM TABLE_A
GROUP BY DATA
Of course, if any other variables are introduced to the requirements, this may no longer work.
;WITH cteA AS (
SELECT
name
,data
,ROW_NUMBER() OVER (PARTITION BY data ORDER BY NEWID()) as DataRowNumber
,ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY NEWID()) as RandomRowNumber
FROM
A
)
SELECT *
FROM
cteA
WHERe
DataRowNumber = 1
AND RandomRowNumber <= 2
This Expands on #AlexKudryashev 's answer a little.
;with tblA as (
select name,data,
row_number() over(partition by data order by newid()) rn
from A
)
select name,data
from tblA
where rn = 1
The only issue with what he had Is that the number of Rows where rn = 1 will be depended on the COUNT(DISTINCT data) so it could lead to more than 2 results. To fix one could add a SELECT TOP 2 clause but it might not be fully random as results at that point as it will be dependent on the ordinal results of how SQL optimizes the query which is likely to be consistent. To get truly random add a second random row number and limit the results to the top 2 of those.

How do I get N records before given one?

How do I get N records before given one?
I have the following table structure:
Id, Message
1, John Doe
2, Jane Smith
3, Error
4, Jane Smith
5, Michael Pirs
7, Gabriel Angelos
8, Error
Is there a way to get the N records before each Error and join all such records?
So the expected result for the N =2 will be
1, John Doe
2, Jane Smith
5, Michael Pirs
7, Gabriel Angelos
Fiddle
You need to create a row number column if your Ids do not increment without gaps. Then you can use a simple join to find the previous N. Your previous N could overlap... so you have to add distinct if you do not want duplicates.
declare #N as integer
set #N=2
;with cte_tbl (Id, Message, rownum) AS
(
select *, ROW_NUMBER() over (order by id) as rownum from test
)
select distinct Prev.Id, Prev.Message
from cte_tbl
join cte_tbl Prev
on Prev.rownum between cte_tbl.rownum-#N and cte_tbl.rownum-1
where cte_tbl.Message = 'Error'
and Prev.Message <> 'Error'
order by Prev.Id
If the one of the previous #N records is an error, the 'error' record will NOT show up. This would have to be modified if you do want those to be included. Just simply remove the line and Prev.Message <> 'Error'.
You can do this using cross apply. The logic is a bit different from typical applications, because you only want the records from the cross apply subquery:
select t2.*
from table t cross apply
(select top 2 t.*
from table t2
where t2.id < t.id
order by t2.id desc
) t2
where t2.message = 'Error';
For those inclined, there is also a method using window functions, but it is a little more cumbersome. Do a reverse cumulative sum of Error records to identify values before a given error. Then enumerate these and choose the ones you want:
select t.id, t.message
from (select t.*, row_number() over (partition by grp order by id desc) as seqnum
from (select t.*,
sum(case when message = 'Error' then 1 else 0 end) over
(order by id desc)) as grp
from table t
) t
where seqnum between 2 and 3;
Note that the filter is between 2 and 3, because 'Error' has a value of 1.
Get all the rows that are 'Error' and join with id previous to it. Assuming your IDs are consecutive. If they aren't get a consecutive id with the help or ROW_NUMBER().
You can try this:
select
T.*
from (
select
id iderror
from myTable
where
Message = 'Error'
) errorRows
inner join myTable T on
T.id between errorRows.iderror -2 and errorRows.iderror -1 and
T.Message <> 'Error'
This would be a little bit easier if you were using an identity field for ID, then you would have continuous numbers, but you can use this method. I am Ranking the rows and then returning the ones prior to the error.
select t1.Rank_ID, t1.id, t1.message, te.id
from (select rank() over(order by id) as Rank_ID, id, message from tbl_test) t1
inner join (select rank() over(order by id)as Rank_ID, id, message from tbl_test) te
on t1.Rank_ID between te.Rank_ID-2 and te.Rank_ID-1
where te.message='Error'

How to update rows based only on ROW_NUMBER()?

Such SQL query:
SELECT ROW_NUMBER() OVER (PARTITION BY ID, YEAR order by ID ), ID, YEAR
from table t
give me following query set:
1 1000415591 2012
1 1000415591 2013
2 1000415591 2013
1 1000415591 2014
2 1000415591 2014
How could I update records with ROW_NUMBER() equals to 2? Other fields of this records is identically (select distinct from table where id = 1000415591 gives 3 records when there are 5 without distinct keyword), so I can depend only on ROW_NUMBER() value.
I need solution for Oracle, because I saw something similar for SQL-Server but it won't work with Oracle.
You could use a MERGE statement which is quite verbose and easy to understand.
For example,
MERGE INTO t s
USING
(SELECT ROW_NUMBER() OVER (PARTITION BY ID, YEAR order by ID ) RN,
ID,
YEAR
FROM TABLE t
) u ON (s.id = u.id)
WHEN MATCHED THEN
UPDATE SET YEAR = some_value WHERE u.RN = 2)
/
Note You cannot merge the same column which is used to join in the ON clause.
Try to use ROWID field:
UPDATE T
SET t.year = t.year*1000
WHERE (rowid,2) in (SELECT rowid,
ROW_NUMBER()
OVER (PARTITION BY ID, t.YEAR order by ID )
FROM T)
SQLFiddle demo
If you need to delete range of ROWNUMBERS then :
UPDATE T
SET t.year = t.year*1000
WHERE rowid in ( SELECT rowid FROM
(
SELECT rowid,
ROW_NUMBER()
OVER (PARTITION BY ID, t.YEAR order by ID ) as RN
FROM T
) T2 WHERE RN >=2 AND RN <=10
)
SQLFiddle demo
This is not the update statement but this is how to get the 2 rows you wanted to update:
SELECT *
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY ID, YEAR order by ID ) as rn, ID, YEAR
from t )
where rn = 2
SQLFIDDLE
When I've posted thq question, I've found that this could be wrong approach. I could modify table and add new fields. So better solution to create one more field IDENTITY and update it with numbers from the new sequence from 1 to total row numbers. Then I could update fields based on this IDENTIY field.
I'll keep this question opened if someone come up with solution based on ROW_NUMBER() analytic function.
update TABLE set NEW_ID = TABLE_SEQ.nextval
where IDENTITY in (
select IDENTITY from (
select row_number() over(PARTITION BY ID, YEAR order by ID) as row_num, t.ID, t."YEAR", t.IDENTITY
from TABLE t
) where row_num > 1
)

Updating field based on sorting of another

I have a user table which contains among others money, level and ranking.
Id | money| ranking| level
---------------------------
1 |30000| 1 1
2 |20000| 2 3
3 |10000| 3 2
4 |50000| 4 2
I want to update the ranking field based on user level (first filter) and money.
That is a user in higher level will always be ranked higher.
That is i want the table after the update like this:
Id | money| ranking| level
---------------------------
1 |30000| 4 1
2 |20000| 1 3
3 |10000| 3 2
4 |50000| 2 2
Thanks!
As a side note, I would NOT store this field within the database - storing values that are dependent on other records in the table make maintenance much more difficult.
Here's a query that would work as a view or within a stored procedure:
SELECT
ID,
[money],
ROW_NUMBER() OVER (order by [level] desc, [money] desc) AS [ranking],
[level]
FROM myTable
If you REALLY wanted to update the table just make the query a subquery to an update:
UPDATE m1
SET ranking = m2.ranking
FROM myTable m1
INNER JOIN
(SELECT
ID,
ROW_NUMBER() OVER (order by [level] desc, [money] desc) ranking
FROM myTable) m2
ON m1.ID = m2.ID
If you simply want to select then here is the query :
select *, dense_rank() over (order by level desc, mony desc) as newranking from YourTable
and if you want to update then :
;with cte_toupdate (ranking, newranking)
as (
select ranking, dense_rank() over (order by level desc, mony desc) as newranking from YourTable
)
Update cte_toupdate set ranking = newranking
select * from YourTable
check here : http://sqlfiddle.com/#!3/8d6d3/10
Note : if you want unique ranks then use Row_Number() instead of dense_rank().
CREATE TABLE #tt
(
id INT,
mony INT,
ranking INT,
levell INT
)
INSERT INTO #tt
VALUES (1,30000,1,1),
(2,20000,2,3),
(3,10000,3,2),
(4,50000,4,2)
UPDATE a
SET ranking = rnk
FROM #tt a
JOIN (SELECT Row_number()
OVER (
ORDER BY levell DESC, mony DESC) AS rnk,
*
FROM #tt) b
ON a.levell = b.levell
AND a.mony = b.mony

Group By Retrieve 4 Values

I have the following query
SELECT Cod ,
MIN(Id) AS id_Min,
-- retrieve value min in the middle as id_Min_Middle,
-- retrieve value max in the middle as id_Max_Middle,
MAX(Id) AS id_Max,
COUNT(*) AS Tot
FROM Table a ( NOLOCK )
GROUP BY Cod
HAVING COUNT(*)=4
How could I retrieve the values between min and max as I have done for min and max?
If I use (SUM(Id) - (MIN(Id)+MAX(Id)) I get the sum of middle min and max, but not the values I want.
EXAMPLES
Cod | Id
Stack 10
Stack 15
Stack 11
Stack 40
Overflow 1
Overflow 120
Overflow 15
Overflow 100
Required output
Cod | Min | Min_In_The_Middle | Max_In_The_Middle | Max
Stack 10 11 15 40
Overflow 1 15 100 120
Just only one [Table|[Clustered] Index]]Scan (demo here):
SELECT pvt.Cod,
pvt.[1] AS MinValue,
pvt.[2] AS MinInterValue,
pvt.[3] AS MaxInterValue,
pvt.[4] AS MaxValue
FROM
(
SELECT x.Cod, x.ID, x.RowNumAsc
FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY t.Cod ORDER BY t.ID ASC) RowNumAsc,
ROW_NUMBER() OVER(PARTITION BY t.Cod ORDER BY t.ID DESC) RowNumDesc
FROM MyTable t
) x
WHERE x.RowNumAsc = 1 AND x.RowNumDesc = 4
OR x.RowNumAsc = 2 AND x.RowNumDesc = 3
OR x.RowNumAsc = 3 AND x.RowNumDesc = 2
OR x.RowNumAsc = 4 AND x.RowNumDesc = 1
) y
PIVOT ( MAX(y.ID) FOR y.RowNumAsc IN ([1], [2], [3], [4]) ) pvt;
Try using this, best of luck
WITH temp AS
(SELECT cod, MIN (ID) min_id, MAX (ID) max_id
FROM tab
GROUP BY cod
HAVING COUNT (ID) = 4)
SELECT code, temp.min_id,
(SELECT MIN (ID)
FROM tab
WHERE cod = temp.cod AND ID NOT IN (temp.min_id)
GROUP BY cod) min_mid_id,
(SELECT MAX (ID)
FROM tab
WHERE cod = temp.cod AND ID NOT IN (temp.max_id)
GROUP BY cod) max_min_id, temp.max_id
FROM temp;
I'm not sure what it means for your question to be tagged plsql and sql-server. But I'll assume you're working with a database system that supports CTEs and window functions.
To generalize what you're been trying to do, first assign row numbers to the rows, then use whatever technique you want to achieve the pivot:
;WITH OrderedValues as (
SELECT Cod,Id,ROW_NUMBER() OVER (PARTITION BY Cod ORDER BY Id) as rn
COUNT(*) OVER (PARTITION BY Cod) as Cnt
FROM Table (NOLOCK)
), With4Values as (
SELECT * from OrderedValues where Cnt=4
)
SELECT Cod,
--However you want to do the pivot. Here I'll use MAX/CASE
MAX(CASE WHEN rn=1 THEN Id END) as Value1,
MAX(CASE WHEN rn=2 THEN Id END) as Value2,
MAX(CASE WHEN rn=3 THEN Id END) as Value3,
MAX(CASE WHEN rn=4 THEN Id END) as Value4
FROM
With4Values
GROUP BY
Cod
You can hopefully see that this is more easily extended to more columns than answering your overly specific questions about 3 rows, or 4 rows. But if you need to deal with an arbitrary number of columns, you'll have to switch to dynamic SQL.
I understand you want to exclude the extreme values and find min and max for the rest.
This is what I think of, but I had no chance to run and test it...
WITH Extremes AS ( SELECT Cod, MAX(ID) AS Id_Max, MIN(ID) AS Id_Min
FROM [Table] a GROUP BY Cod)
SELECT
e.Cod,
e.Id_Min,
MIN(a.Id) AS id_Min_Middle,
MAX(a.Id) AS id_Max_Middle,
e.Id_Max
FROM Extremes e
LEFT JOIN [Table] a ON a.Cod = e.Cod AND a.Id > e.Id_Min AND a.Id < e.Id_Max
GROUP BY e.Cod