Consolidate data from three rows (with nulls) into one - sql

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.

Related

Can we reduce multiple joins to same table

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.

TSQL group by and combine remaining columns in json array

I want to group a result set by a column and combine the remaining columns into a json array, but I'm not sure how to aggregate the results for this.
I want the following output:
A_ID | Translations
--------------------
1 | [{"Name": "english_1","LCID": "en-gb"},{"Name": "french_1","LCID": "fr-fr"}]
2 | [{"Name": "english_2","LCID": "en-gb"},{"Name": "french_2","LCID": "fr-fr"}]
But I cannot group the results by A_ID without an aggregator so I get the following
A_ID | Translations
--------------------
1 | [{"Name": "english_1","LCID": "en-gb"}]
1 | [{"Name": "french_1","LCID": "fr-fr"}]
2 | [{"Name": "english_2","LCID": "en-gb"}]
2 | [{"Name": "french_2","LCID": "fr-fr"}]
Here is an example:
DROP TABLE IF EXISTS #tabA;
DROP TABLE IF EXISTS #tabB;
DROP TABLE IF EXISTS #tabC;
go
CREATE TABLE #tabA
(
Id int not null
);
CREATE TABLE #tabTranslations
(
translationId int not null,
Name nvarchar(32) not null,
aId int not null, -- Foreign key.
languageId int not null --Foreign key
);
CREATE TABLE #tabLanguages
(
languageId int not null,
LCID nvarchar(32) not null
);
go
INSERT INTO #tabA (Id)
VALUES
(1),
(2);
INSERT INTO #tabTranslations (translationId, Name, aId, languageId)
VALUES
(1, 'english_1', 1, 1),
(2, 'french_1', 1, 2),
(3, 'english_2', 2, 1),
(4, 'french_2', 2, 2);
INSERT INTO #tabLanguages (languageId, LCID)
VALUES
(1, 'en-gb'),
(2, 'fr-fr');
go
select
_a.Id as A_ID,
(
select
_translation.Name,
_language.LCID
for json path
)
from #tabA as _a
inner join #tabTranslations as _translation ON _translation.aId = _a.Id
inner join #tabLanguages as _language ON _language.languageId = _translation.languageId
-- group by _a.Id ??
;
go
DROP TABLE IF EXISTS #tabA;
DROP TABLE IF EXISTS #tabTranslations;
DROP TABLE IF EXISTS #tabLanguages;
go
Alternative solution:
I know I can do this, but I obviously don't want to hard code the available LCIDs (maybe I could generate the sql query and exec it? but this feels too complex), also I would prefer an array
select
_a.Id as A_ID,
(
SELECT
MAX(CASE WHEN [LCID] = 'en-gb' THEN [Name] END) 'en-gb',
MAX(CASE WHEN [LCID] = 'fr-fr' THEN [Name] END) 'fr-fr'
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) as b
from #tabA as _a
inner join #tabTranslations as _translation ON _translation.aId = _a.Id
inner join #tabLanguages as _language ON _language.languageId = _translation.languageId
group by _a.Id;
result:
A_ID | Translations
--------------------
1 | { "en-Gb": "english_1", "fr-FR": "french_1"}
2 | { "en-Gb": "english_2", "fr-FR": "french_2"}
If I understand you correctly, next approach may help. Use additional CROSS APPLY operator and FOR JSON PATH to get your expected results:
Statement:
SELECT *
FROM #tabA AS t
CROSS APPLY (
SELECT _translation.Name AS Name, _language.LCID AS LCID
FROM #tabA _a
inner join #tabTranslations as _translation ON _translation.aId = _a.Id
inner join #tabLanguages as _language ON _language.languageId = _translation.languageId
WHERE _a.Id = t.Id
for json path
) _c(Translations)
Output:
Id Translations
1 [{"Name":"english_1","LCID":"en-gb"},{"Name":"french_1","LCID":"fr-fr"}]
2 [{"Name":"english_2","LCID":"en-gb"},{"Name":"french_2","LCID":"fr-fr"}]

