Duplicate id rows with few columns to unique id row with many columns Oracle SQL - 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.

Related

How to set an incrementing flag column for related rows?

I am trying to create a flag column called "Related" to use in reporting to highlight specific rows that are related based on the ID column (1 = related, NULL = not related). The original table "table1" looks like below:
Name ID Related
--------------------------------
Jack 101 NULL
John 101 NULL
Pat 105 NULL
Ben 106 NULL
Jordan 106 NULL
George 300 NULL
Alan 500 NULL
Bill 200 NULL
Bob 200 NULL
I then used this UPDATE statement below:
UPDATE a
SET Related = 1
FROM table1 a
JOIN (SELECT ID FROM table1 GROUP BY ID HAVING COUNT(*) > 1) b
ON a.ID = b.ID
Below is the result of this update statement:
Name ID Related
--------------------------------
Jack 101 1
John 101 1
Pat 105 NULL
Ben 106 1
Jordan 106 1
George 300 NULL
Alan 500 NULL
Bill 200 1
Bob 200 1
This gets me close but I need for it to instead of assigning the number 1 to each related row, to increment the number for each set of related rows based on their different ID column values.
Desired result:
Name ID Related
--------------------------------
Jack 101 1
John 101 1
Pat 105 NULL
Ben 106 2
Jordan 106 2
George 300 NULL
Alan 500 NULL
Bill 200 3
Bob 200 3
This is a possible solution using dense_rank to number your related values and an updateable CTE
with r as (
select id
from t
group by id having Count(*) > 1
),
n as (
select t.id, t.related, Dense_Rank() over (order by r.id) r
from r
join t on t.id = r.id
)
update n set related = r
You can do this without a self-join, just using window functions in a CTE, and updating the CTE directly:
WITH tCounted AS (
SELECT
t.id,
t.related,
c = COUNT(*) OVER (PARTITION BY r.id)
FROM t
),
tWithRelated as (
SELECT
t.id,
t.related,
rn = DENSE_RANK() OVER (ORDER BY r.id)
FROM tCounted
WHERE c > 1
)
UPDATE tWithRelated
SET related = rn;
Use an updateable CTE - comments explain the logic.
with cte1 as (
select [Name], ID, Related
-- Get the count within the id partition, less 1 as specified
, count(*) over (partition by id) - 1 cnt
-- Get the row number within the id partition
, row_number() over (partition by id order by id) rn
from #Test
), cte2 as (
select [Name], ID, Related, cnt, rn
-- Add 1 *only* if the count is > 0 *and* its the first row in the id partition
, case when cnt > 0 then sum(case when cnt > 0 and rn = 1 then 1 else 0 end) over (order by id) else null end NewRelated
from cte1
)
update cte2 set Related = NewRelated;
This doesn't assume Related is already null and works for more than 2 rows for any given ID.
It does assume that one can order by the ID column - even though the data provided doesn't do that.

Updating Rows in a normalized table

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

Calculate product of column values on the basis of other column in SQL Server

I have a table
Tid Did value
------------------
1 123 100
1 234 200
2 123 323
2 234 233
All tids have dids as 123 and 234. So for every tid having dids 123 and 234 I want the product of corresponding values
The output table will be
Tid Product
------------------
1 20000 (product of 100 and 200)
2 75259 (product of 323 and 233)
Any help?
select tid,
min(case when did = 123 then value end)
* min(case when did = 234 then value end) product
from my_table
group by tid
To get the data for multiple rows combined (based on tid) you use GROUP BY.
Because you're grouping by tid, you have to use an aggregate function to do anything with other values from the individual rows. If implied assumptions hold (exactly 1 row matching each did for each tid) then it doesn't matter much what aggregate function you use; min is as good as anything.
Within the aggregation, you use CASE logic to select value for the required did (and NULL for all other rows in the tid group).
Then just do the math.
You can use some arithmetic to get the product per tid.
select tid,exp(sum(log(value))) as prod
from t
group by tid
To do this only for tid's having did values 123 and 234, use
select tid,exp(sum(log(value))) as prod
from t
group by tid
having count(distinct case when did in (123,234) then did end) = 2
Here's a Rexster solution, based on good work of #gbn here
SELECT
Tid,
CASE
WHEN MinVal = 0 THEN 0
WHEN Neg % 2 = 1 THEN -1 * EXP(ABSMult)
ELSE EXP(ABSMult)
END
FROM
(
SELECT
Tid,
SUM(LOG(ABS(NULLIF(value, 0)))) AS ABSMult,
SUM(SIGN(CASE WHEN value < 0 THEN 1 ELSE 0 END)) AS Neg,
MIN(ABS(value)) AS MinVal
FROM
t
GROUP BY
Tid
) t2

