Convert multiple rows into a single row in SQL Server - sql

I want to change multiple rows into a single row based on Number and Type:
+------+---------+----------+-------+---------+-------+
| Name | Address | City | State | Number | Type |
+------+---------+----------+-------+---------+-------+
| XYZ | 123 Rd | New York | NY | 123D | Code1 |
| XYZ | 123 Rd | New York | NY | 56A45 | Code2 |
| XYZ | 123 Rd | New York | NY | D45256 | Code3 |
| XYZ | 123 Rd | New York | NY | 345TT | Code2 |
| XYZ | 123 Rd | New York | NY | 34561NN | Code2 |
| XYZ | 123 Rd | New York | NY | 84567YY | Code2 |
+------+---------+----------+-------+---------+-------+
Result
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
| Name | Address | City | State | code1 | Code2 | code2_II | Code2_III | Code2_IV | Code3 |
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
| XYZ | 123 Rd | New York | NY | 123D | 56A45 | 345TT | 34561NN | 84567YY | D45256 |
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
Query I used only work for Code2 and Code2_II but I want to bring Code2_III and Code2_IV
select
Name, Addresss, City, State,
min(Number) as Code1,
(case when min(Number) <> max(Number) then max(Number) end) as Code2
from
t
group by
Name, Addresss, City, State;

As Tim mentions in the comments to your question, this will be very difficult to do without using dynamic SQL.
This code is not exactly what you are looking for but should get you most of the way there.
CREATE TABLE #Addresses (
[Name] varchar(255),
[Address] varchar(255),
[City] varchar(255),
[State] varchar(255),
[Number] varchar(255),
[Type] varchar(255)
)
INSERT INTO #Addresses
VALUES
('XYZ','123 Rd','New York','NY','123D','Code1'),
('XYZ','123 Rd','New York','NY','56A45','Code2'),
('XYZ','123 Rd','New York','NY','D45256','Code3'),
('XYZ','123 Rd','New York','NY','345TT','Code2'),
('XYZ','123 Rd','New York','NY','34561NN','Code2'),
('XYZ','123 Rd','New York','NY','84567YY','Code2');
SELECT *
FROM (
SELECT
[Name], [Address], [City], [State], [Number]
, [Type] + '_' + LTRIM(STR(rn)) AS NewType
FROM (
SELECT *
,ROW_NUMBER() OVER(PARTITION BY [Name], [Type] ORDER BY [Number]) rn
FROM #Addresses
) a
) p
PIVOT (
MAX([Number])
FOR [NewType] IN ([Code1_1], [Code2_1], [Code2_2], [Code2_3], [Code2_4], [Code3_1])
) AS pvt
In short it is using ROW_NUMBER to figure out how many Numbers exist for a particular Type. It then uses a select statement to build a new name field called NewType.
From there it uses a PIVOT statement to break out the data into separate columns.
A few things to note.
As mentioned it is somewhat dynamic but you will have to use Dynamic SQL for the PIVOT part if you are going to run this in a larger query or have varying amount of values in the Type and Number fields. However if you know you will never have more than 4 of any code you can build the statement like this
SELECT *
FROM (
SELECT
[Name], [Address], [City], [State], [Number]
, [Type] + '_' + LTRIM(STR(rn)) AS NewType
FROM (
SELECT *
,ROW_NUMBER() OVER(PARTITION BY [Name], [Type] ORDER BY [Number]) rn
FROM #Addresses
) a
) p
PIVOT (
MAX([Number])
FOR [NewType] IN ([Code1_1], [Code1_2], [Code1_3], [Code1_4], [Code2_1], [Code2_2], [Code2_3], [Code2_4], [Code3_1], [Code3_2], [Code3_3], [Code3_4])
) AS pvt
Doing it this way will produce null fields for any code that doesn't have a value.
This will likely be slow with larger datasets.
Hope this gets you point in the right direction.

