Can we reduce multiple joins to same table - sql

I have following stored procedure:
ALTER PROCEDURE GetNotes
#ResidentId INT,
#Type INT = 0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #myTab TABLE
(
Residentid int NOT NULL,
Description nvarchar(max) NULL,
CreatedOn DATETIME2(7) NULL,
CreatedBy NVARCHAR(MAX) NULL,
NoteTypeId int NULL
)
INSERT INTO #myTab (Residentid, Description, CreatedOn, CreatedBy, NoteTypeId)
SELECT
R.Id,
v.[Description],
v.[Date],
v.[CreatedBy],
v.Col
FROM
Resident R
LEFT JOIN
Invoice AS I ON I.ResidentId = R.Id
LEFT JOIN
ResidentCourse AS RC ON RC.ResidentId = R.Id
--- LEFT JOIN ON USERS FOR Getting CreatedBy Column ---
LEFT JOIN
USERS AS Ui ON I.CreatedBy = Ui.Id
LEFT JOIN
USERS AS Urc ON rc.CreatedBy = Urc.Id
LEFT JOIN
Resident AS UR ON R.CreatedBy = UR.Id
CROSS APPLY
(VALUES (1, R.CreatedDate, R.Notes, UR.FirstName + ' '+ UR.LastName),
(3, I.Date, I.Description, UI.FirstName + ' '+ UI.LastName),
(4, RC.Date, RC.Notes, URC.FirstName + ' '+ URC.LastName),
) v (Col, [Date], [Description], [CreatedBy])
WHERE
R.Id = #ResidentId
SELECT DISTINCT
Residentid,
Description,
CreatedBy,
CreatedOn,
NoteTypeId
FROM
#myTab
WHERE
Description IS NOT NULL
AND CreatedOn IS NOT NULL
AND (#Type = 0 OR NoteTypeId = #Type)
ORDER BY
CreatedOn DESC
END
Result
Id Description CreatedBy CreatedOn NoteTypeId
-------- ---------- ------------------------------------------
1 ASD ASD John Doe 2020-01-02 1
4 ASD DSS Terry kal 2020-01-02 3
I have a Note column in every table and a CreatedBy column also so in above stored procedure I am performing a LEFT JOIN on the Users table for getting the name of that user. Considering I have 8 different tables with Note / Description columns, right now I have 8 LEFT JOIN against the Users table to get data.
Is there an optimal solution?
Any help would be appreciated.

Instead of joining I, R and RC to Users, you could put each CreatedBy in #MyTab and join in the second query.
DECLARE #myTab TABLE
(
Residentid int NOT NULL,
Description nvarchar(max) NULL,
CreatedOn DATETIME2(7) NULL,
CreatedBy int NULL,
NoteTypeId int NULL
)
INSERT INTO #myTab (Residentid, Description, CreatedOn, CreatedBy, NoteTypeId)
SELECT
R.Id,
v.[Description],
v.[Date],
v.[CreatedBy],
v.Col
FROM
Resident R
LEFT JOIN
Invoice AS I ON I.ResidentId = R.Id
LEFT JOIN
ResidentCourse AS RC ON RC.ResidentId = R.Id
CROSS APPLY
(VALUES (1, R.CreatedDate, R.Notes, UR.CreatedBy ),
(3, I.Date, I.Description, UI.CreatedBy ),
(4, RC.Date, RC.Notes, URC.CreatedBy ),
) v (Col, [Date], [Description], [CreatedBy])
WHERE
R.Id = #ResidentId
SELECT DISTINCT
T.Residentid,
T.Description,
U.FirstName + ' ' + U.LastName,
T.CreatedOn,
T.NoteTypeId
FROM
#myTab AS T
LEFT JOIN
USERS AS U ON T.CreatedBy = U.Id
WHERE
Description IS NOT NULL
AND CreatedOn IS NOT NULL
AND (#Type = 0 OR NoteTypeId = #Type)
ORDER BY
CreatedOn DESC

Give this a whirl:
WITH some_cte AS (
SELECT 'Resident' AS DataSource
, Id AS ResidentId
, Notes AS Description
, CreatedDate AS CreatedOn
, CreatedBy AS CreatedByUserId
FROM Resident
WHERE Notes IS NOT NULL
AND CreatedDate IS NOT NULL
AND Id = #ResidentId
AND #Type IN (0, 1)
UNION ALL
SELECT 'Invoice' AS DataSource
, ResidentId
, Description
, [Date] AS CreatedOn
, CreatedBy AS CreatedByUserId
FROM Description IS NOT NULL
AND [Date] IS NOT NULL
AND ResidentId = #ResidentId
AND #Type IN (0, 3)
UNION ALL
SELECT 'ResidentCourse' AS DataSource
, ResidentId
, Notes AS Description
, [Date] AS CreatedOn
, CreatedBy AS CreatedByUserId
FROM ResidentCourse
WHERE Notes IS NOT NULL
AND [Date] IS NOT NULL
AND ResidentId = #ResidentId
AND #Type IN (0, 4)
)
SELECT some_cte.DataSource
, some_cte.ResidentId
, some_cte.CreatedOn
, some_cte.Description
, some_cte.CreatedByUserId
, Users.FirstName + ' ' + Users.LastName AS CreatedBy
FROM some_cte
INNER
JOIN Users
ON Users.Id = some_cte.CreatedByUserId
ORDER
BY some_cte.CreatedOn DESC
;
The join to Users is now only performed once, but might be sub-optimal. Should it be a problem you can inline the join to Users on each individual query.

Related

Get hierarchical data is SQL SERVER with fallback logic

Consider the below schema
dbo.Cultures (Id, CultureCode, ParentId)
Culture table stores the data in the parent-child relationship.
Suppose we have below demo data
5 es-ES 3
Now I have another table which stores the multilingual data for the different cultures.
Schema for the table is as following
dbo.LangData(KeyName, CultureId, Value)
here cultureId is the foreign key of dbo.Cultures table.
Suppose this table has following data
Now I require to fetch the data for all the cultures which are in the Culture table and the corresponding value column in the LangData table.
The culture Ids which are not in the LangData table, for those the Value column will the value of the corresponding parent culture Id columns value. I.e. Data will be retrieved using fallback logic
E.g. For the above values the Result set will be following.
5 es-ES Colour_IN
Here for de-DE data is missing in LangData so it's value will be the data in it's parent culture i.e. en-IN, if in case data also not found in en-IN then it will pick the data of it's parent en-US.
Tried Soloution
First I fetch the culture hierarchy using CTE
CREATE FUNCTION [dbo].[ufnGetCultureHierarchyAll] ()
RETURNS #hierarchyResult TABLE(RowNo INT, CultureId INT, ParentCultureId INT)
AS
BEGIN
WITH CultureHierarchy_CTE(RowNo, CultureId, ParentCultureId)
AS (
SELECT 1,
Id,
ParentId
FROM [dbo].Cultures
UNION ALL
SELECT RowNo + 1,
ou.Id,
ou.ParentId
FROM [dbo].Cultures ou
JOIN CultureHierarchy_CTE cte
ON ou.Id = cte.ParentCultureId
)
-- inserting desired records into table and returning
INSERT INTO #hierarchyResult (RowNo,CultureId,ParentCultureId )
SELECT RowNo, CultureId , ParentCultureId FROM CultureHierarchy_CTE
RETURN;
END
This will return the hierarchy of the all the cultures
Now I attempted to apply join of the result set with the LangData table,
DECLARE #cultureHierarchy AS TABLE(
RowNumber INT,
CultureId INT,
ParentCultureId INT
)
--SELECT * FROM master.Cultures
----Get and store culture hierarchy
INSERT INTO #cultureHierarchy
SELECT RowNo, CultureId, ParentCultureId
FROM ufnGetCultureHierarchyAll()
SELECT c.Code AS [CultureCode],
c.CultureId AS [CultureId],
rv.Value
FROM dbo.LangData rv WITH (NOLOCK)
JOIN #cultureHierarchy c ON rv.CultureId = c.CultureId
END
but it is not working.
Is someone have any Idea regarding same.
Solution using Itzik Ben-Gan's hierarchy model. If you can extend the dbo.Cultures table with Hierarchy, Lvl and Root columns and index on Hierarchy, query will be faster. It has to be rewrited in that case though.
drop table if exists dbo.Cultures;
create table dbo.Cultures (
ID int
, Code varchar(50)
, ParentID int
);
insert into dbo.Cultures (ID, Code, ParentID)
values (1, 'en-US', null), (2, 'en-IN', 1), (3, 'de-DE', 2), (4, 'hi-HI', 2)
drop table if exists dbo.LangData;
create table dbo.LangData (
KeyName varchar(100)
, CultureID int
, Value varchar(100)
);
insert into dbo.LangData (KeyName, CultureID, Value)
values ('lblColourName', 1, 'Color'), ('lblColourName', 2, 'Colour-IN');
with cteCultures as (
select
c.ID, c.Code, c.ParentID, 0 as Lvl
, convert(varchar(max), '.' + CONVERT(varchar(50), c.ID) + '.') as Hierarchy
, c.ID as Root
from dbo.Cultures c
where c.ParentID is null
union all
select
c.ID, c.Code, c.ParentID, cc.Lvl + 1 as Lvl
, cc.Hierarchy + convert(varchar(50), c.ID) + '.' as Hierarchy
, cc.Root as Root
from dbo.Cultures c
inner join cteCultures cc on c.ParentID = cc.ID
)
select
ccr.ID
, ccr.Code
, coalesce(ld.Value, ld2.Value) as Value
from cteCultures ccr
left join dbo.LangData ld on ccr.ID = ld.CultureID
outer apply (
select
top (1) tcc.ID
from cteCultures tcc
inner join dbo.LangData tld on tcc.ID = tld.CultureID
where ld.KeyName is null
and ccr.Hierarchy like tcc.Hierarchy + '%'
and ccr.Hierarchy <> tcc.Hierarchy
order by tcc.Lvl desc
) tt
left join dbo.LangData ld2 on tt.ID = ld2.CultureID
If I understand your question:
We just build your hierarchy (SEQ and Lvl are optional) and then perform TWO left joins in concert with a Coalesce().
Example
Declare #Cultures table (id int,ParentId int,Code varchar(50))
Insert into #Cultures values
( 1, NULL,'en-US')
,( 2, 1 ,'en-IN')
,( 3, 2 ,'de-DE')
,( 4, 2 ,'hi-HI')
Declare #LangData table (keyName varchar(50),CultureId int,Value varchar(50))
Insert Into #LangData values
('lblColourName',1,'Color')
,('lblColourName',2,'Color_IN')
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Code) as varchar(500))
,ID
,ParentId
,Lvl=1
,Code
From #Cultures
Where ParentId is null
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Code)) as varchar(500))
,r.ID
,r.ParentId
,p.Lvl+1
,r.Code
From #Cultures r
Join cteP p on r.ParentId = p.ID)
Select CultureId = A.ID
,A.Code
,Value = Coalesce(C.Value,B.Value)
From cteP A
Left Join #LangData B on (A.ParentId=B.CultureId)
Left Join #LangData C on (A.Id=C.CultureId)
Order By Seq
Returns
CultureId Code Value
1 en-US Color
2 en-IN Color_IN
3 de-DE Color_IN
4 hi-HI Color_IN

