How to extract ids of the rows with minimum value in sql - sql-server

I have the following table
RecordID Group Date Value
----------------------------------------
1 Test 1 1/01/2012 10
2 Test 1 1/01/2012 10
3 Test 1 1/01/2012 20
4 Test 2 1/01/2012 20
5 Test 1 2/01/2012 10
6 Test 2 2/01/2012 30
7 Test 1 2/01/2012 20
8 Test 1 2/01/2012 20
9 Test 2 2/01/2012 20
10 Test 1 3/01/2012 10
11 Test 2 3/01/2012 10
12 Test 2 3/01/2012 20
13 Test 3 3/01/2012 10
14 Test 1 4/01/2012 10
I need to get all RecordIds, where for the same date and same group it has the lowest value and disregard all records for the same date and group that have greater value. So my query needs to group by "date" and "group" and find records with lowest value, that is result should be:
RecordIds: 1, 2, 4, 5, 9, 10, 11, 13, 14

You can use rank in a sub query.
select T.RecordID,
T.[Group],
T.Date,
T.Value
from
(
select RecordID,
[Group],
Date,
Value,
rank() over(partition by [Group], Date order by Value) as rn
from YourTable
) as T
where T.rn = 1
order by T.RecordID
SQL Fiddle

Related

Get the rank of each string extracted from split_string