You can use conditional aggregation:
select Name, Addresss, City, State,
min(case when type = 'Code1' then number) as code1,
min(case when type = 'Code2' and seqnum = 1 then number) as code2,
min(case when type = 'Code2' and seqnum = 2 then number) as code2_II,
min(case when type = 'Code2' and seqnum = 3 then number) as code2_III,
min(case when type = 'Code2' and seqnum = 4 then number) as code2_IV,
min(case when type = 'Code3' and seqnum = 1 then number) as code3
from (select t.*,
row_number() over (partition by Name, Addresss, City, State, type order by type) as seqnum
from t
) t
group by Name, Addresss, City, State;
Note: This returns exactly six code columns. That seems to be what you expect. If you want a dynamic number of columns, you need to use dynamic SQL.

SELECT DISTINCT Name,
Address,
City,
STATE,
STUFF((
SELECT ',' + [tCode1].Number
FROM #TestTable [tCode1]
WHERE [tCode1].Type = 'Code1'
FOR XML PATH('')
), 1, 1, N'') [Code1],
STUFF((
SELECT ',' + [tCode2].Number
FROM #TestTable [tCode2]
WHERE [tCode2].Type = 'Code2'
FOR XML PATH('')
), 1, 1, N'') [Code2],
STUFF((
SELECT ',' + [tCode3].Number
FROM #TestTable [tCode3]
WHERE [tCode3].Type = 'Code3'
FOR XML PATH('')
), 1, 1, N'') [Code3]
FROM #TestTable;
I referenced this https://www.mssqltips.com/sqlservertip/2914/rolling-up-multiple-rows-into-a-single-row-and-column-for-sql-server-data/
To Rolling multiple rows into one
Result:
+------+---------+----------+-------+-------+-----------------------------+---------+
| Name | Address | City | State | Code1 | Code2 | Code3 |
+------+---------+----------+-------+-------+-----------------------------+---------+
| XYZ | 123 Rd | New York | NY | 123D | 56A45,345TT,34561NN,84567YY | D45256 |
+------+---------+----------+-------+-------+-----------------------------+---------+

Related

SQL - Multiple records into one line with conditions

