Updating Rows in a normalized table - sql

normalized table
ID SEQ Type Value Flag
1 1 a 100 -
1 2 a 200 -
1 3 a 250 -
1 4 b 200 -
2 1 a 150 -
2 2 b 100 -
2 3 b 200 -
How do I write a single update statement such that the resulting table is populated as follows
ID SEQ Type Value Flag
1 1 a 100 valid
1 2 a 200 repeat
1 3 a 250 repeat
1 4 b 200 valid
2 1 a 150 valid
2 2 b 100 valid
2 3 b 200 repeat
Edit: included seq column
only the first occurence of the value for a type for a ID group should have the valid flag
should it be written as two separate update statements?
can someone clarify me?
Much appreciated

Populate the table first using row_number() and then update the table.
Option 1:
select
Id,
Type,
Value,
null as Flag,
row_number() over (partition by ID, Type order by SEQ) as rnk
from yourTable
then you can use update
update yourTable
set flag = case
when rnk = 1 then 'valid'
else 'repeat'
end
Option 2:
You may be able to do without using update statement as following
select
Id,
SEQ,
Type,
Value,
case
when rnk = 1 then 'valid'
else 'repeat'
end as flag
from
(
select
Id,
SEQ,
Type,
Value,
row_number() over (partition by ID, Type order by SEQ) as rnk
from yourTable
) val

Related

Duplicate id rows with few columns to unique id row with many columns Oracle SQL

I have a pole table that can have one to four streetlights on it. Each row has a pole ID and the type (a description) of streetlight. I need the ID's to be unique with a column for each of the possible streetlights. The type/description can anyone of 26 strings.
I have something like this:
ID Description
----------------
1 S 400
1 M 200
1 HPS 1000
1 S 400
2 M 400
2 S 250
3 S 300
What I need:
ID Description_1 Description_2 Description_3 Description_4
------------------------------------------------------------------
1 S 400 M 200 HPS 1000 S 400
2 M 400 S 250
3 S 300
The order the descriptions get populated in the description columns is not important, e.g. for ID = 1 the HPS 1000 value could be in description column 1, 2, 3, or 4. So, long as all values are present.
I tried to pivot it but I don't think that is the right tool.
select * from table t
pivot (
max(Description) for ID in (1, 2, 3))
Because there are ~3000 IDs I would end up with a table that is ~3001 rows wide...
I also looked at this Oracle SQL Cross Tab Query But it is not quite the same situation.
What is the right way to solve this problem?
You can use row_number() and conditional aggregation:
select
id,
max(case when rn = 1 then description end) description_1,
max(case when rn = 2 then description end) description_2,
max(case when rn = 3 then description end) description_3,
max(case when rn = 4 then description end) description_4
from (
select t.*, row_number() over(partition by id order by description) rn
from mytable t
) t
group by id
This handles up to 4 descriptions per id. To handle more, you can just expand the select clause with more conditional max()s.

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;

Is there a way to group this data?

Data Looks like -
1
2
3
1
2
2
2
3
1
5
4
1
2
So whenever there is a 1, it marks the beginning of a group which includes all the elements until it hits the next 1. So here,
1 2 3 - group 1
1 2 2 2 3 - group 2
and so on..
What would be the SQL query to show the average for every such group.
I could not figure out how to group them without using for loops or PLSQL code.
Result should look like two columns, one with the actual data and col 2 with the average value-
1 - avg value of 1,2 3
2
3
1 - avg value of 1,2,2,2,3
2
2
2
3
1 - avg value of 1,5,4
5
4
1 - avg value of 1,2
2
SQL tables represent unordered sets. There is no ordering, unless a column specifies the ordering. Let me assume that you have such a column.
You can identify the groups using a cumulative sum:
select t.*,
sum(case when t.col = 1 then 1 else 0 end) over (order by ?) as grp
from t;
? is the column that specifies the ordering.
You can then calculate the average using aggregation:
select grp, avg(col)
from (select t.*,
sum(case when t.col = 1 then 1 else 0 end) over (order by ?) as grp
from t
) t
group by grp;

SQL Server GROUP BY COUNT Consecutive Rows Only

I have a table called DATA on Microsoft SQL Server 2008 R2 with three non-nullable integer fields: ID, Sequence, and Value. Sequence values with the same ID will be consecutive, but can start with any value. I need a query that will return a count of consecutive rows with the same ID and Value.
For example, let's say I have the following data:
ID Sequence Value
-- -------- -----
1 1 1
5 1 100
5 2 200
5 3 200
5 4 100
10 10 10
I want the following result:
ID Start Value Count
-- ----- ----- -----
1 1 1 1
5 1 100 1
5 2 200 2
5 4 100 1
10 10 10 1
I tried
SELECT ID, MIN([Sequence]) AS Start, Value, COUNT(*) AS [Count]
FROM DATA
GROUP BY ID, Value
ORDER BY ID, Start
but that gives
ID Start Value Count
-- ----- ----- -----
1 1 1 1
5 1 100 2
5 2 200 2
10 10 10 1
which groups all rows with the same values, not just consecutive rows.
Any ideas? From what I've seen, I believe I have to left join the table with itself on consecutive rows using ROW_NUMBER(), but I am not sure exactly how to get counts from that.
Thanks in advance.
You can use Sequence - ROW_NUMBER() OVER (ORDER BY ID, Val, Sequence) AS g to create a group:
SELECT
ID,
MIN(Sequence) AS Sequence,
Val,
COUNT(*) AS cnt
FROM
(
SELECT
ID,
Sequence,
Sequence - ROW_NUMBER() OVER (ORDER BY ID, Val, Sequence) AS g,
Val
FROM
yourtable
) AS s
GROUP BY
ID, Val, g
Please see a fiddle here.

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

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