How to combine GROUP BY and ROW_NUMBER? - sql

I hope following sample code is self-explanatory:
declare #t1 table (ID int,Price money, Name varchar(10))
declare #t2 table (ID int,Orders int, Name varchar(10))
declare #relation table (t1ID int,t2ID int)
insert into #t1 values(1, 200, 'AAA');
insert into #t1 values(2, 150, 'BBB');
insert into #t1 values(3, 100, 'CCC');
insert into #t2 values(1,25,'aaa');
insert into #t2 values(2,35,'bbb');
insert into #relation values(1,1);
insert into #relation values(2,1);
insert into #relation values(3,2);
select T2.ID AS T2ID
,T2.Name as T2Name
,T2.Orders
,T1.ID AS T1ID
,T1.Name As T1Name
,T1Sum.Price
FROM #t2 T2
INNER JOIN (
SELECT Rel.t2ID
,MAX(Rel.t1ID)AS t1ID
-- the MAX returns an arbitrary ID, what i need is:
-- ,ROW_NUMBER()OVER(Partition By Rel.t2ID Order By Price DESC)As PriceList
,SUM(Price)AS Price
FROM #t1 T1
INNER JOIN #relation Rel ON Rel.t1ID=T1.ID
GROUP BY Rel.t2ID
)AS T1Sum ON T1Sum.t2ID = T2.ID
INNER JOIN #t1 T1 ON T1Sum.t1ID=T1.ID
Result:
T2ID T2Name Orders T1ID T1Name Price
1 aaa 25 2 BBB 350,00
2 bbb 35 3 CCC 100,00
What i need is commented above, a way to get the ROW_NUMBER but also to Group By in the first place. So i need the sum of all T1-prices grouped by T2.ID in the relation-table and in the outer query the t1ID with the highest price.
In other words: How to change MAX(Rel.t1ID)AS t1ID to somewhat returning the ID with the highest price?
So the desired result is(notice that first T1ID changed from 2 to 1 since it has the higher price):
T2ID T2Name Orders T1ID T1Name Price
1 aaa 25 1 AAA 350,00
2 bbb 35 3 CCC 100,00
Note: in case you're wondering why i don't multiply Orders with Price: they are not realated(so i should have left off this column since it's a bit ambiguous, please ignore it, i've just added it to make all less abstract). Actually Orders must remain unchanged, that's the reason for the sub-query approach to join both and the reason why i need to group by in the first place.
Conclusion: obviously the core of my question can be answered by the OVER clause that can be applied to any aggregate function like SUM(see Damien's answer) what was new to me. Thank you all for your working approaches.

Wow, the other answers look complex - so I'm hoping I've not missed something obvious.
You can use OVER/PARTITION BY against aggregates, and they'll then do grouping/aggregating without a GROUP BY clause. So I just modified your query to:
select T2.ID AS T2ID
,T2.Name as T2Name
,T2.Orders
,T1.ID AS T1ID
,T1.Name As T1Name
,T1Sum.Price
FROM #t2 T2
INNER JOIN (
SELECT Rel.t2ID
,Rel.t1ID
-- ,MAX(Rel.t1ID)AS t1ID
-- the MAX returns an arbitrary ID, what i need is:
,ROW_NUMBER()OVER(Partition By Rel.t2ID Order By Price DESC)As PriceList
,SUM(Price)OVER(PARTITION BY Rel.t2ID) AS Price
FROM #t1 T1
INNER JOIN #relation Rel ON Rel.t1ID=T1.ID
-- GROUP BY Rel.t2ID
)AS T1Sum ON T1Sum.t2ID = T2.ID
INNER JOIN #t1 T1 ON T1Sum.t1ID=T1.ID
where t1Sum.PriceList = 1
Which gives the requested result set.

Undoubtly this can be simplified but the results match your expectations.
The gist of this is to
Calculate the maximum price in a seperate CTE for each t2ID
Calculate the total price in a seperate CTE for each t2ID
Combine the results of both CTE's
SQL Statement
;WITH MaxPrice AS (
SELECT t2ID
, t1ID
FROM (
SELECT t2.ID AS t2ID
, t1.ID AS t1ID
, rn = ROW_NUMBER() OVER (PARTITION BY t2.ID ORDER BY t1.Price DESC)
FROM #t1 t1
INNER JOIN #relation r ON r.t1ID = t1.ID
INNER JOIN #t2 t2 ON t2.ID = r.t2ID
) maxt1
WHERE maxt1.rn = 1
)
, SumPrice AS (
SELECT t2ID = t2.ID
, Price = SUM(Price)
FROM #t1 t1
INNER JOIN #relation r ON r.t1ID = t1.ID
INNER JOIN #t2 t2 ON t2.ID = r.t2ID
GROUP BY
t2.ID
)
SELECT t2.ID
, t2.Name
, t2.Orders
, mp.t1ID
, t1.ID
, t1.Name
, sp.Price
FROM #t2 t2
INNER JOIN MaxPrice mp ON mp.t2ID = t2.ID
INNER JOIN SumPrice sp ON sp.t2ID = t2.ID
INNER JOIN #t1 t1 ON t1.ID = mp.t1ID

;with C as
(
select Rel.t2ID,
Rel.t1ID,
t1.Price,
row_number() over(partition by Rel.t2ID order by t1.Price desc) as rn
from #t1 as T1
inner join #relation as Rel
on T1.ID = Rel.t1ID
)
select T2.ID as T2ID,
T2.Name as T2Name,
T2.Orders,
T1.ID as T1ID,
T1.Name as T1Name,
T1Sum.Price
from #t2 as T2
inner join (
select C1.t2ID,
sum(C1.Price) as Price,
C2.t1ID
from C as C1
inner join C as C2
on C1.t2ID = C2.t2ID and
C2.rn = 1
group by C1.t2ID, C2.t1ID
) as T1Sum
on T2.ID = T1Sum.t2ID
inner join #t1 as T1
on T1.ID = T1Sum.t1ID

The deduplication (to select the max T1) and the aggregation need to be done as distinct steps. I've used a CTE since I think this makes it clearer:
;WITH sumCTE
AS
(
SELECT Rel.t2ID, SUM(Price) price
FROM #t1 AS T1
JOIN #relation AS Rel
ON Rel.t1ID=T1.ID
GROUP
BY Rel.t2ID
)
,maxCTE
AS
(
SELECT Rel.t2ID, Rel.t1ID,
ROW_NUMBER()OVER(Partition By Rel.t2ID Order By Price DESC)As PriceList
FROM #t1 AS T1
JOIN #relation AS Rel
ON Rel.t1ID=T1.ID
)
SELECT T2.ID AS T2ID
,T2.Name as T2Name
,T2.Orders
,T1.ID AS T1ID
,T1.Name As T1Name
,sumT1.Price
FROM #t2 AS T2
JOIN sumCTE AS sumT1
ON sumT1.t2ID = t2.ID
JOIN maxCTE AS maxT1
ON maxT1.t2ID = t2.ID
JOIN #t1 AS T1
ON T1.ID = maxT1.t1ID
WHERE maxT1.PriceList = 1

Related

sql for breaking one record into 2 records

I have 2 tables with data as below-
table 1-
id name id_start_date
---------------------
345 Fiamma 1/01/1900
Table 2-
Change_Date Old_id New_id Users
-------------------------------
15/06/2017 123 345 abc#xyz.com
I'm looking for data as below-
id product_name start_date end_date
-----------------------------------
123 Fiamma 1/01/1900 15/06/2017
345 Fiamma 15/06/2017 31/12/2099
Basically I want to break the table 2 data into 2 records one with old id and the start and end dates for that id and the other with the new id with start and end dates.
Cheers
select t2.old_id as id, t1.name as product_name, t1.start_date, t2.change_date as end_date
from table1 t1
INNER JOIN table2 t2 ON t1.id = t2.new_id
UNION
select t1.id as id, t1.name as product_name, t2.change_date, "" as end_date
from table1 t1
INNER JOIN table2 t2 ON t1.id = t2.new_id
here is a test that you can run :
create table #table1
(
id int,
name varchar(50),
start_date datetime
)
GO
create table #table2
(
change_date datetime,
Old_id int,
New_id int,
users varchar(50)
)
GO
insert into #table1
values (345,'Fiamma','01/01/1900')
insert into #table2
values ('15/06/2017',123,345,'abc#xyz.com')
select * from #table1
select * from #table2
select t2.old_id as id, t1.name as product_name, t1.start_date as start_date, t2.change_date as end_date
from #table1 t1
INNER JOIN #table2 t2 ON t1.id = t2.new_id
UNION
select t1.id as id, t1.name as product_name, t2.change_date as start_date , DATEADD(YEAR,+10,GETDATE()) as end_date
from #table1 t1
INNER JOIN #table2 t2 ON t1.id = t2.new_id
drop table #table1
drop table #table2

Join 3 tables based on Partition by or Self join

I have following tables
tbl1 (empID, Name, ctNo)
tbl2 (salID, empID, salry)
tbl3 (histryID, salDate, empID, salID)
Expect Output (empID,Name,salary,Salary_Date)
For Salary date, I only want the last salary_date to be displayed.
(maybe By using Partitiion by or Selfjoin methods.)
Thank You.
Below query can answer your question
Create table #tbl1 (empID int , Name varchar(11), ctNo int)
Create table #tbl2 (salID int, empID int, salry int)
Create table #tbl3 (histryID int, salDate DATE, empID int, salID int)
INSERT INTO #tbl1
values(1,'Dinesh',23),(2,'Raj',11)
INSERT INTO #tbl2
values(1,1,1000),(2,1,2000),(3,2,100),(4,2,500)
INSERT INTO #tbl3
values(1,'20020101',1,1),(2,'20020201',1,2),(3,'20020101',2,3),(4,'20020201',2,4)
SELECT M1.* FROM(
SELECT T1.empID,T1.Name,T2.salry,T3.salDate FROM
#tbl1 AS T1
INNER join #tbl2 AS T2
ON T1.empID=T2.empID
INNER join #tbl3 AS T3
ON T1.empID=T3.empID AND T2.salID =T3.salID
) AS M1
INNER JOIN
(
SELECT empID,MAX(salDate) AS SALDATE FROM #tbl3 GROUP BY empID) AS M2
ON M1.SALDATE=M2.salDate AND M1.empID=M2.empID
You could try something like this:
select t1.empID, t1.Name, t2.salry as salary, top (t3.salDate) as Salary_Date from tbl1 t1, tbl2 t2, tbl3 t3 on t1.empID=t2.empID and t1.empID=t3.empID
Note: Pointing in correct direction. Not tested.
You can do this with row_number window function:
select t1.empid, t1.name, t3.saldate, t2.salary
from tbl1 t1
join(select *, row_number() over(partition by empid order by saldate desc) rn
from tbl3)t3 on t1.empid = t3.empid and t3.rn = 1
join tbl2 t2 on t3.empid = t2.empid AND t3.salid = t2.salid

Query for earliest datetime and corresponding number field

I'm attempting to update a table with a dollar amount based on the earliest datetime field from another table. For example:
Table 1
ID|INITIAL_ANNUAL_RATE_AMT|
1 | NULL (I want to update this to 25.02)
Table 2
ID|ANNUAL_RATE_AMT|STARTING_DATE|
1 |25.01 |1/1/2014
1 |25.02 |1/1/2013
I've got a query like this that retreives the earliest date from table 2 and the corresponding objects ID:
select ID,
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from t2
group by t2.ID
But how can I leverage this into an update statement that sets the INITIAL_ANNUAL_RATE_AMT in table 1 to the earliest corresponding value in table 2?
Something like this (which currently fails):
update t1
set t1.Initial_Annual_Rate__c = t3.ANNUAL_RATE_AMT
from t1, t2
left join
(select t2.ID
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from t2
group by t2.DEAL_ID)
as t3 ON (t3.DEAL_ID = t1.DEAL_ID)
One way is to use a CTE
;WITH C AS(
SELECT t.ID, EARLIEST_START_DATE, ANNUAL_RATE_AMT FROM(
select ID,
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from #Table2 AS t2
group by t2.ID) t
INNER JOIN #Table2 AS t2 ON t2.ID = t.ID AND t.EARLIEST_START_DATE = t2.STARTING_DATE
)
UPDATE t1
SET INITIAL_ANNUAL_RATE_AMT = C.ANNUAL_RATE_AMT
FROM #Table1 AS t1
INNER JOIN C ON C.ID = t1.ID
SQLFIDDLE
Another method, using a window function to get the first row in each ID partitioned set:
-- Setup test data
declare #table1 table (ID int, INITIAL_ANNUAL_RATE_AMT decimal(9,2))
declare #table2 table (ID int, ANNUAL_RATE_AMT decimal(9,2), STARTING_DATE date)
INSERT INTO #table1 (ID, INITIAL_ANNUAL_RATE_AMT)
SELECT 1, NULL
INSERT INTO #table2 (ID, ANNUAL_RATE_AMT, STARTING_DATE)
SELECT 1,25.01,'1/1/2014'
UNION SELECT 1,25.02,'1/1/2013'
-- Do the update
;with table2WithIDRowNumbers as (
select ID, ANNUAL_RATE_AMT, STARTING_DATE, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY STARTING_DATE) as rowNumber
FROM #table2
)
UPDATE t1
SET INITIAL_ANNUAL_RATE_AMT=t2.ANNUAL_RATE_AMT
FROM table2WithIDRowNumbers t2
INNER JOIN #table1 t1 ON t1.ID=t2.ID
where t2.rowNumber=1
-- Show the result
SELECT * from #table1

SQL Join query to show records if exists in master table or not

Table1
id name color
1,'a','red'
2,'a','blue'
3,'b','red'
4,'c','red'
5,'d','red'
6,'a','green'
declare #t1 table (id int, name varchar(10),color varchar(5))
insert into #t1 values(1,'a','red')
insert into #t1 values(2,'a','blue')
insert into #t1 values(3,'b','red')
insert into #t1 values(4,'c','red')
insert into #t1 values(5,'d','red')
table t2 (master table )
color
red
blue
green
declare #t2 table (color varchar(5))
insert into #t2 values ('red')
insert into #t2 values ('blue')
insert into #t2 values ('green')
The output will be
'a','red'
'a','blue'
'a','green'
We need to retrieve the name from table 1 what are all having all the t2 color...
You can get the names in t1 that match all master colors using group by, having, and join:
select t1.name
from t1 join
t2
on t1.color = t2.color
group by t1.name
having count(distinct t1.color) = (select count(*) from t2);
This returns the names. If you want the detailed rows, then use this as a subquery or CTE and join t1 back to these results.
And to get the detailed rows:
with n as (
select t1.name
from t1 join
t2
on t1.color = t2.color
group by t1.name
having count(distinct t1.color) = (select count(*) from t2)
)
select t1.*
from t1 join
n
on t1.name = n.name;

Join data from two tables and top from table 2

i have this tables:
Table1:
id Name
1 Example1
2 Example2
Table2:
id Date..............
1 5.2.2014........
1 6.2.2014.........
1 6.2.2014........
2 16.1.2014.......
2 17.1.2014.......
And I need take id and Name from table1 and join table1.id = table2.id and from table2 take only top 1 row...
Example:
id Name Date
1 Example1 5.2.2014
2 Example2 16.1.2014
It is possible?
You can use row_number() to filter out all but the latest row per id:
select *
from (
select row_number() over (partition by id order by Date desc) as rn
, *
from Table2
) as t2
join Table1 as t1
on t1.id = t2.id
where t2.rn = 1 -- Only latest row
Well, a simple attempt would be
SELECT t1.*,
(SELECT TOP 1 t2.Date FROM Table2 t2 WHERE t2.ID = t1.ID t2.Date) t2Date
FROM Table1 t1
If you were using SQL Server, you could use ROW_NUMBER
Something like
;WITH Vals AS (
SELECT t1.ID,
t1.Name,
t2.Date,
ROW_NUMBER() OVER(PARTITION BY t1.ID ORDER BY t2.Date) RowID
FROm Table1 t1 LEFT JOIN
Table2 t2 ON t1.ID
)
SELECT *
FROM Vals
WHERE RowID = 1
Select t1.id, t1.name , MIN(t2.date)
From table1 t1
Inner Join table2 t2
On t1.id=t2.id
Group By t1.id, t1.name