I have two tables that look like this
Account Table
| Account Number | Account Name |
| -------------- | -------------- |
| 12345 | Bob Jones |
| 12346 | Jenny Smith |
| 12347 | Mary Frederick |
Email Table
| Account Number | Email Type |Email | IsPrimary |
| ---------------------- | --------------|-----------------------|-----------------|
| 12345 | WORK | BOBSMITH#STACK.COM | 0 |
| 12345 | HOME | BOBSMITH#GMAIL.COM | 0 |
| 12345 | WORK | BOBSMITH#APPLE.COM | 1 |
| 12345 | HOME | BOBSMITH#HOTMAIL.COM | 0 |
| 12346 | HOME | JMSMITH#GMAIL.COM | 1 |
| 12346 | WORK | JMSMITH#DISNEY.COM | 0 |
| 12346 | HOME | JMSMITH#HOTMAIL.COM | 0 |
| 12347 | HOME | MFRED#HOTMAIL.COM | 0 |
I need to end up with results like this:
| Account Number | Account Name | Home Email | Work Email | Alternate Email |
| ------------------------ | ----------------- |--------------------|--------------------|----------------------|
| 12345 | Bob Jones | BOBSMITH#GMAIL.COM | BOBSMITH#APPLE.COM | BOBSMITH#HOTMAIL.COM |
| 12346 | Jenny Smith | JMSMITH#GMAIL.COM | JMSMITH#DISNEY.COM | JMSMITH#HOTMAIL.COM |
| 12347 | Mary Frederick | MFRED#HOTMAIL.COM | | |
I have all possible combinations of emails such as multiple work and no home, no primaries at all, multiple primaries... etc.
I need the logic to be:
Home Email: When type is Home. If there is a primary, choose the first one. If there is no primary, choose the first non-primary home email. Otherwise make it blank
Work Email: Same as above but for work
Alternate Email: Any third email on the list. If there are more than 3, choose home over work.
I have tried a ton of things and nothing has worked well. Here is my top attempt -two things don't work about it. The 'top 1' returns a lot of nulls and the code for alternates isn't always mutually exclusive to the home and work email, especially for people that have no primary checked:
select distinct
A01.AccountNumber,
A01.FirstName,
A01.LastName,
(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'HOME' and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'HOME')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as HomeEmail
,(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'WORK' and A07.EmailType is not null)--and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'WORK')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as WorkEmail
,(
select top 1 (A07.EmailAddress)
from A07_AccountEmails A07
where A07.AccountNumber = A01.AccountNumber
and A07.Active = 1 and A07.UseAsPrimary = 0
) as AlternateEmailMulti
from A01_AccountMaster A01
left outer join A07_AccountEmails A07 on A07.AccountNumber = A01.AccountNumber
where
A01.[Status] = 'A'
It seems a simple sub-query per email address should do the trick? I have added them in an OUTER APPLY to allow the values to be used twice, once in the select, and once to be excluded from the alternate email column.
declare #Account table (Number int, [Name] varchar(64));
declare #Email table (AccountNumber int, [Type] varchar(4), Email varchar(256), IsPrimary bit);
insert into #Account (Number, [Name])
values
(12345, 'Bob Jones'),
(12346, 'Jenny Smith'),
(12347, 'Mary Frederick');
insert into #Email (AccountNumber, [Type], Email, IsPrimary)
values
(12345, 'WORK', 'BOBSMITH#STACK.COM', 0),
(12345, 'HOME', 'BOBSMITH#GMAIL.COM', 0),
(12345, 'WORK', 'BOBSMITH#APPLE.COM', 1),
(12345, 'HOME', 'BOBSMITH#HOTMAIL.COM', 0),
(12346, 'HOME', 'JMSMITH#GMAIL.COM', 1),
(12346, 'WORK', 'JMSMITH#DISNEY.COM', 0),
(12346, 'HOME', 'JMSMITH#HOTMAIL.COM', 0),
(12347, 'HOME', 'MFRED#HOTMAIL.COM', 0);
select A.Number, A.[Name]
, E.Home [Home Email]
, E.Work [Work Email]
, (
select top 1 Email
from #Email
where AccountNumber = A.Number
and Email not in (E.Home, E.Work)
order by case when [Type] = 'HOME' then 1 else 0 end desc
)
from #Account A
outer apply (
select
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'HOME' order by IsPrimary desc),
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'WORK' order by IsPrimary desc)
) E (Home, Work);
Returns your desired results:
Account Number
Account Name
Home Email
Work Email
Alternate Email
12345
Bob Jones
BOBSMITH#HOTMAIL.COM
BOBSMITH#APPLE.COM
BOBSMITH#GMAIL.COM
12346
Jenny Smith
JMSMITH#GMAIL.COM
JMSMITH#DISNEY.COM
JMSMITH#HOTMAIL.COM
12347
Mary Frederick
MFRED#HOTMAIL.COM
NULL
NULL
Note: If you provide your sample data as DDL+DML (as I have here) it makes it much easier for people to assist.
Here is an alternative approach which list all e-mails, primaries first.
We use group_,concat to show all matching entries, order by primary prim inversed by desc and finally a newline separator \n to make a vertical list.
NB : It doesn't print correctly in the terminal because of the newlines.
USE test;
CREATE TABLE user (
acc INT,
nam CHAR(10)
);
INSERT INTO user VALUES ( 1, 'Andrew');
INSERT INTO user VALUES ( 2, 'Bernard');
INSERT INTO user VALUES ( 3, 'Charles');
CREATE TABLE telMail (
acc INT,
email CHAR(25),
type CHAR(5),
prim INT
);
INSERT INTO telMail VALUES(1,'a#w0','Work',0);
INSERT INTO telMail VALUES(1,'a#w1','Work',0);
INSERT INTO telMail VALUES(1,'ap#w2','Work',1);
INSERT INTO telMail VALUES(1,'a#w3','Work',0);
INSERT INTO telMail VALUES(1,'a#h1','Home',0);
INSERT INTO telMail VALUES(1,'a#h2','Home',0);
INSERT INTO telMail VALUES(2,'b#h1','Home',0);
INSERT INTO telMail VALUES(2,'b#w1','Work',0);
INSERT INTO telMail VALUES(2,'b#w2','Work',0);
INSERT INTO telMail VALUES(3,'c#h1','Home',0);
INSERT INTO telMail VALUES(3,'cp#h1','Home',1);
INSERT INTO telMail VALUES(3,'c#w1','Work',0);
SELECT
u.acc 'Account',
u.nam 'Name',
GROUP_CONCAT(DISTINCT w.email ORDER BY w.prim DESC SEPARATOR '\n' ) work_mail,
GROUP_CONCAT(DISTINCT h.email ORDER BY h.prim DESC SEPARATOR '\n' ) home_mail
FROM
(SELECT acc,email,prim FROM telMail WHERE type ='Home') h
JOIN
user u on u.acc=h.acc
JOIN
(SELECT acc,email,prim FROM telMail WHERE type ='Work') w ON u.acc = w.acc
GROUP BY u.acc, u.nam
ORDER BY u.acc;
DROP TABLE user;
DROP TABLE telMail;

