row_number() in T-SQL - sql

I have a table with these situation:
With my code
select
ID_A,
ID_B,
Position,
row_number() over (partition by ID_A, ID_B order by position) as row
from
TB_EXAMPLE
order by
ID_A, Position
I added a row_number_a column, but I want the row_number_b situation. Do you have any hints?

It's a gaps&islands problem, you need to assign a group number to each row before the Row_number. There are several ways, the following is based on the fact that the difference between two sequential number is the same as long as there's no gap in one of the sequences:
position rownum diff
1 1 0
2 1 1
3 1 2
4 1 3
5 2 3
3 3 3
...
22 1 21
23 2 21
24 2 22
Now all rows with the same ID_A and ID_B get the same difference if position is sequential and you can use this value in PARTITION BY:
with cte as
(
select
ID_A
,ID_B
,Position
,position -- if position is not sequential: ROW_NUMBER()over(partition by ID_A order by position)
- ROW_NUMBER()over(partition by ID_A,ID_B order by position) as grp
from TB_EXAMPLE
)
select
ID_A
,ID_B
,Position
,ROW_NUMBER()over(partition by ID_A, ID_B, grp order by position) as rn
from cte
order by ID_A,Position
Based on your comment that you actually want to delete rows, this can be simplified to a check if the previous row's values are the same as the current row's:
with cte as
(
select
ID_A
,ID_B
,Position
,case when LAG(ID_A)over(order by position) = ID_A
and LAG(ID_B)over(order by position) = ID_B
then 'delete'
else 'keep'
end as flag
from TB_EXAMPLE
)
select *
from cte
where flag = 'delete'
And as it looks like this is only based on changes in ID_B:
with cte as
(
select
ID_A
,ID_B
,Position
,case when LAG(ID_B)over(partition by ID_A order by position) = ID_B
then 'delete'
else 'keep'
end as flag
from TB_EXAMPLE
)
select *
from cte
where flag = 'delete'

Related

SQL Know the most ids in two columns

I want to know what id_b exist the most times in each id_A. I have this table
id_A id_B
1 1
2 1
2 1
3 1
3 3
3 3
And I need a sql command that puts the table like this:
id_A id_B
1 1
2 1
3 3
Technically, this is called the "mode" of (the distribution of) the values.
If you only want one, then use can use the ANSI standard function row_number() with conditional aggregation:
select id_A, id_B as mode_id_B
from (select id_A, id_B, count(*) as cnt,
row_number() over (partition by id_A order by count(*) desc) as seqnum
from t
group by id_A, id_B
) ab
where seqnum = 1;

Select row minimum col for each id

I have a table like following
id id_a id_b uds
--------------------------
1 1 3 20
1 2 8 17
2 1 3 5
3 1 1 32
3 2 1 6
What I would need is to get the row with minimum "uds" for each "id". So the result would be:
id id_a id_b uds
--------------------------
1 2 8 17
2 1 3 5
3 2 1 6
Thank you in advance...
Use Min with a group by clause:
select id, id_a, id_b, min(uds) as uds
from table1
group by id, id_a, id_b
order by id, id_a, id_b;
However, I should mention this is going to get you all of the items, you need to also specify an aggregate on the other columns, or do not include them.
select id, min(uds) as uds
from table1
group by id
order by id;
Judging by your desired output though, the following may be what you want:
select id, max(id_a) as id_a, max(id_b) as id_b, min(uds) as uds
from table1
group by id
order by id;
Most databases support the ANSI standard window functions. An easy way to do what you want:
select t.*
from (select t.*, row_number() over (partition by id order by uds) as seqnum
from t
) t
where seqnum = 1;
you have to set condition with minimum uds value or you have to decides how many numbers of records you want with minimum uds
More one way:
select
a.*
from
#temp a inner join (select id, min(uds) minUds from #temp group by id) b on
a.id = b.id
and a.uds = b.minUds

how to assign a rank for null values with previous first non-null value in oracle