Query different type data in to a single row with the master record

I just created the following sample data to demonstrate what I am after.
I need to query the above table to get my information as 1 single row
i.e. like the following
I was planning to create two temp tables and insert the two different types of addresses seperately. Then, inner join them with the main company table. I am not sure, this is a good solution. I appreaciate if anyone share their thoughts or code to my problem.
Try this..
Select companyId,CompanyName,homesddress1
,homeaddress2,HomePostCode,OfficeAddress1,OfficeAddress2,OfficePostCode
From tblCompany a
Outer apply ( select address1 homesddress1, address2 homeaddress2,postcode HomePostCode
From tblAddress t
Where AddressType='home' and t.companyid=a.companyid)
Outer apply (select address1 OfficeAddress1, address2 Officeaddress2,postcode OfficePostCode
From tblAddress t2
Where AddressType='Office ' and t2.companyid=a.companyid)
You can do it with a simple select using two outer joins. Note that you need the joins to be outer because for some companies you may only have one address.
DECLARE #company TABLE (
CompanyId int,
CompanyName varchar(50)
)
DECLARE #companyAddress TABLE (
Id int,
AddressType varchar(10),
Address1 varchar(50),
Address2 varchar(50),
Postcode varchar(10),
CompanyId int
)
INSERT INTO #company VALUES (1, 'Test Company')
INSERT INTO #companyAddress VALUES (1, 'Home', '25 Street', 'City 1', 'BA3 1PE', 1)
INSERT INTO #companyAddress VALUES (2, 'Office', '25 Street', 'City 2', 'NA1 4TW', 1)
SELECT c.CompanyId, c.CompanyName,
h.Address1 AS HomeAddress1, h.Address2 AS HomeAddress2, h.Postcode AS HomePostcode,
o.Address1 AS OfficeAddress1, o.Address2 AS OfficeAddress2, o.Postcode AS OfficePostcode
FROM #company c
LEFT JOIN
#companyAddress h ON h.CompanyId = c.CompanyId AND h.AddressType = 'Home'
LEFT JOIN
#companyAddress o ON o.CompanyId = c.CompanyId AND o.AddressType = 'Office'
Here is an Almost Dynamic version. You just have to specify the field list in the final pivot
Declare #YourTable table (ID int,AddressType varchar(25),Address1 varchar(50),Address2 varchar(50),CompanyID int)
Insert Into #YourTable values
(1,'Home' ,'25 Street','City 1',1),
(2,'Office','10 Avenue','City 2',1)
Declare #XML xml = (Select * from #YourTable for XML RAW) --<<< Initial Query
;with cteBase as (
Select ID = R.value('#CompanyID','int') --<<< Key ID
,AddressType = R.value('#AddressType','varchar(50)')
,Item = R.value('#AddressType','varchar(50)')+Attr.value('local-name(.)','varchar(100)')
,Value = Attr.value('.','varchar(max)')
From #XML.nodes('/row') as A(R)
Cross Apply A.r.nodes('./#*[local-name(.)!="CompanyID"]') as B(Attr) --<<< Key ID
),cteDist as (Select Distinct ID,Item from cteBase
),cteComp as (
Select A.*,B.Value
From cteDist A
Cross Apply (Select Value=Stuff((Select Distinct ',' + Value
From cteBase
Where ID=A.ID
and Item=A.Item
For XML Path ('')),1,1,'') ) B
)
Select *
From (Select * From cteComp) as s
Pivot (max(value)
For Item in (HomeID,HomeAddressType,HomeAddress1,HomeAddress2,OfficeID,OfficeAddressType,OfficeAddress1,OfficeAddress2)) as pvt
Returns
ID HomeID HomeAddressType HomeAddress1 HomeAddress2 OfficeID OfficeAddressType OfficeAddress1 OfficeAddress2
1 1 Home 25 Street City 1 2 Office 10 Avenue City 2