Join two tables on multiple conditions - SQL Server

I need to join two tables based on some conditions. The src tale in my daily input. I need to join the src table with the mstr table based on some criteria which is as below.
If I get a join between the two tables by memberid, fname and lname then I need to take the dob from the mstr table.
If I do not get a matching criteria on the above basis then I need to join just by member id and then take the dob. If there are multiple records available in the mstr by member id then I need to pick that record which has the older of the dob.
If I do not get a match by memberid also then the need to randomly create a fname which would be xx+5random numbers, lname would be ZZ+5andom numbers and dob as today's date - 110 years.
This is what I have tried which would help you expedite the solution. I am providing the sample data for both the tables
declare #src table (memberids int, fnames varchar(25), lnames varchar(25));
insert into #src values
(1, 'Ankit','Bansal'),
(2, 'Dinesh','Bansal'),
(3, 'Sushil','Dania'),
(4, '',''),
(5, Null ,Null),
(10,Null,Null)
select * from #src
declare #mstr table (memberid int, fname varchar(25), lname varchar(25),dob date);
insert into #mstr values
(1, 'Ankit','Bansal','2010-06-24'),
(2, 'Dinesh','Bansal','2009-06-24'),
(3, 'Sushil','Dania','2000-06-24'),
(4, 'Sunil','Wadh','2011-06-24'),
(5, 'Suresh','Bansal','2000-06-24'),
(5, 'Animesh','Bajaj','2001-06-24'),
(6, 'Dhiresh','Jain','2001-06-24');
select * from #mstr;
This is the query that I have written.
select memberids, fnames, lnames, a.dob
from #src
outer apply
(select dob
from #mstr where memberids = memberid and fnames = fname
and lnames = lname) a ;
The current result that I am getting is
memberids fnames lnames dob
1 Ankit Bansal 2010-06-24
2 Dinesh Bansal 2009-06-24
3 Sushil Dania 2000-06-24
4 NULL
5 NULL NULL NULL
10 NULL NULL NULL
However the output should look like below
memberids fnames lnames dob
1 Ankit Bansal 24-06-2010
2 Dinesh Bansal 24-06-2009
3 Sushil Dania 24-06-2000
4 Sunil Wadh 24-06-2011
5 Suresh Bansal 24-06-2000
10 XX12345 ZZ123456 Today's Date - 110 years
I believe the following will do (tested on SQL Fiddle):
SELECT #src.memberids
, CASE WHEN a.memberid IS NOT NULL THEN #src.fnames
WHEN b.memberid IS NOT NULL THEN b.fname
ELSE 'XX' + FORMAT(ABS(CAST(CHECKSUM(NewId()) AS BIGINT)) % 100000, '00000')
END AS fnames
, CASE WHEN a.memberid IS NOT NULL THEN #src.lnames
WHEN b.memberid IS NOT NULL THEN b.lname
ELSE 'ZZ' + FORMAT(ABS(CAST(CHECKSUM(NewId()) AS BIGINT)) % 100000, '00000')
END AS lnames
, CASE WHEN a.memberid IS NOT NULL THEN a.dob
WHEN b.memberid IS NOT NULL THEN b.dob
ELSE DATEADD(year, -110, CAST(GETDATE() AS DATE))
END AS dob
FROM #src
LEFT JOIN #mstr a ON a.memberid = #src.memberids
AND a.fname = #src.fnames
AND a.lname = #src.lnames
OUTER APPLY (
SELECT TOP 1 b.memberid, b.fname, b.lname, b.dob
FROM #mstr b
WHERE b.memberid = #src.memberids
ORDER BY b.dob
) b
Please check this query. This should work but I must say that there are flaw in table design. If this is the requirement, consider the following query. But if you have scope, you can rethink to redesign your table first.
declare #src table (memberids int, fnames varchar(25), lnames varchar(25));
insert into #src values
(1, 'Ankit','Bansal'),
(2, 'Dinesh','Bansal'),
(3, 'Sushil','Dania'),
(4, '',''),
(5, Null ,Null),
(10,Null,Null)
declare #mstr table (memberid int, fname varchar(25), lname varchar(25),dob date);
insert into #mstr values
(1, 'Ankit','Bansal','2010-06-24'),
(2, 'Dinesh','Bansal','2009-06-24'),
(3, 'Sushil','Dania','2000-06-24'),
(4, 'Sunil','Wadh','2011-06-24'),
(5, 'Suresh','Bansal','2000-06-24'),
(5, 'Animesh','Bajaj','2001-06-24'),
(6, 'Dhiresh','Jain','2001-06-24');
SELECT M.memberid,M.fname,M.lname,M.dob
FROM (
SELECT M.memberid, MIN(M.dob) dob
FROM #src S
INNER JOIN #mstr M ON S.memberids = M.memberid
AND S.fnames = M.fname
AND S.lnames = M.lname
GROUP BY M.memberid
)B
INNER JOIN #mstr M ON B.memberid = M.memberid AND B.dob = M.dob
UNION
SELECT M.memberid,M.fname,M.lname,M.dob
FROM (
SELECT M.memberid, MIN(M.dob) dob
FROM #src S
INNER JOIN #mstr M ON S.memberids = M.memberid
AND (S.fnames IS NULL OR S.fnames = '')
GROUP BY M.memberid
)B
INNER JOIN #mstr M ON B.memberid = M.memberid AND B.dob = M.dob
UNION
SELECT C.smid memberids, 'XX'+CAST ((convert(numeric(5,0),rand() * 10000) + 9999)AS VARCHAR(5)) fname,
'ZZ'+CAST ((convert(numeric(5,0),rand() * 10000) + 9999)AS VARCHAR(5)) lnames,
DATEADD(YY,-110, GETDATE()) dob
FROM
(
SELECT S.memberids smid,M.memberid mmid
FROM #src S
LEFT JOIN #mstr M ON S.memberids = M.memberid
)C WHERE
C.smid IS NOT NULL
AND C.mmid IS NULL