I need to assign a rank to some null values over ordered rows.
My query is like this :
with sub as
(
select 10 as id, 1 as inx,2 as num from dual
union all
select 10 as id, 2 as inx,null as num from dual
union all
select 10 as id, 3 as inx,8 as num from dual
union all
select 10 as id, 4 as inx,null as num from dual
)
select *
from sub order by inx
and result set is like this :
id inx num
----------
10 1 2
10 2 null
10 3 8
10 4 null
i'm tring to set null values with previous first non-null value
for example : num null value should be "2" where inx = 2
and num null value should be "8" where inx = 4 and so on.
thx for any idea..
If you know that the values are increasing, you can just use max():
select id, inx, max(num) over (partition by id order by inx) as num
If they are not increasing and multiple nulls never appear in a sequence, you can use lag():
select id, inx,
(case when num is null
then lag(num) over (partition by id order by inx)
else num
end)as null;
If nulls do appear in a sequence, you can use the ignore nulls option to lag():
select id, inx,
(case when num is null
then lag(num ignore nulls) over (partition by id order by inx)
else num
end)as null

SELECT records until new value SQL

I have a table
Val | Number
08 | 1
09 | 1
10 | 1
11 | 3
12 | 0
13 | 1
14 | 1
15 | 1
I need to return the last values where Number = 1 (however many that may be) until Number changes, but do not need the first instances where Number = 1. Essentially I need to select back until Number changes to 0 (15, 14, 13)
Is there a proper way to do this in MSSQL?
Based on following:
I need to return the last values where Number = 1
Essentially I need to select back until Number changes to 0 (15, 14,
13)
Try (Fiddle demo ):
select val, number
from T
where val > (select max(val)
from T
where number<>1)
EDIT: to address all possible combinations (Fiddle demo 2)
;with cte1 as
(
select 1 id, max(val) maxOne
from T
where number=1
),
cte2 as
(
select 1 id, isnull(max(val),0) maxOther
from T
where val < (select maxOne from cte1) and number<>1
)
select val, number
from T cross join
(select maxOne, maxOther
from cte1 join cte2 on cte1.id = cte2.id
) X
where val>maxOther and val<=maxOne
I think you can use window functions, something like this:
with cte as (
-- generate two row_number to enumerate distinct groups
select
Val, Number,
row_number() over(partition by Number order by Val) as rn1,
row_number() over(order by Val) as rn2
from Table1
), cte2 as (
-- get groups with Number = 1 and last group
select
Val, Number,
rn2 - rn1 as rn1, max(rn2 - rn1) over() as rn2
from cte
where Number = 1
)
select Val, Number
from cte2
where rn1 = rn2
sql fiddle demo
DEMO: http://sqlfiddle.com/#!3/e7d54/23
DDL
create table T(val int identity(8,1), number int)
insert into T values
(1),(1),(1),(3),(0),(1),(1),(1),(0),(2)
DML
; WITH last_1 AS (
SELECT Max(val) As val
FROM t
WHERE number = 1
)
, last_non_1 AS (
SELECT Coalesce(Max(val), -937) As val
FROM t
WHERE EXISTS (
SELECT val
FROM last_1
WHERE last_1.val > t.val
)
AND number <> 1
)
SELECT t.val
, t.number
FROM t
CROSS
JOIN last_1
CROSS
JOIN last_non_1
WHERE t.val <= last_1.val
AND t.val > last_non_1.val
I know it's a little verbose but I've deliberately kept it that way to illustrate the methodolgy.
Find the highest val where number=1.
For all values where the val is less than the number found in step 1, find the largest val where the number<>1
Finally, find the rows that fall within the values we uncovered in steps 1 & 2.
select val, count (number) from
yourtable
group by val
having count(number) > 1
The having clause is the key here, giving you all the vals that have more than one value of 1.
This is a common approach for getting rows until some value changes. For your specific case use desc in proper spots.
Create sample table
select * into #tmp from
(select 1 as id, 'Alpha' as value union all
select 2 as id, 'Alpha' as value union all
select 3 as id, 'Alpha' as value union all
select 4 as id, 'Beta' as value union all
select 5 as id, 'Alpha' as value union all
select 6 as id, 'Gamma' as value union all
select 7 as id, 'Alpha' as value) t
Pull top rows until value changes:
with cte as (select * from #tmp t)
select * from
(select cte.*, ROW_NUMBER() over (order by id) rn from cte) OriginTable
inner join
(
select cte.*, ROW_NUMBER() over (order by id) rn from cte
where cte.value = (select top 1 cte.value from cte order by cte.id)
) OnlyFirstValueRecords
on OriginTable.rn = OnlyFirstValueRecords.rn and OriginTable.id = OnlyFirstValueRecords.id
On the left side we put an original table. On the right side we put only rows whose value is equal to the value in first line.
Records in both tables will be same until target value changes. After line #3 row numbers will get different IDs associated because of the offset and will never be joined with original table:
LEFT RIGHT
ID Value RN ID Value RN
1 Alpha 1 | 1 Alpha 1
2 Alpha 2 | 2 Alpha 2
3 Alpha 3 | 3 Alpha 3
----------------------- result set ends here
4 Beta 4 | 5 Alpha 4
5 Alpha 5 | 7 Alpha 5
6 Gamma 6 |
7 Alpha 7 |
The ID must be unique. Ordering by this ID must be same in both ROW_NUMBER() functions.

Second maximum and minimum values

Given a table with multiple rows of an int field and the same identifier, is it possible to return the 2nd maximum and 2nd minimum value from the table.
A table consists of
ID | number
------------------------
1 | 10
1 | 11
1 | 13
1 | 14
1 | 15
1 | 16
Final Result would be
ID | nMin | nMax
--------------------------------
1 | 11 | 15
You can use row_number to assign a ranking per ID. Then you can group by id and pick the rows with the ranking you're after. The following example picks the second lowest and third highest :
select id
, max(case when rnAsc = 2 then number end) as SecondLowest
, max(case when rnDesc = 3 then number end) as ThirdHighest
from (
select ID
, row_number() over (partition by ID order by number) as rnAsc
, row_number() over (partition by ID order by number desc) as rnDesc
) as SubQueryAlias
group by
id
The max is just to pick out the one non-null value; you can replace it with min or even avg and it would not affect the outcome.
This will work, but see caveats:
SELECT Id, number
INTO #T
FROM (
SELECT 1 ID, 10 number
UNION
SELECT 1 ID, 10 number
UNION
SELECT 1 ID, 11 number
UNION
SELECT 1 ID, 13 number
UNION
SELECT 1 ID, 14 number
UNION
SELECT 1 ID, 15 number
UNION
SELECT 1 ID, 16 number
) U;
WITH EX AS (
SELECT Id, MIN(number) MinNumber, MAX(number) MaxNumber
FROM #T
GROUP BY Id
)
SELECT #T.Id, MIN(number) nMin, MAX(number) nMax
FROM #T INNER JOIN
EX ON #T.Id = EX.Id
WHERE #T.number <> MinNumber AND #T.number <> MaxNumber
GROUP BY #T.Id
DROP TABLE #T;
If you have two MAX values that are the same value, this will not pick them up. So depending on how your data is presented you could be losing the proper result.
You could select the next minimum value by using the following method:
SELECT MAX(Number)
FROM
(
SELECT top 2 (Number)
FROM table1 t1
WHERE ID = {MyNumber}
order by Number
)a
It only works if you can restrict the inner query with a where clause
This would be a better way. I quickly put this together, but if you can combine the two queries, you will get exactly what you were looking for.
select *
from
(
select
myID,
myNumber,
row_number() over (order by myID) as myRowNumber
from MyTable
) x
where x.myRowNumber = 2
select *
from
(
select
myID,
myNumber,
row_number() over (order by myID desc) as myRowNumber
from MyTable
) y
where x.myRowNumber = 2
let the table name be tblName.
select max(number) from tblName where number not in (select max(number) from tblName);
same for min, just replace max with min.
As I myself learned just today the solution is to use LIMIT. You order the results so that the highest values are on top and limit the result to 2. Then you select that subselect and order it the other way round and only take the first one.
SELECT somefield FROM (
SELECT somefield from table
ORDER BY somefield DESC LIMIT 2)
ORDER BY somefield ASC LIMIT 1