SQL SERVER 2008 R2. Two tables having a 1-many relationship, and want some fields from both, but on the many side, only values from a certain row. That row is specified by some condition, in my case it should the row having the smallest ClaimId for that PaymentId.
IF OBJECT_ID('tempdb..#Payments') IS NOT NULL DROP TABLE #Payments
CREATE TABLE #Payments ( PaymentId int UNIQUE NOT NULL )
INSERT INTO #Payments ( PaymentId )
VALUES (1), (2), (3)
IF OBJECT_ID('tempdb..#Claims') IS NOT NULL DROP TABLE #Claims
CREATE TABLE #Claims ( PaymentId int NOT NULL, ClaimId int NOT NULL, FirstName varchar(50) NOT NULL )
INSERT INTO #Claims ( PaymentId, ClaimId, FirstName )
VALUES (1, 51,'Joe'), (1, 57,'Jane'), (2, 62,'Spot'), (2, 63,'Rover'), (3, 88,'Sue'), (3, 89,'Sally')
SELECT * FROM #Payments p
JOIN #Claims c ON p.PaymentId=c.PaymentId
WHERE c.ClaimId=MIN(ClaimId)
GROUP BY p.PaymentId
HAVING MIN(ClaimId)
The desired result is as follows, where the claim is from the row having the minimum claimId for a given paymentId. ClaimId may not be ordered, so seeking minimum, not first.
PaymentId ClaimId FirstName
1 51 Joe
2 62 Spot
3 88 Sue
I'd be happy to refer to an existing question/answer, but don't know how to word this that I find something similar. Perhaps Get field value from a record that causes an aggregate condition to be true but I didn't understand it.
You can use the ROW_NUMBER window function like this:
SELECT PaymentId, ClaimId, FirstName
FROM
(
SELECT
p.PaymentId,
c.ClaimId,
c.FirstName,
ROW_NUMBER() OVER (PARTITION BY p.PaymentId ORDER BY c.ClaimId) as RN
FROM #Payments p
JOIN #Claims c ON p.PaymentId=c.PaymentId
) as T
WHERE RN = 1
In SQL Server, I like to use outer apply for this:
select p.*, c.*
from #Payments p outer apply
(select top 1 c.*
from #Claims c
where c.paymentid = p.paymentid
order by c.claimid
) c;
solution using RANK()
;WITH A
AS (SELECT p.PaymentId
, c.ClaimId
, c.FirstName
, rn = RANK() OVER(PARTITION BY P.PaymentId ORDER BY ClaimId ASC)
FROM #Payments p
JOIN #Claims c ON p.PaymentId = c.PaymentId)
SELECT PaymentId
, ClaimId
, FirstName
FROM A
WHERE rn = 1;
use windows function row_number to provide an order
;with cte as
(
select PaymentID, ClaimID, row_number() over (partition by PaymentID order by ClaimID ) as rn
from #Claims
)
select p.*,cte.ClaimID
from #Payments p
join cte on cte.paymentID = p.paymentID
where rn=1 --limits to earliest claimid
Both sentences return same result, first one select MIN(ClaimId) for every PaymentId:
SELECT P.*, C.*
FROM #Payments P
INNER JOIN #Claims C ON C.PaymentId = P.PaymentId
WHERE C.ClaimID IN (SELECT MIN(ClaimID) OVER (PARTITION BY PaymentId) FROM #Claims);
Second uses a CTE to find MIN(ClaimId) before to join to Payments table:cÂș
WITH PY AS
(
SELECT PaymentId, MIN(ClaimId) as ClaimID
FROM #Claims
GROUP BY PaymentId
)
SELECT PY.PayMentId, PY.ClaimId, C.FirstName, P.*
FROM PY
LEFT JOIN #Claims c ON p.ClaimId = c.ClaimId;
LEFT JOIN #Payments P ON P.PaymentId = PY.PaymentId
+-----------+---------+-----------+
| PayMentId | ClaimId | FirstName |
+-----------+---------+-----------+
| 1 | 51 | Joe |
+-----------+---------+-----------+
| 2 | 62 | Spot |
+-----------+---------+-----------+
| 3 | 88 | Sue |
+-----------+---------+-----------+
Check it here: http://rextester.com/KRT45653
Related
I'm in need of some brainstorming. I have built a query that shows me what I need. However the ask now is to use this list of records and exclude records based on a certain criteria.
This is my current output from the query built:
Patient | Action | Date
james | REG | 2019/01/01
James | CUR | 2019/01/15
Jacon | REG | 2019/01/12
Jacob | REG | 2019/01/13
Main | CUR | 2019/01/01
Main | REG | 2019/01/05
Lucy | REG | 2019/01/08
Lucy | CUR | 2019/01/09
Lucy | CUR | 2019/01/10
Based on the sample data from above I want to remove any patients where the first record is 'REG' and the following Action is 'CUR'. So in this example I only want to remove James.
Any Ideas on what I should do?
Thank you for your help!
Please group your data first by using dense_rank and row_number, then benefiting from temp tables, get the data you are looking for.
CREATE TABLE #temp (Patient VARCHAR(50), Action VARCHAR(3))
Insert INTO #temp VALUES
('james','REG'),
('james','CUR'),
('Jacob','REG'),
('Jacob','REG'),
('Main','CUR'),
('Main','REG'),
('Lucy','REG'),
('Lucy','CUR'),
('Lucy','CUR')
SELECT *, DENSE_RANK() OVER (ORDER BY Patient ASC) GroupNo,
ROW_NUMBER() OVER (partition BY Patient ORDER BY Patient ASC) GroupOrder
INTO #PatientsWithGroup
FROM #temp
SELECT MIN(c1.GroupNo) GroupNo
INTO #PatsToEliminate
FROM #PatientsWithGroup c1
INNER JOIN #PatientsWithGroup c2 ON c1.GroupNo=c2.GroupNo
WHERE (c1.GroupOrder=1 AND c1.Action='REG') AND (c2.GroupOrder = 2 AND c2.Action='CUR')
HAVING COUNT(c1.Patient)<3
SELECT *
FROM #PatientsWithGroup p
WHERE p.GroupNo NOT IN (SELECT GroupNo FROM #PatsToEliminate)
You can use the LEAD function to look ahead.
CREATE TABLE #Patients (
ID int IDENTITY(1,1),
Patient varchar(50),
[Action] varchar(50)
);
INSERT INTO #Patients (Patient, [Action])
VALUES
('james', 'REG'),
('James', 'CUR'),
('Jacon', 'REG'),
('Jacob', 'REG'),
('Main', 'CUR'),
('Main', 'REG'),
('Lucy', 'REG'),
('Lucy', 'CUR'),
('Lucy', 'CUR');
SELECT * FROM #Patients;
WITH
PatientWithNextAction AS (
SELECT
Patient,
[Action],
LEAD([Action]) OVER(PARTITION BY Patient ORDER BY ID) NextAction
FROM
#Patients
)
DELETE
FROM
#Patients
WHERE
Patient IN (
SELECT
Patient
FROM
PatientWithNextAction
WHERE
[Action] = 'REG'
AND NextAction = 'CUR'
);
SELECT * FROM #Patients;
DROP TABLE #Patients;
Try this:
select 1 as ordre, 'james' as Patient, 'REG' as Action into #tmp
union select 2,'James', 'CUR'
union select 3,'Jacon', 'REG'
union select 4,'Jacob', 'REG'
union select 5,'Main' , 'CUR'
union select 6,'Main' , 'REG'
union select 7,'Lucy' , 'REG'
union select 8,'Lucy' , 'CUR'
union select 9,'Lucy' , 'CUR'
;with cte as
(
select ordre, Patient, [Action], RANK () OVER (
PARTITION BY Patient
ORDER BY ordre
) Patient_order from #tmp a
)
select * from cte a where not exists(select 1 from cte b where a.Patient = b.Patient and b.Patient_order = 1 and Action = 'REG'
and exists(select 1 from cte c where c.Patient = b.Patient and c.Patient_order = 2 and Action = 'CUR')
)
Consider the folowing table
Id PersonId Address AddressTypeId
--------------------------------------------------------------------
1 1 AI1P1T1 1
2 1 AI2P1T2 2
3 2 AI3P2T2 2
I want to write a query to print the list of Addresses of Persons who have AddressType =1 or AddressTypeId=2 and
When person has AddressType =1 then select it,
else select person with AddressType =2
Expected result:
Address
--------------
AI1P1T1
AI3P2T2
Good day,
Please check if this solve your needs:
/***************************** DDL+DML */
drop table if exists T;
create table T(Id int,PersonId int, [Address] nvarchar(10), AddressTypeId int)
INSERT T(Id,PersonId, [Address], AddressTypeId)
values
(1,1,'AI1P1T1',1),
(2,1,'AI2P1T2',2),
(3,2,'AI3P2T2',2)
GO
select * from T
GO
/***************************** Solution */
With MyCTE as (
select *, ROW_NUMBER() OVER (partition by PersonId order by AddressTypeId) as RN
from T
)
select [Address]
from MyCTE
where
AddressTypeId in (1,2) -- if there can be only positive numbers then you can use "< 3"
and RN = 1
GO
You can try this also using joins:
select t1.PersonId,t1.Address from #T t1
inner join (select personid,min(AddressTypeId)atype from #T
group by PersonId )x
on x.atype=t1.AddressTypeId and x.PersonId=t1.PersonId
I would write a subquery to make ROW_NUMBER by window function, then use MAX in the main query.
SELECT
PersonId, MAX(Address) Address
FROM
(SELECT
PersonId,
(CASE
WHEN ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY PersonId) = 1
THEN Address
END) Address
FROM
T
WHERE
AddressTypeId IN (1,2)
) t1
GROUP BY
PersonId
sqlfiddle
[Results]:
| PersonId | Address |
+----------+---------+
| 1 | AI1P1T1 |
| 2 | AI3P2T2 |
Here's the top 1 with ties trick:
select top 1 * with ties
from yourtable
order by row_number() over (partition by PersonId order by AddressTypeId)
This will also work for versions <2012, and can return every field
You could use an union between the result for the result for only 1, only 2 and 1 when 1 and 2
select Address
from my_table m
Inner join (
select PersonId , count(distinct distinct AddressTypeId)
from my_table
where AddressTypeId in (1, 2)
group by PersonId
having count(distinct AddressTypeId) = 2
) t on t.personId = m.personId andm.AddressTypeId = 1
UNION
select Address
from my_table m
Inner join (
select PersonId , count(distinct distinct AddressTypeId)
from my_table
where AddressTypeId in ( 2)
group by PersonId
having count(distinct AddressTypeId) = 1
) t on t.personId = m.personId andm.AddressTypeId = 2
UNION
select Address
from my_table m
Inner join (
select PersonId , count(distinct distinct AddressTypeId)
from my_table
where AddressTypeId in ( 1)
group by PersonId
having count(distinct AddressTypeId) = 1
) t on t.personId = m.personId andm.AddressTypeId = 1
Try this one
select personId, last_value(Address) over(partition by personId order by AddressTypeId) as Address
from table
--use the where statement optionally
--where AddressTypeId in (1,2);
I have a table with customer IDs, location IDs, and their order values. I need to select the location ID for each customer with the largest spend
Customer | Location | Order $
1 | 1A | 100
1 | 1A | 20
1 | 1B | 100
2 | 2A | 50
2 | 2B | 20
2 | 2B | 50
So I would get
Customer | Location | Order $
1 | 1A | 120
2 | 2B | 70
I tried something like this:
SELECT
a.CUST
,a.LOC
,c.BOOKINGS
FROM (SELECT DISTINCT TOP 1 b.CUST, b.LOC, sum(b.ORDER_VAL) as BOOKINGS
FROM ORDER_TABLE b
GROUP BY b.CUST, b.LOC
ORDER BY BOOKINGS DESC) as c
INNER JOIN ORDER_TABLE a
ON a.CUST = c.CUST
But that just returns the top order.
Just use variables to emulate ROW_NUM()
DEMO
SELECT *
FROM ( SELECT `Customer`, `Location`, SUM(`Order`) as `Order`,
#rn := IF(#customer = `Customer`,
#rn + 1,
IF(#customer := `Customer`, 1, 1)
) as rn
FROM Table1
CROSS JOIN (SELECT #rn := 0, #customer := '') as par
GROUP BY `Customer`, `Location`
ORDER BY `Customer`, SUM(`Order`) DESC
) t
WHERE t.rn = 1
Firs you have to sum the values for each location:
select Customer, Location, Sum(Order) as tot_order
from order_table
group by Customer, Location
then you can get the maximum order with MAX, and the top location with a combination of group_concat that will return all locations, ordered by total desc, and substring_index in order to get only the top one:
select
Customer,
substring_index(
group_concat(Location order by tot_order desc),
',', 1
) as location,
Max(tot_order) as max_order
from (
select Customer, Location, Sum(Order) as tot_order
from order_table
group by Customer, Location
) s
group by Customer
(if there's a tie, two locations with the same top order, this query will return just one)
This seems like an order by using aggregate function problem. Here is my stab at it;
SELECT
c.customer,
c.location,
SUM(`order`) as `order_total`,
(
SELECT
SUM(`order`) as `order_total`
FROM customer cm
WHERE cm.customer = c.customer
GROUP BY location
ORDER BY `order_total` DESC LIMIT 1
) as max_order_amount
FROM customer c
GROUP BY location
HAVING max_order_amount = order_total
Here is the SQL fiddle. http://sqlfiddle.com/#!9/2ac0d1/1
This is how I'd handle it (maybe not the best method?) - I wrote it using a CTE first, only to see that MySQL doesn't support CTEs, then switched to writing the same subquery twice:
SELECT B.Customer, C.Location, B.MaxOrderTotal
FROM
(
SELECT A.Customer, MAX(A.OrderTotal) AS MaxOrderTotal
FROM
(
SELECT Customer, Location, SUM(`Order`) AS OrderTotal
FROM Table1
GROUP BY Customer, Location
) AS A
GROUP BY A.Customer
) AS B INNER JOIN
(
SELECT Customer, Location, SUM(`Order`) AS OrderTotal
FROM Table1
GROUP BY Customer, Location
) AS C ON B.Customer = C.Customer AND B.MaxOrderTotal = C.OrderTotal;
Edit: used the table structure provided
This solution will provide multiple rows in the event of a tie.
SQL fiddle for this solution
How about:
select a.*
from (
select customer, location, SUM(val) as s
from orders
group by customer, location
) as a
left join
(
select customer, MAX(b.tot) as t
from (
select customer, location, SUM(val) as tot
from orders
group by customer, location
) as b
group by customer
) as c
on a.customer = c.customer where a.s = c.t;
with
Q_1 as
(
select customer,location, sum(order_$) as order_sum
from cust_order
group by customer,location
order by customer, order_sum desc
),
Q_2 as
(
select customer,max(order_sum) as order_max
from Q_1
group by customer
),
Q_3 as
(
select Q_1.customer,Q_1.location,Q_1.order_sum
from Q_1 inner join Q_2 on Q_1.customer = Q_2.customer and Q_1.order_sum = Q_2.order_max
)
select * from Q_3
Q_1 - selects normal aggregate, Q_2 - selects max(aggregate) out of Q_1 and Q_3 selects customer,location, sum(order) from Q_1 which matches with Q_2
I need to be able to find the total count of orders placed by a customer, but also find the top product in one query. For example in the following structure,
CREATE TABLE #Cust (CustId INT, CustName VARCHAR(50))
CREATE TABLE #Product (ProductId INT, ProductName VARCHAR(10) )
CREATE TABLE #Orders (CustId INT, ProductId INT, OrderTaken BIT)
INSERT #Cust
( CustId, CustName )
VALUES ( 1, 'Paul' ),
( 2, 'F' ),
( 3, 'Francis' )
INSERT #Product
( ProductId, ProductName )
VALUES ( 1, 'Table' ),
( 2, 'Chair' )
INSERT #Orders
( CustId, ProductId, OrderTaken )
VALUES ( 1, 1, 1 ),
( 1, 1, 1 ),
( 1, 2, 1 ),
( 2, 1, 1 )
I have come up with a query,
SELECT * FROM #Cust AS C OUTER APPLY
(
SELECT TOP 1 SQ.ProductId, SUM(SQ.TotalCount) AS TotalQty FROM
(
SELECT O.ProductId, COUNT(*) TotalCount
FROM #Orders AS O WHERE O.CustId = C.CustId
GROUP BY O.CustId , O.ProductId
) SQ
GROUP BY SQ.ProductId
) X
But, that is not giving me the result I am looking for, for Paul it is giving me the correct ProductId, but a count of that product alone.
I want the a Query to return,
CustId | CustName | ProductId | TotalQty
--------+---------------+---------------+------------
1 | Paul | 1 | 3
2 | F | 1 | 1
3 | Francis | NULL | NULL
One option is with the WITH TiES clause
Select Top 1 with ties
CustID
,CustName
,ProductId
,TotalQty
From (
Select C.CustID
,C.CustName
,O.ProductId
,TotalQty = count(O.CustId) over (Partition By O.CustID)
,ProdCount = count(O.CustId) over (Partition By O.CustID,O.ProductID)
From #Cust C
Left Join #Orders O on C.CustID=O.CustId
) A
Order by Row_Number() over (Partition By CustID Order by ProdCount Desc)
Returns
CustID CustName ProductId TotalQty
1 Paul 1 3
2 F 1 1
3 Francis NULL 0
Try
SELECT c.*, ProductId, CustProdTotal, CustTotal
FROM #Cust AS C
OUTER APPLY (
select top(1) with ties ProductId, CustProdTotal, CustTotal
from (
select *, count(OrderTaken) over() as CustTotal
, count(OrderTaken) over(partition by ProductId) as CustProdTotal
from #Orders o
where O.CustId = C.CustId) x
order by row_number() over(order by CustProdTotal desc)
) z
Similar question has been answered with a nice explanation here
(Benefit here : common concept of join used).
(disadvantage : query may not be efficient for large records)
I have modified the solution for your scenario
SELECT s.CustId, s.CustName, s.ProductId, m.TotalOrderTaken
FROM (SELECT p.CustId, p.CustName, t.ProductId, COUNT(*) AS ProductIdCount
FROM #Cust AS p
JOIN #Orders AS t
ON p.CustId = t.CustId
GROUP BY p.CustId, p.CustName, t.ProductId
) AS s
JOIN (SELECT s.CustId, MAX(s.ProductIdCount) AS MaxProductIdCount, sum(s.ProductIdCount) TotalOrderTaken
FROM (
SELECT p.CustId,p.CustName, t.ProductId, COUNT(*) AS ProductIdCount
FROM #Cust AS p
JOIN #Orders AS t
ON p.CustId = t.CustId
GROUP BY p.CustId, p.CustName, t.ProductId
) AS s
GROUP BY s.CustId
) AS m
ON s.CustId = m.CustId AND s.ProductIdCount = m.MaxProductIdCount
Works on SQL Server 2005 onwards.
;with cte1 as (select c.custid, c.custname, o.productid, count(*) as TotalQty
from #cust c
left join #orders o on c.custid=o.custid
left join #product p on p.productid=o.productid
group by c.custid, c.custname, o.productid)
,cte2 as (select custid, max(TotalQty) as TopQty
from cte1
group by custid)
Select cte1.*
from cte1
inner join cte2 on cte1.custid=cte2.custid and cte1.TotalQty=cte2.Topqty
If you can't use the over clause, this would work (obviously, a lot more work compared to an over clause):
SELECT custOrderAll.CustId
, custOrderAll.CustName
, MaxOrder.ProductId
, MAX(custOrderAll.cntAll) TotalQty
FROM (
SELECT c.CustId
, c.CustName
, COUNT(O.ProductId) cntAll
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
) custOrderAll
LEFT JOIN (
SELECT custOrderMAX.CustId
, custOrderMAX.CustName
, custOrderMAX.ProductId
FROM (
SELECT c.CustId
, c.CustName
, O.ProductId
, COUNT(O.ProductId) cntMax
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
, O.ProductId
) custOrderMAX
INNER JOIN (
SELECT mxCnt.CustId
, mxCnt.CustName
, MAX(mxCnt.cntMax) mxCnt
FROM (
SELECT c.CustId
, c.CustName
, O.ProductId
, COUNT(O.ProductId) cntMax
FROM #Cust AS C
LEFT JOIN #Orders AS O
ON O.CustId = C.CustId
GROUP BY c.CustId
, c.CustName
, O.ProductId
) mxCnt
GROUP BY mxCnt.CustId
, mxCnt.CustName
) custOrderMAXCnt
ON custOrderMAXCnt.CustId = custOrderMAX.CustId
AND custOrderMAXCnt.mxCnt = custOrderMAX.cntMax
) MaxOrder
ON MaxOrder.CustId = custOrderAll.CustId
AND MaxOrder.CustName = custOrderAll.CustName
GROUP BY custOrderAll.CustId
, custOrderAll.CustName
, MaxOrder.ProductId
Result:
+--------+----------+-----------+----------+
| CustId | CustName | ProductId | TotalQty |
+--------+----------+-----------+----------+
| 1 | Paul | 1 | 3 |
| 2 | F | 1 | 1 |
| 3 | Francis | NULL | 0 |
+--------+----------+-----------+----------+
For example, i create a table about people contribue to 2 campaigns
+-------------------------------------+
| ID Name Campaign Amount (USD) |
+-------------------------------------+
| 1 A 1 10 |
| 2 B 1 5 |
| 3 C 2 7 |
| 4 D 2 9 |
+-------------------------------------+
Task: For each campaign, find the person (Name, ID) who contribute the most to
Expected result is
+-----------------------------------------+
| Campaign Name ID |
+-----------------------------------------+
| 1 A 1 |
| 2 D 4 |
+-----------------------------------------+
I used "group by Campaign" but the result have 2 columns "Campagin" and "max value" when I need "Name" and "ID"
Thanks for your help.
Edited: I fix some values, really sorry
You can use analytic functions for this:
select name, id, amount
from (select t.*, max(amount) over (partition by campaign) as max_amount
from t
) t
where amount = max_amount;
You can also do it by giving a rank/row_number partiton by campaign and order by descending order of amount.
Query
;with cte as(
select [num] = dense_rank() over(
partition by [Campaign]
order by [Amount] desc
), *
from [your_table_name]
)
select [Campaign], [Name], [ID]
from cte
where [num] = 1;
Try the next query:-
SELECT Campaign , Name , ID
FROM (
SELECT Campaign , Name , ID , MAX (Amount)
FROM MyTable
GROUP BY Campaign , Name , ID
) temp;
Simply use Where Clause with the max of amount group by Campaign:-
As following generic code:-
select a, b , c
from tablename
where d in
(
select max(d)
from tablename
group by a
)
Demo:-
Create table #MyTable (ID int , Name char(1), Campaign int , Amount int)
go
insert into #MyTable values (1,'A',1,10)
insert into #MyTable values (2,'B',1,5)
insert into #MyTable values (3,'C',2,7)
insert into #MyTable values (4,'D',2,9)
go
select Campaign, Name , ID
from #MyTable
where Amount in
(
select max(Amount)
from #MyTable
group by Campaign
)
drop table #MyTable
Result:-
Please find the below code for the same
SELECT *
FROM #MyTable T
OUTER APPLY (
SELECT COUNT(1) record
FROM #MyTable T1
where t.Campaign = t1.Campaign
and t.amount < t1.amount
)E
where E.record = 0