Update column value of one row from other rows - sql-server-2005

I have the following table:
sno name pid amount total
1 Arif 0 100 null
2 Raj 1 200 null
3 Ramesh 2 100 null
4 Pooja 2 100 null
5 Swati 3 200 null
6 King 4 100 null
I want total of each person such that it gives total sum of amount of its descendants.
For ex.
for RAJ total will be : total= amount of(raj+ramesh+pooja+swati+king)
for SWATI :Total=amount of swati only.

You could try something like this:
WITH hierarchified AS (
SELECT
sno,
amount,
hierarchyID = CAST(sno AS varchar(500))
FROM yourTable
WHERE pid = 0
UNION ALL
SELECT
t.sno,
t.amount,
hierarchyID = CAST(h.hierarchyID + '/' + RTRIM(t.sno) AS varchar(500))
FROM yourTable t
INNER JOIN hierarchified h ON t.pid = h.sno
)
UPDATE yourTable
SET total = t.amount + ISNULL(
(
SELECT SUM(amount)
FROM hierarchified
WHERE hierarchyID LIKE h.hierarchyID + '/%'
),
0
)
FROM yourTable t
INNER JOIN hierarchified h ON t.sno = h.sno;
Note that this query (which you can try on SQL Fiddle) would probably not be very efficient on a large dataset. It might do as a one-off query, and then it would likely be better to organise updating the totals each time the table is updated, i.e. using triggers.

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.

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.

How to pivot on 2 fields

Here is the table I have:
PeriodID RecordID Basis Amount1 Amount2
1 1 IFRS 10 100
1 2 IFRS 20 200
2 1 IFRS 15 150
2 2 IFRS 25 250
1 1 CGAAP 30 300
1 2 CGAAP 40 400
2 1 CGAAP 35 350
2 2 CGAAP 45 450
I would like to pivot on the PeriodID and Basis field so that the result would have following columns:
RecordID
Period1IFRSAmount1
Period2IFRSAmount1
PeriodID1IFRSAmount2
PeriodID2IFRSAmount2
Period1CGAAPAmount1
Period2CGAAPAmount1
PeriodID1CGAAPAmount2
PeriodID2IFRSAmount22
First of all, you need to create the ouput that you need as rows. That is why union part is really important. After you construct your data you can easily use PIVOT.
Following query written in SQL SERVER. It should be similar to other databases as well.
SELECT *
FROM
(
SELECT RecordID, 'Period' + CONVERT(VARCHAR, PeriodID) + Basis + 'Amount1' AS Basis, Amount1 AS Amount
FROM TableName
UNION ALL
SELECT RecordID, 'Period' + CONVERT(VARCHAR, PeriodID) + Basis + 'Amount2' AS Basis, Amount2 AS AMOUNT
FROM TableName
) AS Q
PIVOT (
SUM(Amount)
FOR Basis IN
(
[Period1IFRSAmount1],
[Period2IFRSAmount1],
[Period1IFRSAmount2],
[Period2IFRSAmount2],
[Period1CGAAPAmount1],
[Period2CGAAPAmount1],
[Period1CGAAPAmount2],
[Period2CGAAPAmount2]
)
) AS P

Get record ids from groups where the sum of one of the field of their records is greater than

I have records as such:
Id ForeignKey Level ValueA ValueB
1 1001 1 2 10
2 1001 1 10 10
3 1001 1 20 20
4 1001 2 20 30
5 1002 1 1 100
6 1003 1 1 100
7 1004 1 1 100
I want to get the Ids of each record of the groups grouped by ForeignKey and Level where the sum of the group's records' ValueA values divided by the sum of ValueB values is greater than 0.5
In this case, I'd like to retrieve the Id of the three first records as (2 + 10 + 20) / (10 + 10 + 20) = 0.8
Here is what I've got so far:
select
ForeignKey,
SUM(ValueA) as ValueASum,
SUM(ValueB) as ValueBSum,
from tableA
group by ForeignKey
having (SUM(ValueA) / SUM(ValueB) > 0.5)
The result is
ForeignKey ValueASum ValueBSum
1001 32 40
How do I get the ids of the records from this point? If I add the Id in the select, I must group on it and then have a group for each record.
Thanks for your time
Hm, how about
select id from your_table where foreignkey = 1001
Is something wrong with working with multiple queries?
If you want you can do a subquery:
select id from your_table where foreignkey in ( select foreignkey from ( <yourQuery> ) sq);
UPDATE:
select t.id from Table1 t
inner join
(
select
ForeignKey, level,
SUM(ValueA) as ValueASum,
SUM(ValueB) as ValueBSum
from Table1
where level = 1
group by ForeignKey, Level
having (SUM(ValueA) / SUM(ValueB) > 0.5) ) sq
ON t.foreignkey = sq.foreignkey AND t.level = sq.level
I added where level = 1 just because your given resultset not what I get when I execute your query.
See it working live in an sqlfiddle.
You were on the right track, but if you wanted it from each "Level", you would need to add that into your group by also.
select
tA2.ID,
tA2.ForeignKey,
tA2.Level,
tA2.ValueA,
tA2.ValueB
from
( select
tA.ForeignKey,
tA.Level,
SUM(tA.ValueA) as ValueASum,
SUM(tA.ValueB) as ValueBSum,
from
tableA tA
group by
tA.ForeignKey,
tA.Level
having
(SUM(tA.ValueA) / SUM(tA.ValueB) > 0.5) ) PreQualified
JOIN tableA tA2
on PreQualified.ForeignKey = tA2.ForeignKey
AND PreQualified.Level = tA2.Level
This would give all values that matched the qualifying condition.