Pivot rows into columns with fixed rows but unknown content in SQL Server

I've seen lots of questions relating to pivoting rows into columns, but nothing similar enough to my problem to make any headway with it.
I have a dataset that looks something like this:
| candidate | qualification | unit | passed |
---------------------------------------------
| C1 | Q1 | U1-1 | 1 |
| C1 | Q1 | U1-2 | 1 |
| C1 | Q2 | U2-1 | 0 |
| C1 | Q2 | U2-2 | 1 |
| C2 | Q1 | U1-1 | 0 |
| C2 | Q1 | U1-2 | 0 |
| C2 | Q2 | U2-1 | 1 |
| C2 | Q2 | U2-2 | 1 |
where each candidate can be signed up to multiple qualifications, which each have multiple units that can be passed (1) or failed (0).
I need the data to be transformed to look like:
| candidate | qualification | unit_1 | unit_1_passed | unit_2 | unit_2_passed |
-------------------------------------------------------------------------------
| C1 | Q1 | U1-1 | 1 | U1-2 | 1 |
| C1 | Q2 | U2-1 | 0 | U2-2 | 1 |
| C2 | Q1 | U1-1 | 0 | U1-2 | 0 |
| C2 | Q2 | U2-1 | 1 | U2-2 | 1 |
so that the unit for each qualification and if is passed is pivoted into a column.
I know that there will always be fixed number of units per qualification, but I do not know what the unit names will be in advance.
My query currently looks like:
select
candidate,
qualification,
unit,
passed
from exams
but I don't know how to go about pivoting the rows into columns.
Thanks in advance.
You can use window function row_number() to assign a rank to each unit in each candidate/qualification group, and then pivot with conditional aggregation:
select
candidate,
qualification,
max(case when rn = 1 then unit end) unit_1,
max(case when rn = 1 then passed end) unit_1_passed,
max(case when rn = 2 then unit end) unit_2,
max(case when rn = 2 then passed end) unit_2_passed
from (
select
t.*,
row_number() over(
partition by candidate, qualification
order by unit
) rn
from exams t
) t
group by candidate, qualification
This is the best I could come up with
select 'C1' as 'candidate', 'Q1' as 'qualification', 'U1-1' as 'unit', '1' as 'passed' into #tmp union
select 'C1', 'Q1', 'U1-2', '1' union
select 'C1', 'Q2', 'U2-1', '0' union
select 'C1', 'Q2', 'U2-2', '1' union
select 'C2', 'Q1', 'U1-1', '0' union
select 'C2', 'Q1', 'U1-2', '0' union
select 'C2', 'Q2', 'U2-1', '1' union
select 'C2', 'Q2', 'U2-2', '1'
DECLARE #cols AS NVARCHAR(MAX),#colsUnit AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select * from #tmp
DECLARE #UnitNames VARCHAR(8000)
SELECT #UnitNames = COALESCE(#UnitNames + ', ', '') + 'unit_' +unitnumber
FROM (select distinct right(unit,1) unitnumber from #tmp)a
select candidate, unit, qualification, passed, 'unit_'+b.unitnumber as unitnumber into #tmp2 from #tmp a
inner join (select distinct right(unit,1) unitnumber from #tmp) b on right(a.unit,1) = b.unitnumber
where right(unit,1) = unitnumber
order by unitnumber
select * from #tmp2 order by unitnumber
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.unitnumber)
FROM #tmp2 c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #colsUnit = STUFF((SELECT distinct ',' + QUOTENAME(c.unitnumber)
FROM #tmp2 c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT candidate, qualification, unit,
' + #colsUnit + ' from
(
select candidate
, qualification
, passed
, unit
, unitnumber
from #tmp2
) x
pivot
(
max(passed)
for unitnumber in (' + #colsUnit + ')
) p '
print #query
exec (#query)
drop table #tmp, #tmp2
It yields a table kinda close to yours:
Hope it helps

SQL - selecting only certain rows if they exist

I have a table that contains Home addresses and Mailing addresses. It looks like this:
ID Name StNum StName City State Zip Type
-- ---- ----- ------ ---- ----- --- ----
1 Joe 1234 Main St Waco TX 76767 HOM
1 Joe 2345 High St Waco TX 76763 MLG
2 Amy 3456 Broad St Athens GA 34622 HOM
3 Mel 987 Front St Cary NC 65331 HOM
3 Mel 1111 Main Ave Hilo HI 99779 MLG
I need to write an SQL statement that will only return the Mailing address (MLG record) if it exists, and if not, will return the Home address (HOM record).
The expected results from this table would be:
ID Name StNum StName City State Zip Type
-- ---- ----- ------ ---- ----- --- ----
1 Joe 2345 High St Waco TX 76763 MLG
2 Amy 3456 Broad St Athens GA 34622 HOM
3 Mel 1111 Main Ave Hilo HI 99779 MLG
Any help that you can provide would be much appreciated! Thanks!
use correlated subquery
select * from
(
select *,case when Type='MLG' then 1 else 0 end as typeval
from tablename
)A where typeval in (select max(case when Type='MLG' then 1 else 0 end) from tablename b
where a.name=b.name)
OR if your DB supports row_number() then u can try below -
select * from
(
select *, row_number() over(partition by name order by case when Type='MLG' then 1 else 0 end desc)
from tablename
)A where rn=1
In case you are using SQL Server, i would solve it with the ROW_NUMBER function.
SELECT ID, Name, StNum, StName, City, State, Zip, Type
FROM (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type DESC) AS Rn
FROM yourtable
)
WHERE Rn = 1
This can be done using a WHERE clause that exclude the ids of the users who have MLG
Schema (MySQL v5.7)
CREATE TABLE test (
`ID` INTEGER,
`Name` VARCHAR(3),
`StNum` INTEGER,
`StName` VARCHAR(8),
`City` VARCHAR(6),
`State` VARCHAR(2),
`Zip` INTEGER,
`Type` VARCHAR(3)
);
INSERT INTO test
(`ID`, `Name`, `StNum`, `StName`, `City`, `State`, `Zip`, `Type`)
VALUES
('1', 'Joe', '1234', 'Main St', 'Waco', 'TX', '76767', 'HOM'),
('1', 'Joe', '2345', 'High St', 'Waco', 'TX', '76763', 'MLG'),
('2', 'Amy', '3456', 'Broad St', 'Athens', 'GA', '34622', 'HOM'),
('3', 'Mel', '987', 'Front St', 'Cary', 'NC', '65331', 'HOM'),
('3', 'Mel', '1111', 'Main Ave', 'Hilo', 'HI', '99779', 'MLG');
Query #1
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t1
WHERE t1.`Type` = 'MLG'
OR t1.id NOT IN
(
SELECT id
FROM test t2
WHERE t2.`Type` = 'MLG'
);
Output :
| id | name | StNum | StName | City | State | Zip | Type |
| --- | ---- | ----- | -------- | ------ | ----- | ----- | ---- |
| 1 | Joe | 2345 | High St | Waco | TX | 76763 | MLG |
| 2 | Amy | 3456 | Broad St | Athens | GA | 34622 | HOM |
| 3 | Mel | 1111 | Main Ave | Hilo | HI | 99779 | MLG |
View on DB Fiddle
Or, my first dumb version :
This can be done using UNION
Schema (MySQL v5.7)
CREATE TABLE test (
`ID` INTEGER,
`Name` VARCHAR(3),
`StNum` INTEGER,
`StName` VARCHAR(8),
`City` VARCHAR(6),
`State` VARCHAR(2),
`Zip` INTEGER,
`Type` VARCHAR(3)
);
INSERT INTO test
(`ID`, `Name`, `StNum`, `StName`, `City`, `State`, `Zip`, `Type`)
VALUES
('1', 'Joe', '1234', 'Main St', 'Waco', 'TX', '76767', 'HOM'),
('1', 'Joe', '2345', 'High St', 'Waco', 'TX', '76763', 'MLG'),
('2', 'Amy', '3456', 'Broad St', 'Athens', 'GA', '34622', 'HOM'),
('3', 'Mel', '987', 'Front St', 'Cary', 'NC', '65331', 'HOM'),
('3', 'Mel', '1111', 'Main Ave', 'Hilo', 'HI', '99779', 'MLG');
Query #1
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t1
WHERE t1.`Type` = 'MLG'
UNION ALL
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t2
WHERE t2.id NOT IN (SELECT id FROM test t3 WHERE t3.`Type` = 'MLG')
ORDER BY id;
Output
| id | name | StNum | StName | City | State | Zip | Type |
| --- | ---- | ----- | -------- | ------ | ----- | ----- | ---- |
| 1 | Joe | 2345 | High St | Waco | TX | 76763 | MLG |
| 2 | Amy | 3456 | Broad St | Athens | GA | 34622 | HOM |
| 3 | Mel | 1111 | Main Ave | Hilo | HI | 99779 | MLG |
View on DB Fiddle
This is a prioritization query. With two values, often the simplest method is union all with not exists (or not in).
That does not generalize well For more values, using row_number() with case is convenient:
select t.*
from (select t.*,
row_number() over (partition by id
order by (case when type = 'MLG' then 1 else 2 end)
) as seqnum
from t
) t
where seqnum = 1;
In your particular case, you could use order by type desc, because the two types happen to be prioritized in reverse alphabetical ordering. However, I recommend using case because the intention is more explicit.

SQL Server - How to PIVOT multi value of same key

How to PIVOT a column which have multi values with same Key.
For instance, we have table
ID FieldName FieldValue
1 Name Jack
1 Country Auz
1 PostCode 1234
1 Name Jhon
2 Name Leo
2 Country USA
2 PostCode 2345
I want to get the result table after pivot:
ID Name Country PostCode
1 Jack Auz 1234
1 Jhon Auz 1234
2 Leo USA 2345
I have used code with pivot function:
SELECT *
FROM (
SELECT
ID, FieldName, FieldValue
FROM Table
) AS p
PIVOT
(
MAX(FieldValue)
FOR FieldName IN ([Name], [Country], [PostCode])
) AS pvt
From the above code, The result table will only get one record for ID '1', I think this may due to the function of 'MAX' used. Is there any way to show two records by using any other function to solve it?
You have an unusual situation in your data because ID 1 has 2 names associated with it, and you want both to share the other attributes such as postcode etc. For this you can use an apply operator to return 1 or more names for each ID, and then group by the combination of id and name.
SELECT
id
, Name
, MAX(CASE WHEN fieldname = 'Country' THEN fieldvalue END) AS Country
, MAX(CASE WHEN fieldname = 'Postcode' THEN fieldvalue END) AS Postcode
FROM (
SELECT
t.*, ca.Name
FROM mytable AS t
CROSS APPLY (
SELECT
FieldValue AS Name
FROM mytable AS t2
WHERE t.id = t2.id
AND t2.FieldName = 'Name'
) AS ca
WHERE FieldName IN ('Country','PostCode')
) t
GROUP BY
id
, name
;
also see: https://rextester.com/GKOK78960
result:
+----+----+------+---------+----------+
| | id | Name | Country | Postcode |
+----+----+------+---------+----------+
| 1 | 1 | Jack | Auz | 1234 |
| 2 | 1 | Jhon | Auz | 1234 |
| 3 | 2 | Leo | USA | 2345 |
+----+----+------+---------+----------+
note that using cross apply is a bit like an inner join; if there is no name associated to an id, that id will not be returned by the query above. If there is a need for an id without name use outer apply instead (as this is similar to left join).
As an alternative, you could use pivot like so:
SELECT id, name, [Country], [PostCode]
FROM (
SELECT
t.*, ca.Name
FROM mytable AS t
CROSS APPLY (
SELECT
FieldValue AS Name
FROM mytable AS t2
WHERE t.id = t2.id
AND t2.FieldName = 'Name'
) AS ca
WHERE FieldName IN ('Country','PostCode')
) AS p
PIVOT
(
MAX(FieldValue)
FOR FieldName IN ([Country], [PostCode])
) AS pvt

Use Split String Table Value Function in a Select statement

I have a table from a third party source that I have no control over. Each address field contains the FULL address including postcode. Each address has a dynamic number of separate address fields included. I have got a Split() TVF that works in splitting the address fields into seperate rows. However, when I CROSS APPLY the TVF table back to the SELECT query it returns a row for each ro in the TVF table. How do I get it to return one row for the main table and separate COLUMNS from the rows in the TVF table?
Example of addresses supplied:
a. 1 The Street, The Locality, The Town, The County, The Postcode
b. 2 The Building, The Street, The Town, The Postcode
c. Floor 3, 3 The Street, The Locality, The Town, The County, The Postcode
The TVF returns these as one value per row using the ',' as a delimiter. I then need to join that data back to the original data as one column per address field.
This is my SELECT QUERY:
select DISTINCT TOP 5 ttp.ProjectID
,cac.ID
,cac1.Proprietor1Address1
--,CASE WHEN addr.ID = 1 THEN addr.Data END AS Address1
FROM ArdentTest.ardent.LRTitlesToProcess ttp
JOIN LandRegistryData.landreg.CommercialandCorporateOwnershipData cac
ON ttp.TitleNo = cac.TitleNumber
JOIN (SELECT TitleNumber
,Proprietor1Address1
FROM LandRegistryData.landreg.CommercialandCorporateOwnershipData
WHERE 1 = 1
AND ISNULL(Proprietor1Address1, '') <> '') cac1
ON ttp.TitleNo = cac1.TitleNumber
CROSS APPLY DBAdmin.resource.Split(cac1.Proprietor1Address1, ', ') addr
WHERE 1 = 1
AND ttp.DateLRRequestSent IS NULL
AND cac.ID IN (50764, 78800, 157089, 206049, 449112)
ORDER BY 1
Which produces the following results:
ProjectID ID Proprietor1Address1
1010 50764 Bridge House, 1 Walnut Tree Close, Guildford, Surrey GU1 4LZ
1010 78800 Bridge House, 1 Walnut Tree Close, Guildford, Surrey GU1 4LZ
1010 157089 Bridge House, 1 Walnut Tree Close, Guildford, Surrey GU1 4LZ
1010 206049 Bridge House, 1 Walnut Tree Close, Guildford, Surrey GU1 4LZ
1010 449112 Church House, Great Smith Street, London SW1P 3AZ
I need to use the rows from the function to add separate address columns to the result set and I cannot figure out how to do it.
If you can make some assumption like an address can contain max 10 parts. You can use this script. I used STRING_SPLIT instead of your split function.
DECLARE #Table TABLE (Id INT, Address VARCHAR(500))
INSERT INTO #Table VALUES
(1, '1 The Street, The Locality, The Town, The County, The Postcode'),
(2, '2 The Building, The Street, The Town, The Postcode'),
(3, 'Floor 3, 3 The Street, The Locality, The Town, The County, The Postcode')
SELECT * FROM #Table T
CROSS APPLY(
SELECT * FROM (SELECT ROW_NUMBER()OVER(ORDER BY (SELECT NULL)) RN, * FROM STRING_SPLIT(T.Address, ',')) SRC
PIVOT (MAX(value) FOR RN IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10])) PVT
) X
Result:
Id Address 1 2 3 4 5 6 7 8 9 10
----------- -------------------------------------------------------------------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- ---------------------
1 1 The Street, The Locality, The Town, The County, The Postcode 1 The Street The Locality The Town The County The Postcode NULL NULL NULL NULL NULL
2 2 The Building, The Street, The Town, The Postcode 2 The Building The Street The Town The Postcode NULL NULL NULL NULL NULL NULL
3 Floor 3, 3 The Street, The Locality, The Town, The County, The Postcode Floor 3 3 The Street The Locality The Town The County The Postcode NULL NULL NULL NULL
You can create an XML out of your concatenated string and address each part via index
--the table (thx Serkan, I copied your DDL)
DECLARE #Table TABLE (Id INT, Address VARCHAR(500))
INSERT INTO #Table VALUES
(1, '1 The Street, The Locality, The Town, The County, The Postcode'),
(2, '2 The Building, The Street, The Town, The Postcode'),
(3, 'Floor 3, 3 The Street, The Locality, The Town, The County, The Postcode');
--This query will return the parts one by one
WITH Splitted AS
(
SELECT *
,CAST('<x>' + REPLACE((SELECT [Address] AS [*] FOR XML PATH('')),',','</x><x>') + '</x>' AS XML) AsXml
FROM #Table
)
SELECT *
,AsXml.value('/x[1]','nvarchar(max)') AS Col1
,AsXml.value('/x[2]','nvarchar(max)') AS Col2
,AsXml.value('/x[3]','nvarchar(max)') AS Col3
,AsXml.value('/x[4]','nvarchar(max)') AS Col4
,AsXml.value('/x[5]','nvarchar(max)') AS Col5
,AsXml.value('/x[6]','nvarchar(max)') AS Col6
FROM Splitted
--the result
/*+----+----------------+--------------+--------------+--------------+--------------+--------------+
| ID | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
+----+----------------+--------------+--------------+--------------+--------------+--------------+
| 1 | 1 The Street | The Locality | The Town | The County | The Postcode | NULL |
+----+----------------+--------------+--------------+--------------+--------------+--------------+
| 2 | 2 The Building | The Street | The Town | The Postcode | NULL | NULL |
+----+----------------+--------------+--------------+--------------+--------------+--------------+
| 3 | Floor 3 | 3 The Street | The Locality | The Town | The County | The Postcode |
+----+----------------+--------------+--------------+--------------+--------------+--------------+*/
--You might use REVERSE on all steps. This will bring the Postcode in the first place in all cases (as long as the postcode is the last element there)
WITH Splitted AS
(
SELECT *
,CAST('<x>' + REPLACE((SELECT REVERSE([Address]) AS [*] FOR XML PATH('')),',','</x><x>') + '</x>' AS XML) AsXml
FROM #Table
)
SELECT *
,REVERSE(AsXml.value('/x[1]','nvarchar(max)')) AS Col1
,REVERSE(AsXml.value('/x[2]','nvarchar(max)')) AS Col2
,REVERSE(AsXml.value('/x[3]','nvarchar(max)')) AS Col3
,REVERSE(AsXml.value('/x[4]','nvarchar(max)')) AS Col4
,REVERSE(AsXml.value('/x[5]','nvarchar(max)')) AS Col5
,REVERSE(AsXml.value('/x[6]','nvarchar(max)')) AS Col6
FROM Splitted;
--the result
/*+----+--------------+------------+------------+----------------+--------------+---------+
| ID | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
+----+--------------+------------+------------+----------------+--------------+---------+
| 1 | The Postcode | The County | The Town | The Locality | 1 The Street | NULL |
+----+--------------+------------+------------+----------------+--------------+---------+
| 2 | The Postcode | The Town | The Street | 2 The Building | NULL | NULL |
+----+--------------+------------+------------+----------------+--------------+---------+
| 3 | The Postcode | The County | The Town | The Locality | 3 The Street | Floor 3 |
+----+--------------+------------+------------+----------------+--------------+---------+*/