Hierarchical sum of the columns in oracle - sql

I have got one problem as follows:
ID NAME AMOUNT PARENTID
1 Adam 1000 0
2 John 2000 1
3 Clark 1500 2
4 Rita 1200 3
5 jack 1600 3
6 mark 1800 2
7 Finn 1500 6
8 Ryan 1100 6
So the data above is the result of a query with multiple joins
and it is a kind of hierarchy or tree something like this:
1
|
2
/ \
3 6
/ \ / \
5 4 7 8
and now I need to modify my query so that I get the following result
ID NAME AMOUNT PARENTID DownstreamSum
1 Adam 1000 0 10700
2 John 2000 1 8700
3 Clark 1500 2 2800
4 Rita 1200 3 0
5 jack 1600 3 0
6 mark 1800 2 2600
7 Finn 1500 6 0
8 Ryan 1100 6 0
So the logic is that the parent should have the sum of all the downstream child nodes in the DownstreamSum column.
for example:
id 6 should have the sum of amount of id 7 and 8
id 3 should have the sum of amount of id 4 and 5
but id 2 should have the sum of the amount of id 3 and the sum of amount of id 4 and 5
I tried with many possibilities with partition by and group by but I was not able to get the desired output.

Here is the query, please check.
SQL fiddle link
SELECT Y.*, X.TOTAL_AMOUNT FROM (
WITH hierarchy (PARENTID, ID) AS (SELECT PARENTID, ID
FROM Hierarchy_result
UNION ALL
SELECT h.PARENTID, bs.ID
FROM Hierarchy_result bs, hierarchy h WHERE bs.PARENTID=h.ID)
SELECT h.PARENTID AS PARENTID,
--SUM(FR.AMOUNT)
/*SUM((CASE WHEN FR.ID=h.PARENTID
THEN FR.amount
ELSE 0.0 END))*/
SUM(FR.amount) TOTAL_AMOUNT
FROM hierarchy h, Hierarchy_result FR WHERE FR.ID=h.ID
GROUP BY h.PARENTID
ORDER BY h.PARENTID) X, Hierarchy_result Y WHERE X.PARENTID(+) = Y.ID ORDER BY Y.ID;

A typical recursive CTE (available since Oracle 11gR2) will allow you to walk the branches to the leaves. Then a simple SUM() gives you the result you want. For example:
with
n (root_id, current_id, name, amount, parentid) as (
select id, id, name, amount, parentid from t
union all
select n.root_id, t.id, t.name, t.amount, t.parentid
from n
join t on t.parentid = n.current_id
)
select t.id, t.name, t.amount, t.parentid,
sum(n.amount) - t.amount as downstreamsum
from t
join n on n.root_id = t.id
group by t.id, t.name, t.amount, t.parentid
order by t.id
Result:
ID NAME AMOUNT PARENTID DOWNSTREAMSUM
--- ------ ------- --------- -------------
1 Adam 1000 0 10700
2 John 2000 1 8700
3 Clark 1500 2 2800
4 Rita 1200 3 0
5 jack 1600 3 0
6 mark 1800 2 2600
7 Finn 1500 6 0
8 Ryan 1100 6 0
See running example at db<>fiddle.

