Is there a way to update groups of rows with separate incrementing values in one query - sql

Lets say you have the following table:
Id Index
1 3
1 1
2 1
3 3
1 5
what I would like to have is the following:
Id Index
1 0
1 1
2 0
3 0
1 2
As you might notice, the goal is for every row where Id is the same, to incrementally update the Index column, starting from zero.
Now, I know this is fairly simple with using cursors, but out of curiosity is there a way to do this with single UPDATE query, somehow combining with temp tables, common table expressions or something similar?

Yes, assuming that the you don't really care about the order of the values for the new index values. SQL Server offers updatable CTEs and window functions that do exactly what you want:
with toupdate as (
select t.*, row_number() over (partition by id order by (select NULL)) as newindex
from table t
)
update toupdate
set index = newindex;
If you want them in a specific order, then you need another column to specify the ordering. The existing index column doesn't work.

With Row_number() -1 and CTE you can write as:
CREATE TABLE #temp1(
Id int,
[Index] int)
INSERT INTO #temp1 VALUES (1,3),(1,1),(2,1),(3,3),(1,5);
--select * from #temp1;
With CTE as
(
select t.*, row_number() over (partition by id order by (select null))-1 as newindex
from #temp1 t
)
Update CTE
set [Index] = newindex;
select * from #temp1;
Demo

I'm not sure why you would want to do this really, but I had fun figuring it out!
This solution relies on your table having a primary key for the self join... but you could always create an auto inc index if none exists and this is a one off job... This will also have the added benefit of getting you to think about the precise ordering of this you want... as currently there is no way of saying which order [ID] will get [Index] in.
UPDATE dbo.Example
SET [Index] = b.newIndex
FROM dbo.Example a
INNER JOIN (
select
z.ID,
z.[Index],
(row_number() over (partition by ID order by (select NULL))) as newIndex
from Example z
) b ON a.ID = b.ID AND a.[Index]=b.[Index] --Is this a unique self join for your table?.. no PK provided. You might need to make an index first.

Probably, this is what you want
SELECT *,RANK() OVER(PARTITION BY Id ORDER BY [Index])-1 AS NewIndex FROM
(
SELECT 1 AS Id,3 [Index]
UNION
SELECT 1,1
UNION
SELECT 2,1
UNION
SELECT 3,3
UNION
SELECT 1,5
) AS T
& the result will come as
Now if you want to update the table then execute this script
UPDATE tblname SET Index=RANK() OVER(PARTITION BY t.Id ORDER BY t.[Index])-1
FROM tblname AS t
In case I am missing something or any further assistance is required please let me know.

CREATE TABLE #temp1(
Id int,
Value int)
INSERT INTO #temp1 VALUES (1,2),(1,3),(2,3),(4,5)
SELECT
Id
,Value
,ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Id) Id
FROM #temp1
Start with this :)
Gave me results like
Id Value Count
1 2 1
1 3 2
1 2 3
1 3 4
1 2 5
1 3 6
1 2 7
1 3 8
2 3 1
2 4 2
2 5 3
2 3 4
2 4 5
2 5 6
2 4 7
2 5 8
2 3 9
2 3 10
3 4 1
4 5 1
4 5 2
4 5 3
4 5 4

Related

How to produce a circular shift with postgreSQL

Let's suppose that I have this simple table:
ID
1
2
3
4
If I do:
WITH example AS
(
SELECT UNNEST(ARRAY[1,2,3,4]) as ID
)
SELECT ID, lAG(ID,1) over() as LAG_ID
FROM example
-- The shift is 1 in this case, but could be any integer in practice.
I got:
ID
LAG_ID
1
2
1
3
2
4
3
But I need a circular shift:
ID
LAG_ID
1
4
2
1
3
2
4
3
Is there an elegant way to produce this result ?
If ID is not nullable
WITH example AS
(
SELECT UNNEST(ARRAY[1,2,3,4]) as ID
)
SELECT ID, coalesce(lAG(ID,1) over(order by ID asc), (select max(ID) from example)) as LAG_ID
FROM example