I have a TABLE with STR DNA DATA the following Table [DYS]
Id
DYS385
3
10-19
4
13-16
5
13-18
6
13-19
7
13-17
8
13-18
9
13-18
10
14-19
11
13-19
12
13-18
I am USING the following script to split the values of [DYS385]
select top 10 id,[DYS385],t.Value
from dys
OUTER APPLY(select * from string_split([DYS385],'-')) t
where dys385 is not null
Output
Id
DYS385
VALUE
3
10-19
10
3
10-19
19
4
13-16
13
4
13-16
16
5
13-18
13
5
13-18
18
6
13-19
13
6
13-19
19
7
13-17
13
7
13-17
17
I want to get for each Value, the Rank
example
10-19 => 10: Rank 1, 19: Rank 2
Desired results:
Id
DYS385
VALUE
RANK
3
10-19
10
1
3
10-19
19
2
4
13-16
13
1
4
13-16
16
2
5
13-18
13
1
5
13-18
18
2
6
13-19
13
1
6
13-19
19
2
7
13-17
13
1
7
13-17
17
2
Use an alternative string-split method, such as XML or Json that can return an ordinal position, such as:
create function dbo.SplitString(#string varchar(1000), #Delimiter varchar(10))
returns table
as
return(
select j.[value], 1 + Convert(tinyint,j.[key]) Seq
from OpenJson(Concat('["',replace(#string,#delimiter, '","'),'"]')) j
);
select value, Seq as [Rank]
from dbo.SplitString('10-19','-')
order by [Rank];
Example fiddle
Given this data:
CREATE TABLE dbo.Something(Id int, DYS385 varchar(10));
INSERT dbo.Something(Id, DYS385) VALUES (3 , '10-19'),
(4 , '13-16'), (5 , '13-18'), (6 , '13-19'),
(7 , '13-17'), (8 , '13-18'), (9 , '13-18'),
(10, '14-19'), (11, '13-19'), (12, '13-18');
Another way to make sure you rank the broken-up strings in the right order could be to use tricks like PARSENAME(), though this can be sensitive to strings lengths and whether a dot is valid within the data:
SELECT s.Id, s.DYS385, value = v.v, [rank] = ROW_NUMBER() OVER
(PARTITION BY s.Id ORDER BY c.c DESC)
FROM dbo.Something AS s
CROSS APPLY (VALUES(1),(2)) AS c(c)
CROSS APPLY (VALUES(PARSENAME(REPLACE(s.DYS385,'-','.'),c.c))) AS v(v)
ORDER BY s.Id, [rank];
Output:
Id
DYS385
value
rank
3
10-19
10
1
3
10-19
19
2
4
13-16
13
1
4
13-16
16
2
5
13-18
13
1
5
13-18
18
2
6
13-19
13
1
6
13-19
19
2
7
13-17
13
1
7
13-17
17
2
8
13-18
13
1
8
13-18
18
2
9
13-18
13
1
9
13-18
18
2
10
14-19
14
1
10
14-19
19
2
11
13-19
13
1
11
13-19
19
2
12
13-18
13
1
12
13-18
18
2
Example db<>fiddle
Split_string has an option to output this rank, called ordinal. Just add an extra parameter with the value of 1:
SELECT *FROM STRING_SPLIT('Lorem ipsum dolor sit amet.', ' ', 1);
That returns:
value ordinal
Lorem 1
ipsum 2
dolor 3
sit 4
amet. 5
In your case the query would be:
select top 10 id,[DYS385],t.* from dys OUTER APPLY(select * from string_split([DYS385],'-',1)) t where dys385 is not null
We can also make recursive split_string function which returns the rank of each item as follows
Link dbfiddle
Create function Split_Recursive(#string nvarchar(max),#delimiter as varchar(1)) returns Table
as
return(
with cte as(
select 1 N,
case when charindex(#delimiter,#string,1) =0 then #string else
substring(#string,1,-1+charindex(#delimiter,#string,1)) end [Found],
case when charindex(#delimiter,#string,1)=0 then #string else substring(#string,1+charindex(#delimiter,#string,1) ,len(#string))
end [MYSTRING]
union all
select 1+n,
case when charindex(#delimiter,[MYSTRING],1)=0 then [MYSTRING] else
substring([MYSTRING],1,-1+charindex(#delimiter,[MYSTRING],1)) end,substring([MYSTRING],1+charindex(#delimiter,[MYSTRING]),len([MYSTRING])) from cte
where charindex(#delimiter,[MYSTRING],1)>0),
cte2 as (select N,Found from cte
union select 1+t.N,Mystring from cte OUTER APPLY(select top 1 N from cte order by n desc)t where t.n=cte.n
)
select N Rank,Found from cte2
)
go
select * from dbo.Split_Recursive('12-16','-')
Output
Rank
Found
1
12
2
16

SQL Server Rank Function without taking into account some flagged values

Here is my problem: I have a list of flagged values, I want to see where those values would be in the case they weren't flagged. But I don't want the other flagged values to influence the order.
Note: Flagged values are the ones with CurrentPlace 10000
ID Value CurrentPlace
------------------------
1 2 1
2 8 3
3 3 2
4 4 10000
5 5 10000
6 10 10000
Using:
select *
from
(select
id, value,
rank() over (order by Value asc) as Rank
from
tbl1) r
where
r.ID in (select id from tbl1 where CurrentPlace = 10000)
Desired output:
ID Value Rank
------------------
4 4 3
5 5 3
6 10 4
But I'm getting this instead:
ID Value Rank
------------------
4 4 3
5 5 4
6 10 6
Any help will be appreciated
Thank you guys
I've solved with
SELECT ID, Value, Rank
FROM tbl1 a
CROSS APPLY
(SELECT isnull(max(currentPlace),0) + 1 AS Rank FROM tbl1 WHERE value < a.value and currentPlace <> 10000) b
WHERE a.CurrentPlace = 10000
Please feel free to comment this out.

Displaying the top two of summed values by group

I have a query named "QueryTotalGrades" which has three fields (Group, StudentID and Mark). each studentID has more than one mark. What I want to do is to create another query that conduct the following :
1- Sum mark for each studentID as a sumOfMark (Descending order)
2- Display the top 2 of sumOfMarks per group.
Example: let say that the "QueryTotalGrades" has the following values.
I'm using Microsoft access 2013
Group StudentID Mark
1 1 8
1 1 7
1 1 8
1 2 7
1 2 7
1 2 7
1 3 9
1 3 9
1 3 9
2 4 5
2 4 7
2 4 5
2 5 7
2 5 7
2 5 7
2 6 6
2 6 6
2 6 6
3 7 8
3 7 7
3 7 8
3 8 7
3 8 7
3 8 7
3 9 10
3 9 10
3 9 10
,so the output that I want should be as following
Group StudentID SumOfMark
1 3 27
1 1 23
2 5 21
2 6 18
3 9 30
3 7 23
I have tried many solutions, but no avail. HELP
A little longwinded but:
select
t1.[Group], t1.StudentID, t1.SumOfMark
from
(select [Group], StudentID, sum(Mark) as SumOfMark
from QueryTotalGrades
group by [Group], StudentID) as t1
where
(select count(*) from
(select [Group], StudentID, sum(Mark) as SumOfMark
from QueryTotalGrades
group by [Group], StudentID) as t2
where
t2.[Group] = t1.[Group] and
t2.SumOfMark >= t1.SumOfMark) <= 2
order by
t1.[Group], t1.SumOfMark desc
You can play with it here: SQL Fiddle
Query
;with cte as
(
select rn=row_number() over
(
partition by [Group]
order by sum(Mark) desc
),
[Group],StudentID,
sum(Mark) as SumOfMark
from student
group by [Group],StudentID
)
select [Group],StudentId,SumOfMark from cte where rn in (1,2);
fiddle demo

Cursor to update values in certain fashion

In SQL Server 2008 R2, I have a table like this:
ID Dates Count
1 03-02-2014 2
2 04-02-2014 1
3 05-02-2014 NULL
4 06-02-2014 1
5 07-02-2014 3
6 08-02-2014 NULL
7 09-02-2014 2
8 10-02-2014 NULL
9 11-02-2014 1
10 12-02-2014 3
11 13-02-2014 NULL
12 14-02-2014 1
I have an INT variable having some value such as #XCount = 15.
My requirement is to update the count column with (#XCount - Count) such as the result of previous record will be subtracted by the Count value in the next record.
Result:
ID Dates Count
1 03-02-2014 13 (15-2)
2 04-02-2014 12 (13-1)
3 05-02-2014 12 (12-0)
4 06-02-2014 11 (12-1)
5 07-02-2014 8 (11-3)
6 08-02-2014 8 (8-0)
7 09-02-2014 6 (8-2)
8 10-02-2014 6 (6-0)
9 11-02-2014 5 (6-1)
10 12-02-2014 2 (5-3)
11 13-02-2014 2 (2-0)
12 14-02-2014 1 (2-1)
I'm reluctant to use cursors as a solution. Can somebody help me?
How about something like
DECLARE #XCount INT = 15
;WITH Vals AS(
SELECT ID, Dates, [Count] OriginalCount, #XCount - ISNULL([COUNT],0) NewCount
FROM Table1
WHERE ID = 1
UNION ALL
SELECT t.ID, t.Dates, t.[Count], v.NewCount - ISNULL(t.[Count],0)
FROM Table1 t INNER JOIN Vals v ON t.ID = v.ID + 1
)
SELECT *
FROM Vals
SQL Fiddle DEMO
Do note thought that this is a recursive query, and that sometimes (until the tech allows for it, such as SQL SERVER 2012 LAG or Running totals) old does work.

Aggregate Function Group by Error

The following is how I have created the table:
CREATE TABLE #tmp
(
[Counter] int
,Per date not null
,Cam float
,CamMeg float
,Hfx float
,HfxMet float
,TorMetric float
)
The following is how I call the table later on in my script:
SELECT
((ROW_NUMBER() over(order by Per desc)-1)/#Avg)+1 as [Counter], Per,
Cam,
AVG(CamMetric) as CamMet,
HfxMe,
FROM #tmp
GROUP BY [counter] ;
DROP TABLE #tmp
The following are the errors that I get:
Msg 8120, Level 16, State 1, Line 175
Column '#tmp.Per' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Msg 8120, Level 16, State 1, Line 175
Column '#tmp.Per' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
What am I doing incorrectly?
The data looks like the following
counter --- per ---Cam --- HfxMet ---......
1 2012-02-09 3 16
1 2012-02-24 4 12
1 2012-03-04 2 15
2 2012-03-15 1 18
2 2012-03-30 6 20
2 2012-04-07 10 6
3 2012-04-28 8 3
Now I want to add two more columns called CamMetricAvg and HfxMetric that will look at all counters that are 1 and then get the CamMetric and HfxMetric values respectively and give the average and put that on each Like the following:
counter --- per ---Cam --- CamMt ---
1 2012-02-09 3 3
1 2012-02-24 4 3
1 2012-03-04 2 3
2 2012-03-15 1 5.6
2 2012-03-30 6 5.6
2 2012-04-07 10 5.6
3 2012-04-28 8 8
SELECT [Counter], Period,
CamMetric,
AvgCamMetric = AVG(CamMetric) OVER(PARTITION BY Counter),
HfxMetric,
AvgHfxMetric = AVG(HfxMetric) OVER(PARTITION BY Counter)
... repeat for other metrics ...
FROM #tmpTransHrsData
GROUP BY [Counter], Period, CamMetric, HfxMetric;
SQLFiddle example

Resources