SQL Query: SUM on three columns with criteria

I have a table with columns like these :
idx | amount | usercol1 | usercol2 | usercol3 | percentage1 | percentage2 | percentage3
Data is typically like this :
0 | 1500 | 1 | null | null | 100 | null | null
1 | 3000 | 2 | 3 | null | 50 | 50 | null
I would like to make a SUM() of every user's amount.
Example :
user1= 1500*100/100 (amount*usercol1/100)
user2= 3000*50/100 (amount*usercol1/100)
user3= 3000*50/100 (amount*usercol2/100)
I tried UNION to no avail (did not sum the SUMs).
Is there a way to do this ? The problem being that it should GROUP BY the username (which I get with a LEFT OUTER JOIN usernames ON exampletable.usercol1=usernames.idx).
I know this is non standard and would be better with relations from another table. But I am not allowed to change the table structure.
Many many many thanks ! :=)
Hereby, an example that gives a wrong result (seems to give only results from the query in the middle)
(
SELECT SUM(projects.amount * (projects.percentage1/100)) as totalproj,
entities.idx as idx,
COUNT(projects.idx) as numproj,
entities.name
FROM projects
INNER JOIN entities ON projects.usercol1=entities.idx
WHERE projects.usercol1=entities.idx
GROUP BY name ORDER BY totalproj DESC
)
UNION ALL
(
SELECT SUM(projects.amount * (projects.percentage2/100)) as totalproj,
entities.idx as idx,
COUNT(projects.idx) as numproj,
entities.name
FROM projects
INNER JOIN entities ON projects.usercol2=entities.idx
WHERE projects.usercol2=entities.idx
GROUP BY name ORDER BY totalproj DESC
)
UNION ALL
(
SELECT SUM(projects.amount * (projects.percentage3/100)) as totalproj,
entities.idx as idx,
COUNT(projects.idx) as numproj,
entities.name
FROM projects
INNER JOIN entities ON projects.usercol3=entities.idx
WHERE projects.usercol3=entities.idx
GROUP BY name ORDER BY totalproj DESC
)
ORDER BY totalproj DESC
LIMIT 10
You could use a derived table to simulate a first normal form table then join onto that.
SELECT SUM(P.amount * (P.percentage/100)) as totalproj,
entities.idx as idx,
COUNT(P.idx) as numproj,
entities.name
FROM
(
SELECT idx, amount, usercol1 AS usercol, percentage1 AS percentage
FROM projects
UNION ALL
SELECT idx, amount, usercol2 AS usercol, percentage2 AS percentage
FROM projects
UNION ALL
SELECT idx, amount, usercol3 AS usercol, percentage3 AS percentage
FROM projects
) P
INNER JOIN entities ON P.usercol=entities.idx
WHERE P.usercol=entities.idx
GROUP BY name
ORDER BY totalproj DESC
using this data (i added some stranger data to make sure the math was working properly)
0 1500 1 NULL NULL 100 NULL NULL
1 3000 2 3 NULL 50 50 NULL
2 780 4 1 3 70 20 50
3 3800 2 4 1 30 20 10
i got these results
user commission
------- -------------
1 2036
2 2640
3 1890
4 1306
is this what you were looking for? below is my query
SELECT [user]
,SUM([commission]) AS commission
FROM ( SELECT [usercol1] AS [user]
,( [amount] * [percentage1] ) / 100 AS commission
FROM [dbo].[projects]
WHERE [usercol1] IS NOT NULL
AND [percentage1] IS NOT NULL
UNION ALL
SELECT [usercol2]
,( [amount] * [percentage2] ) / 100
FROM [dbo].[projects]
WHERE [usercol2] IS NOT NULL
AND [percentage2] IS NOT NULL
UNION ALL
SELECT [usercol3]
,( [amount] * [percentage3] ) / 100
FROM [dbo].[projects]
WHERE [usercol3] IS NOT NULL
AND [percentage3] IS NOT NULL
) x
GROUP BY [user]