Displaying data from different tables and displaying them In details with summary

I am very beginner and I am just learning by myself, so please excuse if I can't even express what I want to say in Programmer way!
I am trying to develop simple business application that can do this:
Register Employee or Customer.
Can store expenses or money that employee can take in advance before salary payment (This can be done several times, or it may be
zero)
Can store payments (Salary given to the employee) - It can be also done several times
It can calculate all expenses, calculate all payments, then find their difference, hence will be paid the remaining of the balance!
CREATE TABLE Customer
( CustomerID INT IDENTITY (1000,1)
,FirstName VARCHAR(40)
,RegDate DATETIME DEFAULT GETDATE()
,Phone VARCHAR(10)
);
CREATE TABLE Expense
( ExpenseID INT IDENTITY (1000,1)
,CustomerID INT NOT NULL
,ExpDate DATETIME DEFAULT GETDATE()
,Amount MONEY
,Comment VARCHAR (100)
);
CREATE TABLE TblLoad
( LoadID INT IDENTITY (1000,1) --PRIMARY KEY
,CustomerID INT NOT NULL
,DepDate DATETIME DEFAULT GETDATE()
,Amount MONEY
,FromTo VARCHAR (100)
);
select Customer.CustomerID, Customer.FirstName, Expense.Amount As ExpenseAmount from Customer inner join
Expense on Customer.CustomerID = Expense.CustomerID
WHERE Customer.CustomerID = 1000
select Customer.CustomerID, Customer.FirstName, TblLoad.Amount As LoadAmount from Customer inner join
TblLoad on Customer.CustomerID = TblLoad.CustomerID
WHERE Customer.CustomerID = 1000
SELECT Customer.CustomerID, Customer.FirstName, e.SE as AmountExp , l.SL as AmountLoad , (l.SL - e.SE) as FinalPay
FROM Customer
INNER JOIN (SELECT CustomerID, SUM(Amount) AS SE FROM Expense GROUP BY CustomerID) E
ON Customer.CustomerID = e.CustomerID
INNER JOIN (SELECT CustomerID, SUM(Amount) AS SL FROM TblLoad GROUP BY CustomerID) L
ON Customer.CustomerID = l.CustomerID
WHERE Customer.CustomerID = 1000
Attached are pictures of what I expected, and what I was willing
Expected Result
Result From The Code
Firstly congrats on getting as far as you have. The bad news is that t-sql is not a reporting tool - you need to look elsewhere if you want pretty output (SSRS,Crystal reports, excel etc,etc) to get anywhere near to your required ouput in t-sql you need to get a bit creative for example:-
/*
drop table customer
drop table expense
drop table TblLoad
CREATE TABLE Customer
( CustomerID INT --IDENTITY (1000,1)
,FirstName VARCHAR(40)
,RegDate DATETIME --DEFAULT GETDATE()
,Phone VARCHAR(10)
);
INSERT INTO CUSTOMER
( CustomerID
,FirstName
,RegDate
,Phone )
VALUES
(1001,'Emp1',cast('2015/10/20 00:00:00.000' as datetime), null),
(1002,'Emp2',cast('2015/10/22 00:00:00.000' as datetime), null),
(1003,'Emp3',cast('2015/10/25 00:00:00.000' as datetime), null)
CREATE TABLE Expense
( ExpenseID INT --IDENTITY (1000,1)
,CustomerID INT NOT NULL
,ExpDate DATETIME DEFAULT GETDATE()
,Amount MONEY
,Comment VARCHAR (100)
);
truncate table expense
insert into expense
( ExpenseID
,CustomerID
,ExpDate
,Amount
--,Comment
)
Values
(1001,1001,cast('2015/10/21' as datetime),100),
(1002,1001,cast('2015/11/22' as datetime),20),
(1003,1001,cast('2015/12/25' as datetime),80),
(1004,1002,cast('2015/11/21' as datetime),100),
(1005,1002,cast('2015/12/25' as datetime),200),
(1006,1003,cast('2015/11/25' as datetime),300)
CREATE TABLE TblLoad
( LoadID INT --IDENTITY (1000,1) --PRIMARY KEY
,CustomerID INT NOT NULL
,DepDate DATETIME DEFAULT GETDATE()
,Amount MONEY
,FromTo VARCHAR (100)
);
truncate table tblload
insert into tblload
( loadID
,CustomerID
,depDate
,Amount
--,Comment
)
Values
(1001,1001,cast('2015/10/21' as datetime),1000),
(1002,1001,cast('2015/11/21' as datetime),200),
(1003,1001,cast('2015/12/24' as datetime),800),
(1004,1002,cast('2015/11/21' as datetime),2000),
(1005,1002,cast('2015/12/25' as datetime),500),
(1006,1003,cast('2015/11/24' as datetime),4000)
*/
select
--case
--when right(rtrim(cast(yyyymm as char(8))) ,2 ) = '13' then char(30)
--else cast(yyyymm as char(8))
--end
case
when right(rtrim(cast(yyyymm as char(8))) ,2 ) = '13' then char(30)
else cast(customerid as char(4))
end
,case
when right(rtrim(cast(yyyymm as char(8))) ,2 ) = '13' then 'Total'
else firstname
end
,sum(amountexp) amuntexp
,sum(amountload) amountload
,sum(amountload) - sum(amountexp) finalpay
from
(
select year(e.expdate) * 100 + month(e.expdate) yyyymm,
C.CustomerID, c.FirstName, sum(e.amount) as AmountExp , 0 as AmountLoad , 0 as FinalPay
FROM Customer c
INNER JOIN Expense E ON C.CustomerID = e.CustomerID
group by year(e.expdate) , month(e.expdate),
C.CustomerID, c.FirstName
union
select year(e.expdate) * 100 + 13 ,
C.CustomerID, c.FirstName, sum(e.amount) as AmountExp , 0 as AmountLoad , 0 as FinalPay
FROM Customer c
INNER JOIN Expense E ON C.CustomerID = e.CustomerID
group by year(e.expdate) * 100 + 13,
C.CustomerID, c.FirstName
union
select year(l.depdate) * 100 + month(l.depdate) yyyymm,
C.CustomerID, c.FirstName, 0 asAmountExp , sum(l.amount) as AmountLoad , 0 as FinalPay
FROM Customer c
INNER JOIN tblload l ON C.CustomerID = l.CustomerID
group by year(l.depdate) , month(l.depdate),
C.CustomerID, c.FirstName
union
select year(l.depdate) * 100 + 13 yyyymm,
C.CustomerID, c.FirstName, 0 asAmountExp , sum(l.amount) as AmountLoad , 0 as FinalPay
FROM Customer c
INNER JOIN tblload l ON C.CustomerID = l.CustomerID
group by year(l.depdate) * 100 + 13,
C.CustomerID, c.FirstName
) s
group by s.CustomerID,s.firstname,yyyymm
order by s.CustomerID,s.firstname,yyyymm