How to apply DENSE_RANK() to records ordered by multiple columns?

I'm writing a SQL stored procedure that for each AliasName it will retrieve me MasterNames it's associated with in the NameAssociations table, which contains AliasNameId, MasterNameId, and MatchRank, and StatusCode columns. I'll be using the stored procedure for server-side paging the data in C#, so I'd like the startRow endRow part to stay the same.
CREATE PROCEDURE [dbo].[NameAssociationsGetNameAssociations]
#StartRow INT = 1,
#EndRow INT = 1
AS
WITH result_set AS (
SELECT DENSE_RANK() OVER (ORDER BY an.Id) AS rowNum,
an.Id as AliasId, an.AliasName,
mn.Id as MasterId, mn.MasterName, mn.CodeId, mn.RowUpdateVersion AS ConcurrencyToken, na.MatchRank
FROM
NameAssociations na
INNER JOIN AliasNames an
ON na.AliasNameId = an.Id
INNER JOIN MasterNames mn
ON na.MasterNameId = mn.Id
WHERE
na.StatusCode = 0
)
SELECT
rowNum,
AliasId as Id, AliasName,
MasterId as Id, MasterName, CodeId, ConcurrencyToken, MatchRank
FROM
result_set
WHERE
rowNum BETWEEN #StartRow AND #EndRow
This procedure works and retrieves rows numbered correctly:
rowNum
Id
AliasName
Id
MasterName
CodeId
ConcurrencyToken
MatchRank
1
5
BName
34
SomeName1
2
0x0000021
1
2
6
DName
21
SomeName2
3
0x0000003
2
2
6
DName
2
SomeName3
1
0x00000A2
1
2
6
DName
40
SomeName4
1
0x00000B4
3
3
7
AName
11
SomeName5
1
0x000005B
1
So basically, I get a list of MasterNames for every AliasName.
But the rows are not ordered in the way I would like them to be, which is by AliasName ASC, Id (of alias) ASC, and MatchRank ASC, which would look like this:
rowNum
Id
AliasName
Id
MasterName
CodeId
ConcurrencyToken
MatchRank
1
7
AName
11
SomeName5
1
0x000005B
1
2
5
BName
34
SomeName1
2
0x0000021
1
3
6
DName
2
SomeName3
1
0x00000A2
1
3
6
DName
21
SomeName2
3
0x0000003
2
3
6
DName
40
SomeName4
1
0x00000B4
3
You can see the names of aliases are ordered alphabetically, and then the associated masters were reordered so the MatchRank is in ASC order. All of this while the rowNum is correct, which is what I've been having trouble with attaining.
I've tried doing PARTITION BY an.Id ORDER BY an.AliasName ASC, an.Id ASC, na.MatchRank ASC, but I must be misunderstanding what ORDER BY does in this case because the results come out wrong. The results look like it ordered the records only by MatchRank ASC and partitioned by it. I'm expecting it to order the data by those three columns, and then partition it by the an.Id.
How can I write the query so that the output result looks like the second example of table? I hope this was all clear.
Ignoring what looks like a transcription/obfuscation error in your script that actually renders your SQL as invalid (dense_rank must have an order by), I think your solution is actually pretty simple.
The dense_rank function uses the values in the order by to determine which records get the same ranking value, so if you want to sort the output by the AliasName, but also want to make sure records with the same AliasName but different id values (not sure why this would be possible, but defensive coding is generally a good practice), you just need to include both values in your order by, in the order that you want to apply sorting. In your case this looks like the following minimal solution:
Query
declare #t table(id int, AliasName varchar(10), val int);
insert into #t values (1,'b',1),(2,'c',2),(2,'c',3),(2,'cc',4),(22,'c',5),(3,'a',6);
select *
,dense_rank() over (order by id) as rid
,dense_rank() over (order by AliasName, id) as rAliasName
from #t;
Output
id
AliasName
val
rid
rAliasName
3
a
6
3
1
1
b
1
1
2
2
c
2
2
3
2
c
3
2
3
22
c
5
4
4
2
cc
4
2
5