Merging multiple rows into one using Postgresql

I am trying to combine multiple rows with the same IDs to one.
My raw table looks like this:
ID | person_id | eur_amount
1 3 200
1 2 100
2 3 80
2 2 100
The output should look like this:
ID | person_1 | eur_amount_1 | person_2 | eur_amount_2 |
1 3 200 2 100
2 3 80 2 100
The max number of persons is the same. I already tried solving it with a multiple JOIN statements and the crosstab() function as mentioned here PostgreSQL Crosstab Query.
But I couldn't find a solution for this - does anyone know a good way to achive the desired output?
Thanks in advance!
You can do this using cross tab or conditional aggregation. But you need to identify the rows, using row_number(). For instance:
select id,
max(case when seqnum = 1 then person_id end) as person_id_1,
max(case when seqnum = 1 then eur_amount end) as eur_amount_1,
max(case when seqnum = 2 then person_id end) as person_id_2,
max(case when seqnum = 2 then eur_amount end) as eur_amount_2
from (select t.*,
row_number() over (partition by id order by id) as seqnum
from t
) t
group by id;

How to declare a row as a Alternate Row

id Name claim priority
1 yatin 70 5
6 yatin 1 10
2 hiren 30 3
3 pankaj 40 2
4 kavin 50 1
5 jigo 10 4
7 jigo 1 10
this is my table and i want to arrange this table as shown below
id Name claim priority AlternateFlag
1 yatin 70 5 0
6 yatin 1 10 0
2 hiren 30 3 1
3 pankaj 40 2 0
4 kavin 50 1 1
5 jigo 10 4 0
7 jigo 1 10 0
It is sorted as alternate group of same row.
I am Using sql server 2005. Alternate flag starts with '0'. In my example First record with name "yatin" so set AlternateFlag as '0'.
Now second record has a same name as "yatin" so alternate flag would be '0'
Now Third record with name "hiren" is single record, so assign '1' to it
In short i want identify alternate group with same name...
Hope you understand my problem
Thanks in advance
Try
SELECT t.*, f.AlternateFlag
FROM tbl t
JOIN (
SELECT [name],
AlternateFlag = ~CAST(ROW_NUMBER() OVER(ORDER BY MIN(ID)) % 2 AS BIT)
FROM tbl
GROUP BY name
) f ON f.name = t.name
demo
You could use probably an aggregate function COUNT() and then HAVING() and then UNION both Table, like:
SELECT id, A.Name, Claim, Priority, 0 as AlternateFlag
FROM YourTable
INNER JOIN (
SELECT Name, COUNT(*) as NameCount
FROM YourTable
GROUP BY Name
HAVING COUNT(*) > 1 ) A
ON YourTable.Name = A.Name
UNION ALL
SELECT id, B.Name, Claim, Priority, 1 as AlternateFlag
FROM YourTable
INNER JOIN (
SELECT Name, COUNT(*) as NameCount
FROM YourTable
GROUP BY Name
HAVING COUNT(*) = 1 ) B
ON YourTable.Name = B.Name
Now, this assumes that the Names are unique meaning the names like Yatin for example although has two counts is only associated to one person.
See my SqlFiddle Demo
You can use Row_Number() function with OVER that will give you enumeration, than use the reminder of integer division it by 2 - so you'll get 1s and 0s in your SELECT or in the view.