Consolidate data from three rows (with nulls) into one

I have three tables:
Profile
-ProfileID
-FirstName
-LastName
ProfilePhoneNumber
-ProfileID
-PhoneNumberID
PhoneNumber
-PhoneNumberID
-PhoneNumberTypeID
-Number
ProfilePhoneNumber is a simple bridge table between Profile and PhoneNumber.
I want to query for specific phone number types and return a single row. I want to be able to accept null values because not all people will have all types of phone numbers.
Here is my current query:
SELECT
p.FirstName
, p.LastName
, bpn.Number as BusinessPhoneNumber
, mpn.Number as MobilePhoneNumber
FROM Profile p
LEFT JOIN ProfilePhoneNumber ppn ON p.ProfileID = ppn.ProfileID
LEFT JOIN PhoneNumber bpn ON ppn.PhoneNumberID = bpn.PhoneNumberID AND bpn.PhoneNumberTypeID = '1'
LEFT JOIN PhoneNumber mpn ON ppn.PhoneNumberID = mpn.PhoneNumberID AND mpn.PhoneNumberTypeID = '2'
WHERE p.ProfileID = '123'
This always works, but returns three rows because Profile 123 has three phone numbers, and so the query returns a row for each phone number.
If I change it to an INNER JOIN on PhoneNumber, I can get only one row back, but only in circumstances where the Profile being queried has all of the PhoneNumberTypeID types that I am querying for.
How do I return one row that is null tolerant?
I will update the query later, if you provide some data sample and expected output.
So far, will this help?
SELECT
p.FirstName
, p.LastName
, Max(bpn.Number) as BusinessPhoneNumber
, Max(mpn.Number) as MobilePhoneNumber
FROM Profile p
LEFT JOIN ProfilePhoneNumber ppn on p.ProfileID = ppn.ProfileID
LEFT JOIN PhoneNumber bpn on ppn.PhoneNumberID
= bpn.PhoneNumberID AND bpn.PhoneNumberTypeID = '1'
LEFT JOIN PhoneNumber mpn on ppn.PhoneNumberID
= mpn.PhoneNumberID AND mpn.PhoneNumberTypeID = '2'
WHERE p.ProfileID = '123'
group by p.FirstName, p.LastName;
If each profile has one phone per type then you can:
Use INNER JOIN queries to match profile with each phone type
Use LEFT JOIN query to match the profiles with the above
DDL:
CREATE TABLE profile (ProfileID INT NOT NULL, FirstName VARCHAR(100), LastName VARCHAR(100), PRIMARY KEY (ProfileID));
INSERT INTO profile (ProfileID, FirstName, LastName) VALUES (1, 'User', '#1'), (2, 'User', '#2'), (3, 'User', '#3');
CREATE TABLE phonenumber (PhoneNumberID INT NOT NULL, PhoneNumberTypeID INT, Number VARCHAR(100), PRIMARY KEY (PhoneNumberID));
INSERT INTO phonenumber (PhoneNumberID, PhoneNumberTypeID, Number) VALUES (1, 1, '0800-U1BUS'), (2, 1, '0800-U2BUS'), (3, 2, '0800-U2MOB'), (4, 1, '0800-U3BUS'), (5, 2, '0800-U3MOB'), (6, 3, '0800-U3ETC');
CREATE TABLE profilephonenumber (ProfileID INT NOT NULL, PhoneNumberID INT NOT NULL, PRIMARY KEY (ProfileID,PhoneNumberID));
INSERT INTO profilephonenumber (ProfileID, PhoneNumberID) VALUES (1, 1), (2, 2), (2, 3), (3, 4), (3, 5), (3, 6);
Query:
SELECT Profile.FirstName, Profile.LastName, BusPhone.Number AS BusPhoneNumber, MobPhone.Number AS MobPhoneNumber
FROM profile
LEFT JOIN (
SELECT ProfileID, Number
FROM profilephonenumber
INNER JOIN phonenumber ON profilephonenumber.PhoneNumberID = phonenumber.PhoneNumberID
WHERE PhoneNumberTypeID = 1
) AS BusPhone ON Profile.ProfileID = BusPhone.ProfileID
LEFT JOIN (
SELECT ProfileID, Number
FROM profilephonenumber
INNER JOIN phonenumber ON profilephonenumber.PhoneNumberID = phonenumber.PhoneNumberID
WHERE PhoneNumberTypeID = 2
) AS MobPhone ON Profile.ProfileID = MobPhone.ProfileID
Output:
+-----------+----------+----------------+----------------+
| FirstName | LastName | BusPhoneNumber | MobPhoneNumber |
+-----------+----------+----------------+----------------+
| User | #1 | 0800-U1BUS | NULL |
| User | #2 | 0800-U2BUS | 0800-U2MOB |
| User | #3 | 0800-U3BUS | 0800-U3MOB |
+-----------+----------+----------------+----------------+
SQL Fiddle
-- Sample data.
declare #Profile as Table ( ProfileId Int Identity, FirstName VarChar(10), LastName VarChar(10) );
insert into #Profile ( FirstName, LastName ) values
( 'Alice', 'Aardvark' ), ( 'Bob', 'Bear' ), ( 'Cindy', 'Cat' );
declare #PhoneNumber as Table ( PhoneNumberId Int Identity, PhoneNumberTypeId Int, Number VarChar(10) );
insert into #PhoneNumber ( PhoneNumberTypeId, Number ) values
( 1, '1111111111' ), ( 2, '1112221111' ), ( 1, '1113331111' ), ( 2, '2222222222' );
declare #ProfilePhoneNumber as Table ( ProfileId Int, PhoneNumberId Int );
insert into #ProfilePhoneNumber ( ProfileId, PhoneNumberId ) values
( 1, 1 ), ( 1, 2 ),
( 2, 3 ),
( 3, 4 );
-- Dump the sample data.
select *
from #Profile as P left outer join
#ProfilePhoneNumber as PPN on PPN.ProfileId = P.ProfileId left outer join
#PhoneNumber as PN on PN.PhoneNumberId = PPN.PhoneNumberId;
-- Do the deed.
with ExtendedPhoneNumbers as (
select ProfileId, PhoneNumberTypeId, Number
from #ProfilePhoneNumber as PPN inner join
#PhoneNumber as PN on PN.PhoneNumberId = PPN.PhoneNumberId )
select P.FirstName, P.LastName,
EPNB.Number as BusinessPhoneNumber,
EPNM.Number as MobilePhoneNumber
from #Profile as P left outer join
ExtendedPhoneNumbers as EPNB on P.ProfileID = EPNB.ProfileID and EPNB.PhoneNumberTypeId = 1 left outer join
ExtendedPhoneNumbers as EPNM on P.ProfileID = EPNM.ProfileID and EPNM.PhoneNumberTypeId = 2
where P.ProfileId = 2; -- Comment out the WHERE clause to see all profiles.

Is it possible to simplify the SQL query used to produce the checksum based difference between a table and the given TVP?

I need to write many entities into the database. I want to optimize it by:
Issuing a preflight request to compute the difference between the current data in the table and the new data in the process memory.
Only update/delete/insert the relevant records.
All the data is checksumed, so I am only going to compare the checksums.
Here is my preflight request:
;WITH src AS (
SELECT cs.AdmClientSiteId, src.Id ClientId, Checksum, AuxId
FROM #src src
JOIN AdmClientSite cs ON cs.AdmClientMasterId = src.Id
WHERE cs.AdmSiteId = #AdmSiteId
), dst AS (
SELECT dst.AdmClientSiteId, cs.AdmClientMasterId ClientId, Checksum, LegalAuxId AuxId
FROM AdmCustomerInfoLegal dst
JOIN AdmClientSite cs ON cs.AdmClientSiteId = dst.AdmClientSiteId
WHERE cs.AdmSiteId = #AdmSiteId
)
SELECT ISNULL(src.AdmClientSiteId, dst.AdmClientSiteId) AdmClientSiteId, ISNULL(src.ClientId, dst.ClientId) ClientId, ISNULL(src.AuxId, dst.AuxId) AuxId,
CASE
WHEN src.Checksum IS NULL THEN 0 -- DBAction.DELETE
WHEN dst.Checksum IS NOT NULL THEN 1 -- DBAction.UPDATE
ELSE 2 -- DBAction.INSERT
END Action
FROM src
FULL JOIN dst ON src.AdmClientSiteId = dst.AdmClientSiteId AND src.AuxId = dst.AuxId
WHERE src.Checksum IS NULL OR dst.Checksum IS NULL OR src.Checksum <> dst.Checksum
ORDER BY Action, ClientId
In this code:
#src is a TVP
AdmCustomerInfoLegal is the table to be updated
The schema of #src is slightly different from that of AdmCustomerInfoLegal.
My question - can it be simplified/improved?
Yes , you can use merge statement which used in above condition.
I does not have time , please check this example and modify at your end.
create table temptable (id int, firstname varchar(50), lastname varchar(50), email varchar(50), homephone varchar(50))
insert into temptable values
(1,'aaa' , 'bbb', 'xxx#yyy.com', 1234444),
(2,'aaa' , 'bbb', null, null),
(3,'ccc' , 'ddd', 'abc#ddey.com', null),
(4,'ccc' , 'ddd', null, 34343322 )
select * from temptable
;with cte as
(
select firstname, lastname
,(select top 1 id from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and ( b.email is not null or b.homephone is not null)) tid
,(select top 1 email from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and b.email is not null ) email
,(select top 1 homephone from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and b.homephone is not null ) homephone
from temptable a
group by firstname , lastname
)
--select * from cte
merge temptable as a
using cte as b
on ( a.id = b.tid )
when matched
then
update set a.email = b.email , a.homephone = b.homephone
when not matched by source then
delete ;
select * from temptable
drop table temptable