sql - select single ID for each group with the lowest value

Consider the following table:
ID GroupId Rank
1 1 1
2 1 2
3 1 1
4 2 10
5 2 1
6 3 1
7 4 5
I need an sql (for MS-SQL) select query selecting a single Id for each group with the lowest rank. Each group needs to only return a single ID, even if there are two with the same rank (as 1 and 2 do in the above table). I've tried to select the min value, but the requirement that only one be returned, and the value to be returned is the ID column, is throwing me.
Does anyone know how to do this?
Use row_number():
select t.*
from (select t.*,
row_number() over (partition by groupid order by rank) as seqnum
from t
) t
where seqnum = 1;

SQL - Add value with previous row only

I have a table named myvals with the following fields:
ID number
-- -------
1 7
2 3
3 4
4 0
5 9
Starting on 2nd row, I would like to add the number with the previous row number. So, my end result would look like this
ID number
-- ------
1 7
2 10
3 7
4 4
5 9
You could use the LAG analytic function
SELECT Id, number + LAG(number,1,0) OVER (ORDER BY Id) FROM table
First thing's first. You can't add to null to ID 1 must have a value.
create table #temp
(
month_type datetime,
value int
)
insert into #temp
Select '2015/01/01',1
union
Select '2015/02/01',2
union
Select '2015/03/01',3
union
Select '2015/04/01',4
SELECT t.value,t1.value,(t.value+t1.value)/2 FROM #temp t1
left join #temp t on t.month_type=Dateadd(MONTH,-1,t1.month_type)

Update grouped records in Oracle with an incremental value

So a dilemna, I have an Oracle table called T_GROUP. The records in the table have a unique id (ID) and they are part of a Study, identified by STUDY_ID, so multiple groups can be in the same Study.
CREATE TABLE T_GROUP
(
"ID" NUMBER(10,0),
"GROUP_NAME" VARCHAR2(255 CHAR),
"STUDY_ID" NUMBER(10,0)
)
The existing table has hundreds of records and I now add a new column called GROUP_INDEX:
ALTER TABLE T_GROUP ADD (
GROUP_INDEX NUMBER(10,0) DEFAULT(0)
);
After adding the column I need to run a script to update the GROUP_INDEX field as such: it should start at 1 and increment by 1 for each group within a study, starting with the lowest ID.
So now I have data as follows:
ID GROUP_NAME STUDY_ID GROUP_INDEX
-------------------------------------------
1 Group 1 3 0
2 Group 2 3 0
3 My Group 5 0
4 Big Group 5 0
5 Group X 5 0
6 Group Z 6 0
7 Best Group 6 0
After the update the group_index field should be as follows:
ID GROUP_NAME STUDY_ID GROUP_INDEX
-------------------------------------------
1 Group 1 3 1
2 Group 2 3 2
3 My Group 5 1
4 Big Group 5 2
5 Group X 5 3
6 Group Z 6 1
7 Best Group 6 2
The update will be run from sqlplus via a batch file. I've played around with group by and sub queries but I'm not having much luck, and having never used sqlplus I'm not sure if I can use variables, cursors etc. All tips greatly appreciated!
You should be able to use the analytic function row_number for this
UPDATE t_group t1
SET group_index = (SELECT rnk
FROM (SELECT id,
row_number() over (partition by study_id
order by id) rnk
FROM t_group) t2
WHERE t2.id = t1.id)
Here is a version using the MERGE statement. Might be faster than the sub-select (but doesn't have to be).
merge into t_group
using
(
select id,
row_number() over (partition by study_id order by id) rnk
from t_group
) t on t.id = t_group.id
when matched then update
set group_index = t.rnk;
This assumes that id is the primary key (or at least unique)
I can't test it right now, so there might be some syntax error in it.