I have two tables -
Table Records with columns ID, Code,ProviderId
Table Codes with columns Code, Number, ProviderId
Sample Data:
Table Records
ID Code ProviderId
1 ABC 1
2 DEF 2
3 XYZ 1
4 PQR 2
Table Codes
Code Number ProviderId
ABC 1111 1
Default 9999 1
XYZ 2222 2
Default 4444 2
All the rows in Records table will have a code. Codes table will have a set of codes defined with other information. It is not necessary that all the codes in Records table will have an entry in Codes table. For Code in Records table if corresponding value exists then select the same else need to select Default code based on the ProviderID column.
My expected result is:
ID Code Number
-------------------------
1 ABC 1111
2 DEF 9999 -> Picked up the default for ProviderId 1
3 XYZ 2222
4 PQR 4444 -. Picked up the default for ProviderId 2
I was able to achieve this using left outer join to add the records into a table variable and then again performing an inner join.
I was wondering if I can achieve this in a single select.
You can do this with left joins:
select r.id, r.code, coalesce(c.number, cd.number) as number:
from records r left join
codes c
on r.code = c.code left join
codes cd
on r.providerid = c.providerid and c.code = 'Default';
That is, lookup both values. Choose the one based on the code if there is a match; otherwise, use the default.
I think, your 'DEF' or 'PQR' entry is wrong, eigher one should have "Providerid" is 1. I prepare a sample from your data.
Use some code of #Gordon. but logic is different.
declare #records table(id int, code varchar(50), providerid int)
declare #code table(code varchar(50), number int, providerid int)
insert into #records values (1,'ABC',1), (2,'DEF',2),(3,'XYZ',1),(4,'PQR',2)
insert into #code values ('ABC',1111,1 ), ('DEFAULT',9999, 1) , ('XYZ',2222,2) , ('Default', 4444, 2)
--your data in wrongly entered , that why the 'DEF' show the wrong thing below.
select
id, r.code ,
case
when ISNULL(c.code , '') = ''
then c1.number
else c.number
end
from
#records r
left outer join #code c on r.code = c.code
left outer join #code c1 on r.providerid = c1.providerid and LOWER( c1.code) = 'default'
--if the default entry is multiple times, then good to use sub-query with top
select
id, r.code ,
case
when ISNULL(c.code , '') = ''
then ( select top 1 number from #code c1 where r.providerid = c1.providerid and LOWER( c1.code) = 'default' )
else c.number
end
from
#records r
left outer join #code c on r.code = c.code
Try this.. As mentioned by others 'PQR' entry is wrong in your OUTPUT. It should not be '9999'. It should be '4444'
SELECT a.ID,b.Code,b.Number
FROM Records a
JOIN code b
ON a.Code = b.Code
UNION ALL
SELECT a.ID,a.Code,b.Number
FROM Records a
JOIN code b
ON a.ProviderId = b.ProviderId
WHERE NOT EXISTS(SELECT 1
FROM Code aa
WHERE aa.Code = a.Code)
AND b.Code = 'default'
SELECT A.ID,A.Code,B.Number,A.ProviderId
FROM #Source A
JOIN #Code B ON A.Code = B.Code
UNION
(SELECT A.ID,A.Code,B.Number,A.ProviderId
FROM #Source A
JOIN #Code B ON A.Code != B.Code
AND B.Code = 'Default'
WHERE A.Code NOT IN (SELECT A.Code
FROM #Source A
JOIN #Code B ON A.Code = B.Code)
AND A.ProviderId = B.ProviderId )
Try using this
Related
I am trying to get the SUM from two different but related tables that have a one to many relationship, but when I add a where condition to the second table the first does not properly sum up the total. Can this be done in a single query? I should also note that it is critical that both consider the same set of LocationId's as they come from an outside filter. I also need the Activityname condition to happen after the join if at all possible. If that isn't possible then that is fine.
IF OBJECT_ID('tempdb..#tmpVisits') is not null
begin
drop TABLE #tmpVisits
end
IF OBJECT_ID('tempdb..#tmpVisitsByActivity') is not null
begin
drop TABLE #tmpVisitsByActivity
end
CREATE TABLE #tmpVisits
(
AccountId int,
LocationId int,
Dt DATE,
TotalVisits int
)
CREATE TABLE #tmpVisitsByActivity
(
AccountId int,
LocationId int,
EventDate DATE,
TotalCompleted INT,
ActivityName varchar(20)
)
insert INTO #tmpVisits
SELECT 1,10,'2018-09-12',12
union ALL
SELECT 1,11,'2018-09-12',20
union ALL
SELECT 1,22,'2018-09-12',10
insert INTO #tmpVisitsByActivity
SELECT 1,10,'2018-09-12',55,'ActivityA'
union ALL
SELECT 1,10,'2018-09-12',1,'ActivityA'
union ALL
SELECT 1,10,'2018-09-12',2,'ActivityB'
union ALL
SELECT 1,22,'2018-09-12',3,'ActivityC'
SELECT SUM(v.TotalVisits) --expecting 42 actual 10
, SUM(a.TotalCompleted) --expecting 3 actual 3
FROM #tmpVisits v
left JOIN #tmpVisitsByActivity a
ON v.AccountId = a.AccountId
AND v.dt = a.EventDate
AND v.LocationId = a.locationid
WHERE v.dt='2018-09-12' AND v.AccountId=1
AND a.ActivityName='ActivityC'
You can move the where condition clauses in the join condition like below, if you want to use a single query.
SELECT SUM(v.TotalVisits) --expecting 42 actual 10
, SUM(a.TotalCompleted) --expecting 3 actual 3
FROM #tmpVisits v
left JOIN #tmpVisitsByActivity a
ON v.AccountId = a.AccountId
AND v.dt = a.EventDate
AND v.LocationId = a.locationid
AND v.dt='2018-09-12' AND v.AccountId=1
AND a.ActivityName='ActivityC'
The last criteria makes it so that some in #tmpVisits are excluded when there's no match.
But that's easy to get around.
Move the criteria for a.ActivityName to the ON clause, and remove it from the WHERE clause.
...
LEFT JOIN #tmpVisitsByActivity a
ON a.AccountId = v.AccountId AND
a.EventDate = v.dt AND
a.LocationId = v.locationId AND
a.ActivityName = 'ActivityA'
WHERE v.dt = '2018-09-12'
AND v.AccountId = 1
But it would be better to put the second table in a sub-query. Otherwise the first SUM could be wrong.
Example snippet:
DECLARE #Visits TABLE
(
AccountId INT,
LocationId INT,
Dt DATE,
TotalVisits INT
);
DECLARE #VisitsByActivity TABLE
(
AccountId INT,
LocationId INT,
EventDate DATE,
TotalCompleted INT,
ActivityName VARCHAR(20)
);
INSERT INTO #Visits (AccountId, LocationId, Dt, TotalVisits) VALUES
(1,10,'2018-09-12',12),
(1,11,'2018-09-12',20),
(1,22,'2018-09-12',10);
INSERT INTO #VisitsByActivity (AccountId, LocationId, EventDate, TotalCompleted, ActivityName) VALUES
(1,10,'2018-09-12',55,'ActivityA'),
(1,10,'2018-09-12',1,'ActivityA'),
(1,10,'2018-09-12',2,'ActivityB'),
(1,22,'2018-09-12',1,'ActivityC'),
(1,22,'2018-09-12',2,'ActivityC');
SELECT
SUM(v.TotalVisits) AS TotalVisits,
SUM(ac.TotalCompleted) AS TotalCompleted
FROM #Visits v
LEFT JOIN
(
SELECT AccountId, EventDate, locationid,
SUM(TotalCompleted) AS TotalCompleted
FROM #VisitsByActivity
WHERE ActivityName = 'ActivityC'
GROUP BY AccountId, EventDate, locationid
) AS ac
ON (ac.AccountId = v.AccountId AND ac.EventDate = v.dt AND ac.LocationId = v.locationid)
WHERE v.dt = '2018-09-12'
AND v.AccountId=1
I am trying to get the Sum of rows while applying a left join with more than 1 table. It seems it is creating a matrix of result which results in wrong sum function.
Example:
First Table: Customer
Second Table: TotalAssets
Third Table: TotalLiability
Table Structure:
Customer
CustID(int) CustomerName(varchar)
1 Abc
2 Def
3 Ghi
TotalAssets
CustID Amount
1 2000
1 1000
2 600
TotalLiability
CustID Amount
1 1000
1 1000
2 800
Output Expected
CustID TotalAssets TotalLiability
1 3000 2000
2 600 800
Current Query
Select c.CustID , Sum(a.Amount) , Sum(l.Amount) From Customer c
left join TotalAssests a on a.CustID = c.CustID
left join TotalLiability l on l.CustID = c.CustID
Group by c.CustID
The problem with this current query is the sum is not correct as i think the first left join create a first set with multiple records and then second one is applied.
Any help is appreciated
UPDATE:
I find some luck by following method but it seems a bad/hacky option as in my case i have over 7-8 elements in group by and adding more left clauses results in query difficult to manage.
New Query which is resulting correct result but looks very bad to maintains
Select Set1.CustID , Set1.TotalAssets, Sum(l.Amount) from (Select c.CustID , Sum(a.Amount) as TotalAssets From Customer c
left join TotalAssests a on a.CustID = c.CustID
Group by c.CustID)Set1
left join TotalLiability l on l.CustID = Set1.CustID.
Group by Set1.CustID , Set1.TotalAssets
I think this gets you what you want with minimum complexity:
select c.CustId, isnull(a.Amount, 0) as TotalAssets, isnull(l.Amount, 0) as TotalLiability
from Customers c
left join (
select CustId, sum(Amount) as Amount from TotalAssets group by CustId
) a on a.CustId = c.CustId
left join (
select CustId, sum(Amount) as Amount from TotalLiability group by CustId
) l on l.CustId = c.CustId
You need to group/sum the two tables separately, since the data in them is independent. Left-joining both to the customers table ensures that customers with no entries in either/both tables are still reported.
This should work:
Select c.CustID
, (select sum(a.amount) from TotalAssests a where a.CustId = c.CustID) as SumAsset
, (select Sum(l.Amount) TotalLiability l where l.CustID = c.CustID) as SumLiability
From Customer c
Hope the below works with less maintenance,
DECLARE #Customer TABLE (CustID int, CustomerName varchar(50)) DECLARE #TotalAssets TABLE (CustID int, Amount INT) DECLARE #TotalLiability TABLE (CustID int, Amount INT)
INSERT INTO #Customer
SELECT 1,
'ABC'
UNION
SELECT 2,
'DEF'
UNION
SELECT 3,
'GHI'
--Select * From #Customer
INSERT INTO #TotalAssets
SELECT 1,
2000
UNION
SELECT 1,
1000
UNION
SELECT 2,
600
--Select * From #TotalAssets
INSERT INTO #TotalLiability
SELECT 1,
1000
UNION
SELECT 1,
1000
UNION
SELECT 2,
800
--Select * From #TotalLiability
SELECT *
FROM #Customer
SELECT C.CustID,
C.CustomerName,
Sum(A.Amount) TotalAssets,
Sum(L.Amount) TotalLiability
FROM #Customer C
JOIN #TotalAssets A ON C.CustID = A.CustID
JOIN #TotalLiability L ON C.CustId = L.CustID
GROUP BY C.CustID,
C.CustomerName
I have 3 columns to base my JOIN on -> ID, Account, Cust. There can be multiple rows containing the same ID value.
I want to prioritise my JOIN on 1) ID, 2) Account, 3) Cust.
So in the example below, the UserCode that should be populated in #UserData should be 'u11z' as all columns contain a value.
How do I do this? Below my code to date...
UPDATE #UserData
SET UserCode = ur.UserCode
FROM #UserData uA
INNER JOIN UserReference ur
ON uA.ID = ur.ID
AND ((ua.Account = ur.Account) OR (ur.Account = ur.Account))
AND ((ua.Cust = ur.Cust) OR (ur.Cust = ur.Cust))
UserReference TABLE:
Cust Account ID UserCode
234 NULL 9A2346 u12x
234 Test 9A2346 u11z
NULL NULL 9A2346 u30s
#UserData TABLE:
Cust Account ID UserCode
234 Test 9A2346 NULL
Thanks!
You can try the following. I joined tables, counted the number of matches, and ranked them. Then select rank 1.
; with userCte (userCodeA, userCodeB, rank)
as
(
select a.usercode, b.usercode,
rank() over (partition by a.id order by case when a.cust = b.cust then 1 else 0 end +
case when a.account = b.account then 1 else 0 end +
case when a.id = b.id then 1 else 0 end desc) as rank
from userdata a
join userreference b
on a.id = b.id or a.account = b.account or a.id = b.id
)
select * from userCte
--update userCte
--set userCodeA = userCodeB
where rank = 1
Is this what you want? It is difficult to understand what you are asking for.
USE tempdb;
CREATE TABLE UserReference
(
ID VARCHAR(255) NULL
, Account VARCHAR(255) NULL
, Cust INT NULL
, UserCode VARCHAR(255)
);
INSERT INTO UserReference VALUES ('9A2346', NULL, 234, 'A');
INSERT INTO UserReference VALUES ('9A2346', 'TEST', 234, 'B');
INSERT INTO UserReference VALUES ('9A2346', NULL, NULL, 'C');
DECLARE #UserData TABLE
(
ID VARCHAR(255) NULL
, Account VARCHAR(255) NULL
, Cust INT NULL
, UserCode VARCHAR(255)
);
INSERT INTO #UserData
SELECT UR.ID, UR.Account, UR.Cust, NULL
FROM dbo.UserReference UR;
UPDATE #UserData
SET UserCode = ur.UserCode
FROM #UserData uA
INNER JOIN UserReference ur
ON uA.ID = ur.ID
AND ua.Account = ur.Account
AND ua.Cust = ur.Cust;
SELECT *
FROM #UserData;
Results of the last SELECT :
If I understood your question correctly...
And if any col in a row having a null value will drop the priority then you can use a query something like below to check the count of null values in a row, this might not be a complete answer, but a possible approach...
SELECT count(*)
FROM TableName
WHERE Col1 IS NULL and Col2 is null
Considering following table:
SELECT [ItemID]
,[ParentID]
,[PolicyID]
,[PolicyRoot]
FROM [AdventureWorks2008R2].[dbo].[Example]
ItemID ParentID PolicyID PolicyRoot
----------- ----------- ---------- ----------
1 NULL default 1
2 1 b 1
3 1 c 0
4 NULL d 1
5 3 e 0
6 3 f 1
7 NULL g 0
I'm trying to select the PolicyID from each item where PolicyRoot = 1, in case PolicyRoot = 0 I need to use PolicyID from its ParentID. This is recursive...
Working with a function:
CREATE FUNCTION dbo.Policies(#ItemID INT) RETURNS VARCHAR(10)
AS
BEGIN
DECLARE #ParentID INT, #PolicyRoot BIT, #PolicyID VARCHAR(10)
SELECT #ParentID = ParentID
, #PolicyRoot = PolicyRoot
, #PolicyID = PolicyID
FROM [dbo].[Example]
WHERE ItemID = #ItemID
IF #PolicyRoot != 1
SELECT #PolicyID = dbo.Policies(#ParentID)
RETURN #PolicyID
END;
GO
SELECT ItemID
, dbo.Policies(ItemID) AS Policy
FROM [dbo].[Example];
ItemID Policy
----------- ----------
1 default
2 b
3 default
4 d
5 default
6 f
7 NULL
I'm trying to rewrite this function to a CTE, but I don't have any CTE knowledge yet. I've read into multiple CTE's but I don't have a single clue how to manage a conditional CTE. This is as far as I've gotten, I'm not familiar (enough) with the UNION ALL.
WITH Policies (ItemID, PolicyID) AS (
SELECT ItemID
, PolicyID
FROM dbo.Example
UNION ALL
...
)
SELECT ItemID
, PolicyID
FROM Policies;
Can someone explain me in plain steps how such a CTE works and push me in the right direction?
A recursive CTE works by joining to itself, using a UNION ALL to collate the results.
You start with yourtable to populate the initial dataset of the recursive query
select * from yourtable
and you add to that with the UNION ALL, further results
select c.ItemID, t2.ParentID, t2.PolicyID, t2.PolicyRoot
from yourtable t2
inner join c on c.ParentID = t2.ItemID
where c.PolicyRoot=0
and the recursion occurs in this - where the results of this query are fed through this query again and again, up to the MAXRECURSION limit, or when no more results are added.
;with c as
(
select * from yourtable
union all
select c.ItemID, t2.ParentID, t2.PolicyID, t2.PolicyRoot
from yourtable t2
inner join c on c.ParentID = t2.ItemID
where c.PolicyRoot=0
)
select t.ItemID, c.PolicyID
from yourtable t
left join c on t.ItemID = c.ItemID
and c.PolicyRoot=1
I get a list of values from the select query. From the list, I am checking whether the items in the list is available in the table. If the values exist, I need to update the values else Insert the list into the Table.
With the list, I can insert the list of values in the table.
How to check and update the list in sql.
My Query :
WITH pq AS
(
SELECT A.[ProductId] ,A.[Quantity],A.[OrderId],D.[ProductName],E.[SpecialPrice],E.[SpecialPrice]*A.[Quantity] AS SPrice FROM [Table1] A
LEFT JOIN [Table2] B ON A.[OrderId] = B.[OrderId] INNER JOIN [Table3] D
ON A.[ProductId] = D.[ProductId] INNER JOIN [Table4] E
ON A.[ProductId] = E.[ProductId] WHERE B.[CustomerId] = 1
AND A.[OrderId] = 77
)
IF (EXISTS(SELECT [ProductId] FROM [Table5] WHERE [ProductId] = A.[ProductId]))
BEGIN
UPDATE [Table5]
SET [Quantity] = A.[Quantity]
WHERE B.[CustomerId] = 1 AND [ProductId] = A.[ProductId]
END
ELSE
BEGIN
INSERT INTO [Table5]
([ProductId],[ProductName],[Quantity],[Price],[TotalAmount])
SELECT
[ProductId],[ProductName],[Quantity],[SpecialPrice],SPrice
FROM pq;
END
Any suggestions will be greatly helpful.
EDIT : SELECT QUERY RESULT
ProductId Quantity
65 2
64 1
Assuming you're on SQL Server 2008 or above, the MERGE statement will solve your problem:
MERGE Table5 TRG
USING (
SELECT
A.ProductId,
A.Quantity,
A.OrderId,
D.ProductName,
E.SpecialPrice,
(E.SpecialPrice * A.Quantity) SPrice
FROM Table1 A
LEFT JOIN Table2 B ON A.OrderId = B.OrderId
INNER JOIN Table3 D ON A.ProductId = D.ProductId
INNER JOIN Table4 E ON A.ProductId = E.ProductId
WHERE
B.CustomerId = 1
AND A.OrderId = 77
) SRC
ON TRG.ProductID = SRC.ProductID
WHEN MATCHED THEN
UPDATE SET TRG.Quantity = SRC.Quantity
WHEN NOT MATCHED BY TARGET THEN
INSERT (
ProductId
, ProductName
, Quantity
, Price
, TotalAmount
)
VALUES (
SRC.ProductId
, SRC.ProductName
, SRC.Quantity
, SRC.SpecialPrice
, SRC.SPrice)
;
You can move the SELECT query out to a CTE for legibility like you did in your example.