SQL to fetch out dump data

I kind of stuck in fetching out the count of unique customers I have in sqlserver table. The way table storing data is:
+----------+----------+----------+
| Value | Label | ClientID |
+----------+----------+----------+
| Mr | Title | 1 |
| Sul | Forename | 1 |
| Last | Surname | 1 |
| WD17 6JJ | Postcode | 1 |
+----------+----------+----------+
Now I have to count\list unique customer on the basis of forename,surname,postcode. Can someone please help
Here are two queries that will give you the desired results:
DECLARE #T table (Value varchar(255), Label varchar(255), ClientID int)
INSERT INTO #T
VALUES
('Mr', 'Title', 1)
, ('Sul', 'Forename', 1)
, ('Last', 'Surname', 1)
, ('WD17 6JJ', 'Postcode', 1)
, ('Dr', 'Title', 2) -- different Title will be ignored
, ('Sul', 'Forename', 2)
, ('Last', 'Surname', 2)
, ('WD17 6JJ', 'Postcode', 2)
, ('Mr', 'Title', 3)
, ('Sul2', 'Forename', 3) -- different Forename
, ('Last', 'Surname', 3)
, ('WD17 6JJ', 'Postcode', 3)
-- Using JOIN
SELECT DISTINCT
T1.Value Forename
, T2.Value Surname
, T3.Value Postcode
FROM
#T T1
JOIN #T T2 ON T1.ClientID = T2.ClientID AND T2.Label = 'Surname'
JOIN #T T3 ON T1.ClientID = T3.ClientID AND T3.Label = 'Postcode'
WHERE T1.Label = 'Forename'
-- Using PIVOT
SELECT DISTINCT
Forename
, Surname
, Postcode
FROM
(
SELECT
Value
, Label
, ClientID
FROM #T
) T
PIVOT
(
MAX (Value)
FOR Label IN
(
Forename, Surname, Postcode
)
) P
for a count of distinct Customers
SELECT COUNT (DISTINCT ClientId)
FROM dbo.table
Or use pivot to get a distinct list
DECLARE #Table TABLE ( [Value] NVARCHAR(20), Label NVARCHAR(20), ClientID INT)
INSERT INTO #Table
([Value], Label, ClientID)
VALUES
(N'Mr', N'Title', 1),
(N'Sul', N'Forename', 1),
(N'Last', N'Surname', 1),
(N'WD17 6JJ', N'Postcode', 1)
SELECT Pvt.Forename
, Pvt.Surname
, Pvt.Postcode
FROM #Table T
PIVOT ( MAX([Value]) FOR Label IN ([Title], [Forename], [Surname], [Postcode])) AS Pvt
GROUP BY
Pvt.Forename
,Pvt.Surname
,Pvt.Postcode
You can do something like this:
SELECT DISTINCT
id.clientID,
title.title,
Forename.Forename,
Surname.Surname,
Postcode.Postcode
FROM table id
CROSS APPLY (SELECT value AS title FROM table title WHERE label = 'title' AND title.clientID = id.clientID ) title
CROSS APPLY (SELECT value AS Forename FROM table Forename WHERE label = 'Forename' AND Forename.clientID = id.clientID ) Forename
CROSS APPLY (SELECT value AS Surname FROM table Surname WHERE label = 'Surname' AND Surname.clientID = id.clientID ) Surname
CROSS APPLY (SELECT value AS Postcode FROM table Postcode WHERE label = 'Postcode' AND Postcode.clientID = id.clientID ) Postcode
It's not pretty, but should work.