This query also calculates own and child sums and count:
with mcnt (
id, parentid, name, amount
,rnk
,level_members_cnt
,level_members_sum
,own_members_cnt
,own_members_sum
,child_members_cnt
,child_members_sum
) as (
select
p.id, p.parentid, p.name, p.amount
, row_number()over(partition by p.parentid order by p.id) rnk
, sum((select count(*) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) as level_members_cnt
, sum((select sum(m.amount) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) as level_members_sum
, (select count(*) from Hierarchy_result m where m.parentid=p.id) as own_members_cnt
, (select nvl(sum(m.amount),0) from Hierarchy_result m where m.parentid=p.id) as own_members_sum
, 0 as child_members_cnt
, 0 as child_members_sum
from Hierarchy_result p
where not exists(select null from Hierarchy_result child where p.id = child.parentid)
union all
select p.id, p.parentid, p.name, p.amount
, row_number()over(partition by p.parentid order by p.id) rnk
, sum((select count(*) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) level_members_cnt
, sum((select sum(m.amount) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) level_members_sum
, (select count(*) from Hierarchy_result m where m.parentid=p.id) as own_members_cnt
, (select nvl(sum(m.amount),0) from Hierarchy_result m where m.parentid=p.id) as own_members_sum
, mcnt.level_members_cnt + mcnt.child_members_cnt as child_members_cnt
, nvl(mcnt.level_members_sum,0) + nvl(mcnt.child_members_sum,0) as child_members_sum
from mcnt, Hierarchy_result p
where mcnt.parentid=p.id
and mcnt.rnk=1
)
select
id, parentid, name, amount, child_members_cnt, own_members_cnt
,child_members_cnt+own_members_cnt as total_cnt
,child_members_sum,own_members_sum
,child_members_sum+own_members_sum as total_sum
from mcnt
order by id;
Full example with sample data:
with Hierarchy_result ("ID", "NAME", "AMOUNT", "PARENTID") as (
select 1, 'Adam', 1000, 0 from dual union all
select 2, 'John', 2000, 1 from dual union all
select 3, 'Clark', 1500, 2 from dual union all
select 4, 'Rita', 1200, 3 from dual union all
select 5, 'jack', 1600, 3 from dual union all
select 6, 'mark', 1800, 2 from dual union all
select 7, 'Finn', 1500, 6 from dual union all
select 8, 'Ryan', 1100, 6 from dual
)
,mcnt (
id, parentid, name, amount
,rnk
,level_members_cnt
,level_members_sum
,own_members_cnt
,own_members_sum
,child_members_cnt
,child_members_sum
) as (
select
p.id, p.parentid, p.name, p.amount
, row_number()over(partition by p.parentid order by p.id) rnk
, sum((select count(*) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) as level_members_cnt
, sum((select sum(m.amount) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) as level_members_sum
, (select count(*) from Hierarchy_result m where m.parentid=p.id) as own_members_cnt
, (select nvl(sum(m.amount),0) from Hierarchy_result m where m.parentid=p.id) as own_members_sum
, 0 as child_members_cnt
, 0 as child_members_sum
from Hierarchy_result p
where not exists(select null from Hierarchy_result child where p.id = child.parentid)
union all
select p.id, p.parentid, p.name, p.amount
, row_number()over(partition by p.parentid order by p.id) rnk
, sum((select count(*) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) level_members_cnt
, sum((select sum(m.amount) from Hierarchy_result m where m.parentid=p.id))over(partition by p.parentid) level_members_sum
, (select count(*) from Hierarchy_result m where m.parentid=p.id) as own_members_cnt
, (select nvl(sum(m.amount),0) from Hierarchy_result m where m.parentid=p.id) as own_members_sum
, mcnt.level_members_cnt + mcnt.child_members_cnt as child_members_cnt
, nvl(mcnt.level_members_sum,0) + nvl(mcnt.child_members_sum,0) as child_members_sum
from mcnt, Hierarchy_result p
where mcnt.parentid=p.id
and mcnt.rnk=1
)
select
id, parentid, name, amount, child_members_cnt, own_members_cnt
,child_members_cnt+own_members_cnt as total_cnt
,child_members_sum,own_members_sum
,child_members_sum+own_members_sum as total_sum
from mcnt
order by id;
Result
ID PARENTID NAME CHILD_MEMBERS_SUM OWN_MEMBERS_SUM TOTAL_SUM CHILD_MEMBERS_CNT OWN_MEMBERS_CNT TOTAL_CNT
--- -------- ----- ----------------- --------------- ---------- ----------------- --------------- ----------
1 0 Adam 8700 2000 10700 6 1 7
2 1 John 5400 3300 8700 4 2 6
3 2 Clark 0 2800 2800 0 2 2
4 3 Rita 0 0 0 0 0 0
5 3 jack 0 0 0 0 0 0
6 2 mark 0 2600 2600 0 2 2
7 6 Finn 0 0 0 0 0 0
8 6 Ryan 0 0 0 0 0 0
ps. based on my another solution: Hierarchical Query - Counting records that belongs to Parent and Child Places

Related

How to find most frequent code(varchar) from a table

I would like to find most frequent code within CodeID which is in same code_group from a table.
For example, from original table
ID CodeID Name Code Code_group
1 1 A 101 0
2 1 A 102 0
3 1 B 102 0
4 2 C 201 0
5 2 C 201 0
6 2 D 202 0
7 2 E 202 0
8 3 F 101 1
9 3 G 103 1
10 3 G 104 1
11 3 G 104 1
I want output like the below.
ID CodeID Name Code Code_group Selected_code
1 1 A 101 0 102
2 1 A 102 0 102
3 1 B 102 0 102
4 2 C 201 0 NULL
5 2 C 201 0 NULL
6 2 D 202 0 NULL
7 2 E 202 0 NULL
8 3 F 101 1 104
9 3 G 103 1 104
10 3 H 104 1 104
11 3 H 104 1 104
Even though code of 8th ID is same in CodeID: 1,it is not in the same Code_group.
So For CodeID: 1, Selected_code would be 102.
it must be counted within exactly same Code_group.
=======================================
I have tried it like the below. I should not use ID for this one.
From TableA
with m as
(
select
CodeID,
Name,
Code,
Code_group,
cnt,
Selected_code = ROW_NUMBER() over (partition by Code_group order by cnt desc)
from( select CodeID, Name, Code,Code_group
,count(*) over (partition by Code,CodeID) as cnt from tableA
group by CodeID, Name, Code, Code_group,
) as t
group by CodeID,
Name,
Code,
Code_group, cnt
)
select a.CodeID,
a.Name,
a.Code,
a.Code_group, b.Code as Selected_code, cnt
from(select
CodeID,
Name,
Code,
Code_group,Selected_code,
cnt
from m) as a left outer join
(select CodeID,
Name,
Code,
Code_group,Selected_code,
cnt
from m where selected_Code=1) as b on a.CodeID = b.CodeID and a.Code_Group = b.Code_Group
order by a.CodeID, a.Code_Group
The problem of this is
With statment makes my table distinct. It shows only one row if there is exactly same data such as ID 1,2.
Also, I cannot make NULL if there is exactly same frequencies.
What should I add to get my desired output?
Or is there any better approach for this?
CTE cte find the highest frequency code by Code_group and CodeID using dense_rank()
CTE selected check for any Code with same frequency and exclude them.
Final query just select from the original table and LEFT JOIN the selected
with
cte as
(
select Code_group, CodeID, Code
from
(
select Code_group, CodeID, Code,
r = dense_rank() over (partition by Code_Group, CodeID
order by count(*) desc)
from tableA
group by Code_group, CodeID, Code
) c
where c.r = 1
),
selected as
(
select Code_group, CodeID, Code
from
(
select Code_group, CodeID, Code,
cnt = count(*) over (partition by Code_group, CodeID)
from cte
) s
where s.cnt = 1
)
select a.*,
Selected_Code = s.Code
from tableA a
left join selected s on a.Code_Group = s.Code_Group
and a.CodeID = s.CodeID;
db<>fiddle demo

Recursive CTE - recalculate the tree after exclusions

Lets say I have a table called #OrgList
CREATE TABLE #OrgList (
OrgUnitId int,
ParentOrgUnitId int,
PersonId int,
isExcluded bit
);
INSERT INTO #OrgList(OrgUnitId, ParentOrgUnitId, PersonId, isExcluded) VALUES
(1, NULL, 100, 0), (2,1, 101, 0), (3,1,102,0), (4,2,103,1), (5,2,104,0), (6,3,105,0), (7,4,106,0), (8,4,107,0), (9,4,108,0), (10,4,109,0), (11,4,110,1), (12,11,111,0)
My hierarchy tree structure looks like this
and my cte like this:
;
with cte as (
select OrgUnitId, ParentOrgUnitId, PersonId, isExcluded , 0 as level_num
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num+1 as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
)
select * from cte
I exclude OrgUnitId=4 and =11, then I want to update my recursive query that will recalculate the tree and show the new tree details, including level moves (there can be more levels and more consecutive exclusions, of course except the root node):
You should just add a second UNION ALL in your cte:
with cte as (
select OrgUnitId, ParentOrgUnitId, PersonId, isExcluded , 0 as level_num
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num+1 as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
where cte.isExcluded = 0
UNION ALL
select o.OrgUnitId, cte.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
where cte.isExcluded = 1
)
select * from cte
My approach:
Extend your initial CTE with an exclusion counter (ExclusionCount), counting the number of excluded nodes going from root to leaf nodes.
Add another recursive CTE to construct the upward path (cte_upwards) for each leaf node. Now decrement the counter added in the initial CTE.
Use a cross apply to select the first node where the upward path reaches an exlusion count of zero.
Solution:
with cte as -- initial CTE
(
select OrgUnitId,
ParentOrgUnitId,
PersonId,
IsExcluded,
convert(int, IsExcluded) as 'ExclusionCount', -- new counter
0 as 'level_num'
from #OrgList
where ParentOrgUnitId is null
union all
select o.OrgUnitId,
o.ParentOrgUnitId,
o.PersonId,
o.IsExcluded,
cte.ExclusionCount + convert(int, o.isExcluded), -- increment counter
cte.level_num + 1
from #OrgList o
join cte on o.ParentOrgUnitId = cte.OrgUnitId
),
cte_upwards as
(
select cte.OrgUnitId,
cte.ParentOrgUnitId as 'NewParentOrgUnitId',
cte.IsExcluded,
cte.ExclusionCount,
cte.level_num
from cte
where cte.ParentOrgUnitId is not null -- only leaf nodes (not a root)
and not exists ( select top 1 'x' -- only leaf nodes (not an intermediate node)
from cte cp
where cp.ParentOrgUnitId = cte.OrgUnitId )
union all
select cte_upwards.OrgUnitId,
cte.ParentOrgUnitId,
cte.IsExcluded,
cte_upwards.ExclusionCount - cte.IsExcluded, -- decrement counter
cte.level_num
from cte_upwards
join cte
on cte.OrgUnitId = cte_upwards.NewParentOrgUnitId
)
select cte.OrgUnitId,
cte.ParentOrgUnitId,
cte.IsExcluded,
x.NewParentOrgUnitId,
coalesce(x.NewParentOrgUnitId, cte.ParentOrgUnitId) as 'Recalculated'
from cte
outer apply ( select top 1 cu.NewParentOrgUnitId
from cte_upwards cu
where cu.OrgUnitId = cte.OrgUnitId
and cu.ExclusionCount = 0 -- node without excluded parent nodes
order by cu.level_num desc ) x -- select lowest node in upwards path
order by cte.OrgUnitId;
Result:
OrgUnitId ParentOrgUnitId IsExcluded NewParentOrgUnitId Recalculated
----------- --------------- ---------- ------------------ ------------
1 NULL 0 NULL NULL
2 1 0 NULL 1
3 1 0 NULL 1
4 2 1 NULL 2
5 2 0 2 2
6 3 0 3 3
7 4 0 2 2
8 4 0 2 2
9 4 0 2 2
10 4 0 2 2
11 4 1 NULL 4
12 11 0 2 2
I've added a VirtualParentOrgUnitId, which contains the parent with excluded nodes taken into account. I've also added a counter, VirtualDistance, which will report how many real hops there are between this node and it's virtual parent.
VirtualParentOrgUnitId will use the parent's ID if is not excluded, otherwise it will use it's parents's VirtualParentOrgUnitId, which allows chaining of multiple levels.
DROP TABLE IF EXISTS #OrgList
CREATE TABLE #OrgList (
OrgUnitId int,
ParentOrgUnitId int,
PersonId int,
isExcluded bit
);
INSERT INTO #OrgList(OrgUnitId, ParentOrgUnitId, PersonId, isExcluded) VALUES
(1, NULL, 100, 0), (2,1, 101, 0), (3,1,102,0), (4,2,103,1), (5,2,104,0), (6,3,105,0), (7,4,106,0), (8,4,107,0), (9,4,108,0), (10,4,109,0), (11,4,110,1), (12,11,111,0)
DROP TABLE IF EXISTS #Excludes
CREATE Table #Excludes (
OrgUnitId int
);
INSERT INTO #Excludes VALUES (4), (11);
with cte as (
select OrgUnitId, ParentOrgUnitId, ParentOrgUnitId VirtualParentOrgUnitId, 1 as VirtualDistance , PersonId, isExcluded , 0 as level_num
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, IIF(o.ParentOrgUnitId IN (SELECT OrgUnitId FROM #Excludes),cte.VirtualParentOrgUnitId, o.ParentOrgUnitId ), IIF(o.ParentOrgUnitId IN (SELECT OrgUnitId FROM #Excludes),VirtualDistance + 1, 1 ), o.PersonId, o.isExcluded , level_num+1 as level_num
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
)
select * from cte
Here are the results:
OrgUnitId ParentOrgUnitId VirtualParentOrgUnitId VirtualDistance PersonId isExcluded level_num
----------- --------------- ---------------------- --------------- ----------- ---------- -----------
1 NULL NULL 0 100 0 0
2 1 1 0 101 0 1
3 1 1 0 102 0 1
6 3 3 0 105 0 2
4 2 2 0 103 1 2
5 2 2 0 104 0 2
7 4 2 1 106 0 3
8 4 2 1 107 0 3
9 4 2 1 108 0 3
10 4 2 1 109 0 3
11 4 2 1 110 1 3
12 11 2 2 111 0 4
;
with cte as (
select OrgUnitId, ParentOrgUnitId, PersonId, isExcluded , 0 as level_num, 0 as level_after_exclusions,
cast(',' as varchar(max)) + case isExcluded when 1 then cast(OrgUnitId as varchar(20)) else '' end as excludedmembers,
case isExcluded when 1 then ParentOrgUnitId end as newParentId
from #OrgList
where ParentOrgUnitId is null
UNION ALL
select o.OrgUnitId, o.ParentOrgUnitId, o.PersonId, o.isExcluded , level_num + 1, level_after_exclusions + case o.isExcluded when 1 then 0 else 1 end,
excludedmembers + case o.isExcluded when 1 then cast(o.OrgUnitId as varchar(20))+',' else '' end,
case when excludedmembers like '%,'+cast(o.ParentOrgUnitId as varchar(20))+',%' then newParentId else o.ParentOrgUnitId end
from #OrgList o
join cte on o.ParentOrgUnitId=cte.OrgUnitId
)
select *, level_num - level_after_exclusions as shiftbylevels
from cte

How to select RANK over condition (depending previous row)

What we have
I have table like this
id PlayerId Amount
----------- ----------- -----------
1 1 10
2 1 20
3 1 30
4 1 40
5 1 40
11 1 20
13 1 20
15 1 40
14 2 19
12 2 10
6 2 1
7 2 5
8 2 10
9 2 20
10 2 30
I have to select only rows where amount greater than previous row amount (per player).
So here is a query
SELECT a.id,
a.PlayerId,
a.Amount,
a.PreVval,
a.NextVal
FROM (SELECT id,
PlayerId,
Amount,
LAG(Amount) OVER (PARTITION BY PlayerId ORDER BY id) PreVval,
lead(Amount) OVER (PARTITION BY PlayerId ORDER BY id) NextVal
FROM dbo.Bets ) a
WHERE a.Amount > a.PreVval OR a.Amount < a.NextVal OR (a.PreVval IS NULL AND a.Amount < a.NextVal)
ORDER BY a.PlayerId, a.id
id PlayerId Amount PreVval NextVal
----------- ----------- ----------- ----------- -----------
1 1 10 NULL 20
2 1 20 10 30
3 1 30 20 40
4 1 40 30 40
13 1 20 20 40
15 1 40 20 NULL
6 2 1 NULL 5
7 2 5 1 10
8 2 10 5 20
9 2 20 10 30
10 2 30 20 10
12 2 10 30 19
14 2 19 10 NULL
Question
So now i need to select sets where increase step count > 4 , i mean 1,2,3,4 for player 1 and 6,7,8,9,10 for player 2
Query should run over 15m rows
The following query is an example where you can set the "step_count".
WITH Bets(id,PlayerId,Amount)
AS
(
SELECT 1,1,10 UNION ALL
SELECT 2,1,20 UNION ALL
SELECT 3,1,30 UNION ALL
SELECT 4,1,40 UNION ALL
SELECT 5,1,40 UNION ALL
SELECT 11,1,20 UNION ALL
SELECT 13,1,20 UNION ALL
SELECT 15,1,40 UNION ALL
SELECT 14,2,19 UNION ALL
SELECT 12,2,10 UNION ALL
SELECT 6,2,1 UNION ALL
SELECT 7,2,5 UNION ALL
SELECT 8,2,10 UNION ALL
SELECT 9,2,20 UNION ALL
SELECT 10,2,30
)
,split_ranges
as(
select *,case when lag(amount) over(partition by playerid order by id) > amount
or lag(amount) over(partition by playerid order by id) is null
then row_number() over(partition by playerid order by id)
end as rnk_val
from bets
)
,groups_data
as(
select *
,max(rnk_val) over(partition by playerid order by id) as fill_ranges
from split_ranges
)
select * from (
select *,count(*) over(partition by playerid,fill_ranges) as cnt
from groups_data
)x
where x.cnt>=4
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=6bd815da2cbfa8f65bc999e5736f2041
The following logic is bit tricky, but you can check this out-
DEMO HERE
WITH CTE
AS
(
SELECT * FROM
(
SELECT id,
PlayerId,
Amount,
LAG(Amount) OVER (PARTITION BY PlayerId ORDER BY id) PreVval,
lead(Amount) OVER (PARTITION BY PlayerId ORDER BY id) NextVal,
ISNULL(LAG(ID,3) OVER (PARTITION BY PlayerId ORDER BY id),0) LAG3,
ISNULL(LAG(ID,2) OVER (PARTITION BY PlayerId ORDER BY id),0) LAG2,
ISNULL(LAG(ID,1) OVER (PARTITION BY PlayerId ORDER BY id),0) LAG1,
ISNULL(LEAD(ID,1) OVER (PARTITION BY PlayerId ORDER BY id),0) LEAD1,
ISNULL(LEAD(ID,2) OVER (PARTITION BY PlayerId ORDER BY id),0) LEAD2,
ISNULL(LEAD(ID,3) OVER (PARTITION BY PlayerId ORDER BY id),0) LEAD3
FROM Bets
) a
WHERE a.Amount > a.PreVval
OR a.Amount < a.NextVal
OR (a.PreVval IS NULL AND a.Amount < a.NextVal)
)
SELECT id,PlayerId,Amount,PreVval,NextVal
FROM CTE A
WHERE ID-LAG3 = 3
OR LEAD1 - LAG2 = 3
OR LEAD2 - LAG1 = 3
OR LEAD3 - ID = 3

SQL Grouping and dense rank concept

I have a data set that looks like:
cust city hotel_id amount
-------------------------------
A 1 252 3160
B 1 256 1893
C 2 105 2188
D 2 105 3054
E 3 370 6107
F 2 110 3160
G 2 150 1893
H 3 310 2188
I 1 252 3160
J 1 250 4000
K 3 370 5000
L 3 311 1095
Query to display the top 3 hotels by revenue (Sum of amount) for each city?
Since same hotel can be booked by other customer in same city so we need to sum the amount to find total amount.
Expected output:
city hotel_id amount
---------------------------
1 252 6320
1 250 4000
1 256 1893
2 105 5242
2 110 3160
2 150 1893
3 370 11107
3 310 2188
3 311 1095
SELECT
t.city, t.hotel_id, t.amount
FROM
(
SELECT city, hotel_id, SUM(amount) AS amount,
ROW_NUMBER() OVER (PARTITION BY city ORDER BY SUM(amount) DESC) AS rn
FROM yourTable
GROUP BY city, hotel_id
) t
WHERE t.rn <= 3
ORDER BY t.city, t.amount DESC;
Demo here:
Rextester
To get the total sum for each hotel_id you need to group by that column first, then group by the city for syntax purposes. The #tmp table here should have all of the data you need, so then you just have to select the top 3 entries for each city from there.
SELECT city, hotel_id, SUM(amount) AS 'total' INTO #tmp
FROM [table]
GROUP BY hotel_id, city
(SELECT TOP 3 *
FROM #tmp
WHERE city = 1)
UNION
(SELECT TOP 3 *
FROM #tmp
WHERE city = 2)
UNION
(SELECT TOP 3 *
FROM #tmp
WHERE city = 3)

Sort on each SQL Server query not working when used with UNION

I have to merge data from 3 different tables which have different columns and my desired output should be based on different sort condition of each table.
I am using this query below but this query sort the overall result on based on Menu Position, How can I change this query to sort the result on individual query
SELECT DISTINCT
PID, MENU, Handler, PageLangID, ParentID, IssueID, CatID, MenuPosition
FROM (
--Pages Table
SELECT TOP 50
PageId AS PID,SUBSTRING(PageName,0,20) AS MENU,SUBSTRING(PageInternalLinkURL,0,24) AS Handler, PageLangID,PageInheritance AS ParentID, 1 AS IssueID, 1 AS CatID,
PageLinkPosition as MenuPosition FROM pg_Pages WHERE PageLangID = 1 ORDER BY PageLinkPosition DESC
UNION
--Article Category Table
SELECT TOP 20 p.PageID AS PID, SUBSTRING(c.ArticleCategoryName,0,20) AS MENU,SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler,
c.LangID,p.PageID,1, ArticleCategoryID, 1 AS MenuPosition FROM art_Category c JOIN pg_Pages p
ON c.PageID = p.PageID JOIN art_CategoryType ct ON c.Type = ct.CategoryTypeID WHERE c.LangID =1 AND c.Type =1
ORDER BY c.ArticleCategoryName
UNION
--Magazine Table
SELECT TOP 10 p.PageID AS PID, CAST(IssueCode AS varchar(10)),SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler,LangID,p.PageID AS ParentID, m.IssueID AS IssueID, 1,
1 AS MenuPosition FROM Magazine m JOIN pg_pages p ON m.PageID = p.PageID WHERE LangID =1 ORDER BY 2 DESC
) AS T WHERE T.PageLangID = 1 ORDER BY MenuPosition
Second Way
If I use below query then I get following error
Msg 156, Level 15, State 1, Line 4
Incorrect syntax near the keyword 'UNION'.
Msg 156, Level 15, State 1, Line 10
Incorrect syntax near the keyword 'UNION'.
QUERY
--Pages Table
SELECT TOP 50 PageId AS PID,SUBSTRING(PageName,0,20) AS MENU,SUBSTRING(PageInternalLinkURL,0,24) AS Handler, PageLangID,PageInheritance AS ParentID, 1 AS IssueID, 1 AS CatID,
PageLinkPosition as MenuPosition FROM pg_Pages WHERE PageLangID = 1 ORDER BY PageLinkPosition DESC
UNION
--Article Category Table
SELECT TOP 20 p.PageID AS PID, SUBSTRING(c.ArticleCategoryName,0,20) AS MENU,SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler,
c.LangID,p.PageID,1, ArticleCategoryID, 1 AS MenuPosition FROM art_Category c JOIN pg_Pages p
ON c.PageID = p.PageID JOIN art_CategoryType ct ON c.Type = ct.CategoryTypeID WHERE c.LangID =1 AND c.Type =1
ORDER BY c.ArticleCategoryName ASC
UNION
--Magazine Table
SELECT TOP 10 p.PageID AS PID, CAST(IssueCode AS varchar(10)),SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler,LangID,p.PageID AS ParentID, m.IssueID AS IssueID, 1,
1 AS MenuPosition FROM Magazine m JOIN pg_pages p ON m.PageID = p.PageID WHERE LangID =1 ORDER BY 2 DESC
SAMPLE DATA
PID MENU Handler PageLangID ParentID IssueID CatID MenuPosition
----------- -------------------- ------------------------ ----------- ----------- ----------- ----------- ------------
6 Book Review Page.aspx 1 6 1 18 1
6 Business Page.aspx 1 6 1 16 1
6 Culture Page.aspx 1 6 1 3 1
6 Economy Page.aspx 1 6 1 2 1
6 Finance Page.aspx 1 6 1 19 1
6 Infrastructure Page.aspx 1 6 1 17 1
6 Lifestyle Page.aspx 1 6 1 20 1
6 Others Page.aspx 1 6 1 21 1
6 People Page.aspx 1 6 1 7 1
6 Politics Page.aspx 1 6 1 1 1
6 Sports Page.aspx 1 6 1 4 1
12 102 Page.aspx 1 12 3 1 1
12 103 Page.aspx 1 12 4 1 1
12 106 Page.aspx 1 12 1 1 1
12 109 Page.aspx 1 12 5 1 1
1 Home Default.aspx 1 0 1 1 10
11 Video Videos.aspx 1 10 1 1 10
2 About Us Page.aspx 1 0 1 1 20
5 Articles Articles.aspx 1 0 1 1 30
6 Categories Page.aspx 1 0 1 1 40
12 Archive Page.aspx 1 0 1 1 50
3 News News.aspx 1 0 1 1 60
10 Multimedia Multimedia.aspx 1 0 1 1 70
I want first query to sort by ORDER BY PageLinkPosition DESC
Second query to sort by ORDER BY c.ArticleCategoryName ASC
and third query to sort by ORDER BY IssueCode DESC
I tried so many way but could not get it work properly.
Appreciate help in this regard
UPDATE: Working Query based on Mark Solution with small changes
SELECT DISTINCT PID, MENU, Handler, PageLangID, ParentID, IssueID, CatID, MenuPosition, block, rNum
FROM
( --Pages Table
SELECT TOP 50
PageId AS PID,SUBSTRING(PageName,0,20) AS MENU,SUBSTRING(PageInternalLinkURL,0,24) AS Handler, PageLangID,PageInheritance AS ParentID, 1 AS IssueID, 1 AS CatID,
PageLinkPosition as MenuPosition
,Block = 1
,rNum = ROW_NUMBER() OVER (ORDER BY PageLinkPosition DESC)
FROM pg_Pages
WHERE PageLangID = 1
UNION ALL -- Better to UNION ALL and then DISTINCT outside
--Article Category Table
SELECT TOP 20
p.PageID AS PID, SUBSTRING(c.ArticleCategoryName,0,20) AS MENU,SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler,
c.LangID,p.PageID,1, ArticleCategoryID, 1 AS MenuPosition
,Block = 2
,rNum = ROW_NUMBER() OVER (ORDER BY c.ArticleCategoryName )
FROM art_Category c
JOIN pg_Pages p ON c.PageID = p.PageID
JOIN art_CategoryType ct ON c.Type = ct.CategoryTypeID
WHERE c.LangID =1 AND c.Type =1
UNION ALL
--Magazine Table
SELECT TOP 10 p.PageID AS PID, CAST(IssueCode AS varchar(10)),SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler,LangID,p.PageID AS ParentID, m.IssueID AS IssueID, 1,
1 AS MenuPosition
,Block = 3
,rNum = ROW_NUMBER() OVER (ORDER BY CAST(IssueCode AS varchar(10))DESC)
FROM Magazine m
JOIN pg_pages p ON m.PageID = p.PageID
WHERE LangID =1
) AS T WHERE T.PageLangID = 1
ORDER BY Block, rNum
MARK Later update his solution to CTE version please check his answer.
This should work;
;WITH CTE AS
(
SELECT TOP 50
PageId AS PID
,SUBSTRING(PageName,0,20) AS MENU
,SUBSTRING(PageInternalLinkURL,0,24) AS Handler
,PageLangID
,PageInheritance AS ParentID
,1 AS IssueID
,1 AS CatID
,PageLinkPosition as MenuPosition
,Block = 1
,rNum = ROW_NUMBER() OVER (ORDER BY PageLinkPosition DESC)
FROM pg_Pages
WHERE PageLangID = 1
UNION ALL
--Article Category Table
SELECT TOP 20
p.PageID AS PID
,SUBSTRING(c.ArticleCategoryName,0,20) AS MENU
,SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler
,c.LangID
,p.PageID
,1
,ArticleCategoryID
,1 AS MenuPosition
,Block = 2
,rNum = ROW_NUMBER() OVER (ORDER BY c.ArticleCategoryName )
FROM art_Category c
JOIN pg_Pages p ON c.PageID = p.PageID
JOIN art_CategoryType ct ON c.Type = ct.CategoryTypeID
WHERE c.LangID =1 AND c.Type =1
UNION ALL
--Magazine Table
SELECT TOP 10
p.PageID AS PID
,CAST(IssueCode AS varchar(10))
,SUBSTRING(p.PageInternalLinkURL,0,24) AS Handler
,LangID
,p.PageID AS ParentID
,m.IssueID AS IssueID
,1
,1 AS MenuPosition
,Block = 3
,rNum = ROW_NUMBER() OVER (ORDER BY CAST(IssueCode AS varchar(10)))
FROM Magazine m
JOIN pg_pages p ON m.PageID = p.PageID
WHERE LangID =1
)
,Deduplicate AS
(
SELECT PID
,MENU
,Handler
,PageLangID
,ParentID
,IssueID
,CatID
,MenuPosition
,Block
,rNum
,Occ = ROW_NUMBER() OVER (PARTITION BY PID, MENU, Handler, PageLangID, ParentID, IssueID, CatID, MenuPosition ORDER BY (SELECT NULL))
FROM CTE
)
SELECT PID
,MENU
,Handler
,PageLangID
,ParentID
,IssueID
,CatID
,MenuPosition
FROM Deduplicate
WHERE Occ = 1
ORDER BY Block, rNum