I have a ClientID formatted as a link to the file, it looks like:
Doe, John (W21135446).
I need to separate out this to be:
Last Name First Name File Number
Doe John W2135446
Including removing the () around the file number. How do I write the T-SQL code to do this for me?
I was able to achieve this using the CHARINDEX() and some string-manipulating system functions.
SELECT val.ClientID,
LTRIM(RTRIM(LEFT(ClientID, CHARINDEX(',', ClientID)-1))) AS LastName,
LTRIM(RTRIM(SUBSTRING(ClientID, CHARINDEX(',', ClientID) + 2, CHARINDEX('(', val.ClientID) - (CHARINDEX(',', ClientID)+2)))) AS FirstName,
LTRIM(RTRIM(SUBSTRING(ClientID, CHARINDEX('(', ClientID)+1, (CHARINDEX(')', val.ClientID) - CHARINDEX('(', ClientID)-1)))) AS FileNumber
FROM (VALUES ('Doe, John (W21135446)')) AS val (ClientID);
There are some notable caveats that always accompany working with strings like this: any name value with a comma (something like "Jones, Jr") will break, same with unexpected positions of the characters "(" and ")". So long as all of your data matches this exact pattern, this should help you get the job done.
I used the LTRIM() and RTRIM() functions to remove any leading or trailing spaces because I don't entirely trust my string-math, but they're not required so long as you verify your starting and ending points. Also, pay close attention to the arithmetic groupings and order of operations.
Output:
ClientID
LastName
FirstName
FileNumber
Doe, John (W21135446)
Doe
John
W21135446
There are a few ways to tackle this depending on what version of SQL Server you are on. Each of these:
DECLARE #string VARCHAR(1000) = 'Doe, John (W21135446).'
--==== 1. CHARINDEX + APPLY
SELECT
ClientId = #string,
LastName = SUBSTRING(#string, i1.Pos+1, i2.Pos-i1.Pos-1),
FirstName = SUBSTRING(#string, 0, i1.Pos),
FileNumer = SUBSTRING(#string, i2.Pos+1, i1.ln-i2.Pos-2)
FROM (VALUES(CHARINDEX(',',#string),LEN(#string))) AS i1(Pos,Ln)
CROSS APPLY (VALUES(CHARINDEX('(',#string,i1.Pos))) AS i2(Pos);
--==== 2. DELIMITEDSPLIT8K + CROSS TAB APPLY
SELECT
ClientId = #string,
LastName = MAX(CASE s.ItemNumber WHEN 1 THEN TRIM(',() .' FROM s.item) END),
FirstName = MAX(CASE s.ItemNumber WHEN 2 THEN TRIM(',() .' FROM s.item) END),
FileNumber = MAX(CASE s.ItemNumber WHEN 3 THEN TRIM(',() .' FROM s.item) END)
FROM dbo.DelimitedSplit8K(#string,' ') AS s;
Both Return:
ClientId LastName FirstName FileNumer
----------------------- ----------- ---------- -----------
Doe, John (W21135446). John Doe W21135446
Against a table:
--==== Sample Data
DECLARE #strings TABLE (ClientID VARCHAR(100));
INSERT #strings VALUES('Doe, John (W21135446).'),('Smith, Fred (X21139999).'),
('White, Walter (BCH20XD0).'),('Black, Jack (998877).')
--==== 1. CHARINDEX + APPLY
SELECT itvf_extract.*
FROM #strings AS s
CROSS APPLY
(
SELECT
ClientId = s.ClientID,
LastName = TRIM(SUBSTRING(s.ClientID, i1.Pos+1, i2.Pos-i1.Pos-1)),
FirstName = TRIM(SUBSTRING(s.ClientID, 0, i1.Pos)),
FileNumber = SUBSTRING(s.ClientID, i2.Pos+1, i1.ln-i2.Pos-2)
FROM (VALUES(CHARINDEX(',',s.ClientID), LEN(s.ClientID))) AS i1(Pos,Ln)
CROSS APPLY (VALUES(CHARINDEX('(',s.ClientID, i1.Pos))) AS i2(Pos)
) AS itvf_extract;
--==== 2. DELIMITEDSPLIT8K + CROSS TAB APPLY
SELECT
ClientId = s.ClientID,
LastName = MAX(CASE sp.ItemNumber WHEN 1 THEN TRIM(',() .' FROM sp.item) END),
FirstName = MAX(CASE sp.ItemNumber WHEN 2 THEN TRIM(',() .' FROM sp.item) END),
FileNumber = MAX(CASE sp.ItemNumber WHEN 3 THEN TRIM(',() .' FROM sp.item) END)
FROM #strings AS s
CROSS APPLY dbo.DelimitedSplit8K(s.ClientID,' ') AS sp
GROUP BY s.ClientID;
Both Return:
ClientId LastName FirstName FileNumber
-------------------------- --------- ---------- -----------
Doe, John (W21135446). John Doe W21135446
Smith, Fred (X21139999). Fred Smith X21139999
White, Walter (BCH20XD0). Walter White BCH20XD0
Black, Jack (998877). Jack Black 998877
have a play.....reasonably flexible for up to a 4 part split
CREATE TABLE #yourtable(
reference VARCHAR(50) NOT NULL PRIMARY KEY
);
INSERT INTO #yourtable(reference) VALUES
('Doe, John (W21135446)'),
('(Doe), John [W21135446]'),
('Jones [Bill] (W2)'),
('{Smith}, Fred, 684664')
;
WITH CTE AS (
SELECT reference,
REPLACE(
TRIM(REPLACE(TRANSLATE(reference,'()|[]{},',REPLICATE('*',8)),'*','')) ,' ','.' )AS extracted
FROM #yourtable
)
SELECT
reference,
PARSENAME(extracted,3) as Lastname,
PARSENAME(extracted,2) as Firstname,
PARSENAME(extracted,1) as FileNo
FROM CTE
drop table #yourtable
Related
I have as selection of Columns called Parameter(1 to 10) and I need the row select to join on the column of another table, depending on the Matrix selected. This needs to be driven by the matrix selected. The rows to columns heading is a 1-1 mapping, but the values are 1 to many. Like below:
Matrix 1 - Name, Parameter1 = First Name, Parameter2 = Last Name
Matrix 2 - Location, Parameter1 = City, Parameter2 = State, Parameter3 = Country
All the data a held in the table client detail table. The base table is over 50 columns long and there are over 100 different parameters, along with 60+ matrix possibilities.
Example - Base Table
First Name
Last Name
City
State
Country
Mary
Smith
Austin
Texas
USA
Steven
Johnson
Toronto
Ontario
Canada
Matrix
Parameter1
Parameter2
Parameter3
1
City
State
Country
2
First Name
Last Name
I need the outputs to be like :
Output A
When Matrix 1 is selected
City
State
Country
Austin
Texas
USA
Toronto
Ontario
Canada
Output B
When Matrix 2 selected
First Name
Last Name
Mary
Smith
Steven
Johnson
I believe you can do what you want using dynamic SQL that builds up the query and the variable conditions into a SQL string and then executes the query using sp_ExecuteSql.
Something like:
DECLARE #ConditionTemplate VARCHAR(MAX) = 'B.<ColumnName> = #Parameter<ParameterId>'
DECLARE #ConditionSeparator VARCHAR(50) = '
AND '
DECLARE #Conditions NVARCHAR(MAX) = (
SELECT STRING_AGG(REPLACE(REPLACE(
#ConditionTemplate
, '<ColumnName>', QUOTENAME(P.ColumnName))
, '<ParameterId>', CAST(P.ParameterId AS VARCHAR))
, #ConditionSeparator)
FROM Matrix M
CROSS APPLY (
VALUES
(1, M.Parameter1),
(2, M.Parameter2),
(3, M.Parameter3)
) P(ParameterId, ColumnName)
WHERE M.MatrixId = #MatrixId
AND P.ColumnName > ''
)
DECLARE #Sql NVARCHAR(MAX) = '
SELECT *
FROM BaseTable B
WHERE '
+ #Conditions
DECLARE #Params NVARCHAR(MAX) = N'#Parameter1 VARCHAR(100), #Parameter2 VARCHAR(100), #Parameter3 VARCHAR(100)'
PRINT #Sql
EXEC sp_ExecuteSql #Sql, #Params, #Parameter1, #Parameter2, #Parameter3
Most of the work is done in the #Conditions calculation. That calculation selects the proper matrix row, flatens out the data by mapping each parameter column into a numbered row, formats each into a "column = #Parameter" equality comparison, and then uses STRING_AGG() to join the conditions together. That list of conditions is then combined with the rest of the query and executed. Because the executed dynamic SQL cannot access the parameters from the calling sql, the parameters must be explicitly passed in via the sp_ExecuteSql call.
Given the following parameters:
DECLARE #MatrixId INT = 2
DECLARE #Parameter1 VARCHAR(100) = 'Steven'
DECLARE #Parameter2 VARCHAR(100) = 'Johnson'
DECLARE #Parameter3 VARCHAR(100) = NULL
The generated SQL would be as follows:
SELECT *
FROM BaseTable B
WHERE B.[First Name] = #Parameter1
AND B.[Last Name] = #Parameter2
Which will yield the following result:
First Name
Last Name
City
State
Country
Steven
Johnson
Toronto
Ontario
Canada
See this db<>fiddle.
I tried to repro this. Below is the SQL script.
Input Tables
1. base_table
First Name
Last Name
City
State
Country
Mary
Smith
Austin
Texas
USA
Steven
Johnson
Toronto
Ontario
Canada
2. Matrix table
Matrix
Parameter1
Parameter2
Parameter3
1
City
State
Country
2
[First Name]
[Last Name]
null
SQL script:
DECLARE #StgTable TABLE ([ColName] varchar(256), Done bit default(0));
Insert into #StgTable( [ColName] ) select concat_ws(',',parameter1,parameter2,parameter3) from matrix_table
DECLARE #SQLstmnt nvarchar(max), #colnames varchar(200);
WHILE EXISTS (SELECT 1 FROM #StgTable WHERE Done = 0) BEGIN
SELECT TOP 1 #colnames = [ColName] FROM #StgTable WHERE Done = 0;
SET #SQLstmnt = 'SELECT '+ #colnames +' FROM base_table ;';
EXECUTE sp_executesql #SQLstmnt;
UPDATE #StgTable SET Done = 1 WHERE [ColName] = #colnames;
END;
See this db<>fiddle
Output:
City
State
Country
Austin
Texas
USA
Toronto
Ontario
Canada
First Name
Last Name
Mary
Smith
Steven
Johnson
Student Records are updated for subject and update date. Student can be enrolled in one or multiple subjects. I would like to get each student record with most subject update date and status.
CREATE TABLE Student
(
StudentID int,
FirstName varchar(100),
LastName varchar(100),
FullAddress varchar(100),
CityState varchar(100),
MathStatus varchar(100),
MUpdateDate datetime2,
ScienceStatus varchar(100),
SUpdateDate datetime2,
EnglishStatus varchar(100),
EUpdateDate datetime2
);
Desired query output, I am using CTE method but trying to find alternative and better way.
SELECT StudentID, FirstName, LastName, FullAddress, CityState, [SubjectStatus], UpdateDate
FROM Student
;WITH orginal AS
(SELECT * FROM Student)
,Math as
(
SELECT DISTINCT StudentID, FirstName, LastName, FullAddress, CityState,
ROW_NUMBER OVER (PARTITION BY StudentID, MathStatus ORDER BY MUpdateDate DESC) as rn
, _o.MathStatus as SubjectStatus, _o.MupdateDate as UpdateDate
FROM original as o
left join orignal as _o on o.StudentID = _o.StudentID
where _o.MathStatus is not null and _o.MUpdateDate is not null
)
,Science AS
(
...--Same as Math
)
,English AS
(
...--Same As Math
)
SELECT * FROM Math WHERE rn = 1
UNION
SELECT * FROM Science WHERE rn = 1
UNION
SELECT * FROM English WHERE rn = 1
First: storing data in a denormalized form is not recommended. Some data model redesign might be in order. There are multiple resources about data normalization available on the web, like this one.
Now then, I made some guesses about how your source table is populated based on the query you wrote. I generated some sample data that could show how the source data is created. Besides that I also reduced the number of columns to reduce my typing efforts. The general approach should still be valid.
Sample data
create table Student
(
StudentId int,
StudentName varchar(15),
MathStat varchar(5),
MathDate date,
ScienceStat varchar(5),
ScienceDate date
);
insert into Student (StudentID, StudentName, MathStat, MathDate, ScienceStat, ScienceDate) values
(1, 'John Smith', 'A', '2020-01-01', 'B', '2020-05-01'),
(1, 'John Smith', 'A', '2020-01-01', 'B+', '2020-06-01'), -- B for Science was updated to B+ month later
(2, 'Peter Parker', 'F', '2020-01-01', 'A', '2020-05-01'),
(2, 'Peter Parker', 'A+', '2020-03-01', 'A', '2020-05-01'), -- Spider-Man would never fail Math, fixed...
(3, 'Tom Holland', null, null, 'A', '2020-05-01'),
(3, 'Tom Holland', 'A-', '2020-07-01', 'A', '2020-05-01'); -- Tom was sick for Math, but got a second chance
Solution
Your question title already contains the word unpivot. That word actually exists in T-SQL as a keyword. You can learn about the unpivot keyword in the documentation. Your own solution already contains common table expression, these constructions should look familiar.
Steps:
cte_unpivot = unpivot all rows, create a Subject column and place the corresponding values (SubjectStat, Date) next to it with a case expression.
cte_recent = number the rows to find the most recent row per student and subject.
Select only those most recent rows.
This gives:
with cte_unpivot as
(
select up.StudentId,
up.StudentName,
case up.[Subject]
when 'MathStat' then 'Math'
when 'ScienceStat' then 'Science'
end as [Subject],
up.SubjectStat,
case up.[Subject]
when 'MathStat' then up.MathDate
when 'ScienceStat' then up.ScienceDate
end as [Date]
from Student s
unpivot ([SubjectStat] for [Subject] in ([MathStat], [ScienceStat])) up
),
cte_recent as
(
select cu.StudentId, cu.StudentName, cu.[Subject], cu.SubjectStat, cu.[Date],
row_number() over (partition by cu.StudentId, cu.[Subject] order by cu.[Date] desc) as [RowNum]
from cte_unpivot cu
)
select cr.StudentId, cr.StudentName, cr.[Subject], cr.SubjectStat, cr.[Date]
from cte_recent cr
where cr.RowNum = 1;
Result
StudentId StudentName Subject SubjectStat Date
----------- --------------- ------- ----------- ----------
1 John Smith Math A 2020-01-01
1 John Smith Science B+ 2020-06-01
2 Peter Parker Math A+ 2020-03-01
2 Peter Parker Science A 2020-05-01
3 Tom Holland Math A- 2020-07-01
3 Tom Holland Science A 2020-05-01
my data repeats rows for individual relationships between people. For example, the below states that John Smith is known by 3 employees:
Person EmployeeWhoKnowsPerson
John Smith Derek Jones
John Smith Adrian Daniels
John Smith Peter Low
I am looking to do the following:
1) Count the number of people who know John Smith. I have done this via the row_number() function and it appears to be behaving:
select Person, MAX(rowrank) as rowrank
from (
select Person, EmployeeWhoKnowsPerson, rowrank=ROW_NUMBER() over (partition by Person order by EmployeeWhoKnowsPerson desc)
from Data
) as t
group by Person
Which returns:
Person rowrank
John Smith 3
But now i am looking at concatenating the EmployeeWhoKnowsPerson column to return and was wondering how this might be possible:
Person rowrank EmployeesWhoKnow
John Smith 3 Derek Jones, Adrian Daniels, Peter Low
For SQL Server 2017 +
select
person,
count(*) as KnowsCount,
string_agg(EmployeeWhoKnowsPerson, ',') WITHIN GROUP (ORDER BY EmployeeWhoKnowsPerson ASC) AS EmployeesWhoKnowPerson
from
data
group by person;
For prior versions:
select
person,
count(*) as KnowsCount,
stuff((select ',' + EmployeeWhoKnowsPerson
from data as dd
where dd.Person = d.Person
order by EmployeeWhoKnowsPerson
for xml path('')), 1, 1, '') AS EmployeesWhoKnowPerson
from
data as d
group by person;
And you're overthinking that whole count of who knows piece.
Here's a SQL Fiddle Demo with an extra name thrown in.
If 2017+, you can use string_agg() in a simple group by
Example
Declare #YourTable Table ([Person] varchar(50),[EmployeeWhoKnowsPerson] varchar(50)) Insert Into #YourTable Values
('John Smith','Derek Jones')
,('John Smith','Adrian Daniels')
,('John Smith','Peter Low')
Select Person
,rowrank = sum(1)
,[EmployeeWhoKnowsPerson] = string_agg([EmployeeWhoKnowsPerson],', ')
From #YourTable
Group By Person
Returns
Person rowrank EmployeeWhoKnowsPerson
John Smith 3 Derek Jones, Adrian Daniels, Peter Low
If <2017 ... use the stuff()/xml approach
Select Person
,rowrank = sum(1)
,[EmployeeWhoKnowsPerson] = stuff((Select ', ' + [EmployeeWhoKnowsPerson]
From #YourTable
Where Person=A.Person
For XML Path ('')),1,2,'')
From #YourTable A
Group By Person
If I have a table with a column that contains fullnames such as:
fullname
------------
Joe Bloggs
Peter Smith
Mary Jones and Liz Stone
How can I retrieve the first and last name from each of the entries in the full name column using SQL. I'm not worried about the second name in the 3rd entry in my example i.e. Liz Stone.
So basically to retrieve
Firstname
---------
Joe
Peter
Mary
Lastname
--------
Bloggs
Smith
Jones
Here is a pre SQL Server 2016 method, which uses basic string functions to isolate the first and last names.
SELECT SUBSTRING(fullname, 1, CHARINDEX(' ', fullname) - 1) AS Firstname,
SUBSTRING(fullname,
CHARINDEX(' ', fullname) + 1,
LEN(fullname) - CHARINDEX(' ', fullname)) AS Lastname
FROM yourTable
Note that this solution assumes that the fullname column only contains a single first name and a single last name (i.e. no middle names, initials, etc.).
This is a slippery slope and there are no easy answers. That said, consider the following
Declare #YourTable table (FullName varchar(50))
Insert Into #YourTable values
('Joe Bloggs'),
('Peter Smith'),
('Betty Jane Martinez'),
('Mary Jones and Liz Stone')
Select A.*
,FirstName = Pos1+case when Pos3 is not null then ' '+Pos2 else '' end
,LastName = case when Pos3 is null then Pos2 else Pos3 end
From #YourTable A
Cross Apply (
Select Pos1 = xDim.value('/x[1]','varchar(max)')
,Pos2 = xDim.value('/x[2]','varchar(max)')
,Pos3 = xDim.value('/x[3]','varchar(max)')
,Pos4 = xDim.value('/x[4]','varchar(max)')
,Pos5 = xDim.value('/x[5]','varchar(max)')
,Pos6 = xDim.value('/x[6]','varchar(max)')
From (Select Cast('<x>' + replace((Select substring(FullName,1,charindex(' and ',FullName+' and ')-1) as [*] For XML Path('')),' ','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
FullName FirstName LastName
Joe Bloggs Joe Bloggs
Peter Smith Peter Smith
Betty Jane Martinez Betty Jane Martinez
Mary Jones and Liz Stone Mary Jones
If it helps with the visual, the CROSS APPLY generates
SELECT CASE
WHEN CHARINDEX(' ', FullName) > 0
THEN SUBSTRING(FullName, 1, LEN(FullName) - CHARINDEX(' ', REVERSE(FullName)))
ELSE ''
END AS FirstName,
CASE
WHEN CHARINDEX(' ', FullName) > 0
THEN REVERSE(SUBSTRING(REVERSE(FullName),
1,
CHARINDEX(' ', REVERSE(FullName)) - 1))
ELSE FullName
END AS LastName
FROM(VALUES('Mary Anne Bloggs'), ('Joe Bloggs'), ('Bloggs')) AS T(FullName);
This version checks that there is a space in the full name to split on. If there isn't then the first name is set to an empty string and the full name is put into the surname. Also, reverse is employed to split on the last space when there is more than one space
I use this query to retrieve first and lastname
SELECT
SUBSTRING(FULLNAME, 1, CASE WHEN CHARINDEX(' ', FULLNAME)>0 THEN CHARINDEX(' ', FULLNAME) - 1 ELSE LEN(FULLNAME) END ) AS Firstname,
REVERSE(SUBSTRING(REVERSE(FULLNAME), 1, CASE WHEN CHARINDEX(' ', REVERSE(FULLNAME))>0 THEN CHARINDEX(' ', REVERSE(FULLNAME)) - 1 ELSE LEN(REVERSE(FULLNAME)) END ) ) AS Firstname
FROM HRMDESFO.EMPLOID
Results
BigQuery: Standard SQL
substr(name,1,STRPOS(name,' ')-1) as FirstName,
substr(name,STRPOS(name,' ')+1,length(name)) as LastName
This is the easiest and shortest to this question without any assumptions. Also you can even further enhance this with a rtrim(ltrim('firstname lastname')).
Just in case of any spaces before the strings,
Select
substring('Firstname Lastname',1,CHARINDEX(' ', 'Firstname Lastname')) as firstname,
substring('Firstname Lastname',CHARINDEX(' ', 'Firstname Lastname'),LEN('Firstname Lastname')) as Lastname
select passemail,substring(passemail,1,instr(passemail,'#') - 1) as name ,
substring(passemail,instr(passemail,'#') + 1,length(passemail)) from passenger
For getting firstName
SELECT SUBSTR(FULLNAME,1,(LOCATE(' ',FULLNAME))) AS FIRSTTNAME from EmployeeDetails;
FOR LASTNAME
SELECT SUBSTR(FULLNAME,(LOCATE(' ',FULLNAME))) AS LASTNAME from EmployeeDetails;
SO
SELECT SUBSTR(FULLNAME,1,(LOCATE(' ',FULLNAME))) AS FIRSTTNAME, SUBSTR(FULLNAME,(LOCATE(' ',FULLNAME))) AS LASTNAME from EmployeeDetails;
SELECT
LEFT(column_name, POSITION(' ' IN column_name)-1) AS first_name,
RIGHT(column_name, LENGTH(column_name) - POSITION(' ' IN column_name)) AS last_name
FROM table_name
SELECT SUBSTRING(candidate_name, 1, CASE WHEN CHARINDEX(' ', candidate_name)>0 THEN CHARINDEX(' ', candidate_name) - 1
ELSE LEN(candidate_name) END ) AS Firstname,
SUBSTRING(substring(candidate_name,CHARINDEX(' ', candidate_name)+1,LEN(candidate_name)), 1,
CASE WHEN CHARINDEX(' ', candidate_name)>1 THEN CHARINDEX(' ', substring(candidate_name,CHARINDEX(' ', candidate_name)+1,LEN(candidate_name)))
ELSE null END ) AS middle_name,
REVERSE(SUBSTRING(REVERSE(candidate_name), 1,
CASE WHEN CHARINDEX(' ', REVERSE(candidate_name))>0
THEN CHARINDEX(' ', REVERSE(candidate_name)) - 1
ELSE null END ) )AS last_name
FROM Test_name
So first we have to find the index for space(" ") because space is the character which is separating the two words ( first_name+" "+last_name).
In my case, its mid_index is a variable that stores the index for space(" ").
SELECT primary_poc, STRPOS(fullname,' ') AS "mid_index"
FROM yourTable_name
*Now we will use min_index to find the left and right sides of words. For this, we can use a subquery.
Below is the final query *
SELECT fullname,
LEFT(fullname, mid_index - 1) AS "first_name",
RIGHT(fullname, LENGTH(primary_poc) - mid_index) AS "last_name"
FROM
(
SELECT primary_poc, STRPOS(fullname,' ') AS "mid_index"
FROM yourTable_name
) AS t1
SELECT
SUBSTR(NAME,1,(LOCATE(NAME, ' '))) AS FIRSTTNAME
, SUBSTR(NAME,(LOCATE(NAME, ' ')+1)) AS LASTNAME
FROM yourTABLE;
FOR SQL SERVER
SELECT
SUBSTRING(fullname, 0, CHARINDEX(' ', fullname)) AS FirstName
,SUBSTRING(fullname, CHARINDEX(' ', fullname), LEN(fullname)) AS LastName
FROM [YourTable]
If your full name has another delimiter aside from space, such as dashes, you substitute in the dash e.g
SELECT
SUBSTRING(fullname, 0, CHARINDEX('-', fullname)) AS FirstName
,SUBSTRING(fullname, CHARINDEX('-', fullname), LEN(fullname)) AS LastName
FROM [YourTable]
In Postgres SQL
SELECT fullname,
SUBSTRING(fullname, 1, POSITION(' ' IN fullname) - 1) as first_name ,
SUBSTRING(fullname,(position(' 'in fullname))) AS lastname from details;
You can use,
STRING_SPLIT (string , separator)
I've tried many different ways to pivot a table to show all records on 1 row. I've provided my query for the closest solution I came up with. It'll probably be easier if I illustrate what I need. Since there can be an unlimited number of teacher survey questions the query has to be dynamic. I've modified the column names to make it easier to read.
teacherSurveyQuestions
TSQID CID Order OQReference Stem
1 1011 1 q1_rb blabla
2 1011 2 q2_rb blabla
3 1011 3 q2a_cb blabla
teacherSurveyUserID
TSUID firstName lastName UID
1 Bob Smith 1027
2 Tom Jones 1034
teacherSurveyAnswers
TSAID UID TSQID TSUID Response
1 1027 1 1 Bob 1
2 1027 2 1 Bob 2
3 1027 3 1 Bob 3
4 1034 1 2 Tom 1
5 1034 2 2 Tom 2
6 1034 3 2 Tom 3
Now I need this data to look like this:
firstName lastName q1_rb q2_rb q2a_cb
Bob Smith Bob 1 Bob 2 Bob 3
Tom Jones Tom 1 Tom 2 Tom 3
Here's what I have so far that kind of works except all the responses are NULL
declare #query as nvarchar(max),
#colsPivot as nvarchar(max)
select #colsPivot = stuff((select ','
+ quotename(OQReference)
from teacherSurveyQuestions tsq
where tsq.CID = 1011
order by tsq.Order
for xml path(''), type
).value('.', 'nvarchar(max)')
,1,1,'')
set #query
= 'select *
from
(
select firstName, lastName, value, col +''_''+ CAST(rn as varchar(10)) as col
from
(
select
tsu.TSUID
,tsu.firstName
,tsu.lastName
,tsq.OQReference
,tsa.Response
,ROW_NUMBER() over(partition by tsu.TSUID order by tsq.Order) rn
from teacherSurveyQuestions tsq
inner join teacherSurveyAnswers tsa on tsa.TSQID = tsq.TSQID
inner join teacherSurveyUsers tsu on tsu.TSUID = tsa.TSUID
where tsq.CID = 1011
) x
unpivot
(
value
for col in (OQReference)
) u
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
Result of query:
firstName lastName q1_rb q2_rb q2a_cb
Bob Smith NULL NULL NULL
Tom JOnes NULL NULL NULL
Try this
declare #query as nvarchar(max),
#colsPivot as nvarchar(max)
select #colsPivot = stuff((select ','
+ quotename(OQReference)
from teacherSurveyQuestions tsq
where tsq.CID = 1011
order by tsq.[Order]
for xml path(''), type
).value('.', 'nvarchar(max)')
,1,1,'')
set #query
= 'select firstName, lastName,'+ #colspivot +'
from
(
select firstName, lastName,Response, value
from
(
select
tsu.TSUID
,tsu.firstName
,tsu.lastName
,tsq.OQReference
,tsa.Response
,ROW_NUMBER() over(partition by tsu.TSUID order by tsq.[Order]) rn
from teacherSurveyQuestions tsq
inner join teacherSurveyAnswers tsa on tsa.TSQID = tsq.TSQID
inner join teacherSurveyUserID tsu on tsu.TSUID = tsa.TSUID
where tsq.CID = 1011
) x
unpivot
(
value
for col in (OQReference)
) u
) x1
pivot
(
max(Response)
for value in ('+ #colspivot +')
) p'
exec(#query)
SQL FIDDLE DEMO