need sql, a tricky one

suppose I have three relations
part(partno,partname,color) partno is P.K
supplier(supplierno,sname) supplierno is P.K
part_supplier(supplierno,partno) supplierno, partno is P.K.
Now i want to get the name of the suppliers who supply parts of only one color.
The having clause is your freind.
select field1, etc, count(*) records
from yourTables
where whatever
group by field1, etc
having count(*) = 1
SELECT s.supplierno
FROM supplier AS s
JOIN part_supplier AS ps
ON s.supplierno = ps.supplierno
JOIN part AS p
ON ps.partno = p.partno
GROUP BY s.supplierno
HAVING COUNT(DISTINCT p.color) = 1 -- only one color for all parts
Select sup.sname,count(*) from part_supplier Map
inner join supplier sup
on map.supplierno=sup.supplierno
inner join part par
on Map.partno=par.partno
group by sup.sname
having count(*)=1
Query:
SELECT S.sname
FROM part P, supplier S, part_supplier PS
WHERE S.supplierno = ps.supplierno AND
PS.partno = P.partno
GROUP BY P.partno
HAVING count(P.color) = 1
Tables :
CREATE TABLE supplier (
supplierno INT PRIMARY KEY,
suppliername VARCHAR(10) NOT NULL
);
CREATE TABLE part (
partno INT PRIMARY KEY,
partname VARCHAR(10) NOT NULL,
color INT NOT NULL
);
CREATE TABLE part_supplier (
supplierno INT NOT NULL REFERENCES supplier(supplierno),
partno INT NOT NULL REFERENCES part(partno),
PRIMARY KEY (supplierno, partno)
);
Some data for testing :
INSERT INTO part VALUES (1, 'a',1), (2, 'b',2), (3, 'c',3),
(4, 'd',1), (5, 'e',2), (6, 'c',3),
(7, 'd',1), (8, 'e',2), (9, 'e',3);
INSERT INTO supplier VALUES (1,'a'), (2,'b'), (3,'c');
INSERT INTO part_supplier VALUES (1,1), (2,1), (3,3),
(4,1), (5,1), (6,1),
(7,2), (8,2), (9,3);