I will try and keep this as concise and easy to understand as possible. I have a dataset which includes a large number of names, some are distinct, however some are not and all names have a corresponding reference number. Where the names are not distinct, I want to create a query that will display a distinct list all of names in that table, and have seperate columns that list listing the reference numbers of the names in the original dataset. Is this at all possible using SQL? I was thinking a PIVOT clause might be required, but not sure that would be appropriate
Like below;
Current Dataset
FullName
Reference
Joe Bloggs
T1234567
Joe Bloggs
T3456789
John Smith
T1234568
Sarah Edwards
T1234567
Karen Culford
T0999221
Sarah Edwards
T0239222
Joe Bloggs
T2045292
Desired Outcome
FullName
Reference1
Reference2
Reference3
Joe Bloggs
T1234567
T3456789
T2045292
John Smith
T1234568
NULL
NULL
Sarah Edwards
T1234567
T0239222
NULL
Karen Culford
T0999221
NULL
NULL
If the number of pivot columns is unknown, you'd need dynamic sql (which has both pros and cons). Using this example as a base, first build a comma separated list of column names "Reference1,Reference2,....".
SQL Server 2017+
DECLARE #colList AS NVARCHAR(MAX)
, #query AS NVARCHAR(MAX);
; WITH colsByName AS (
-- count how many columns per fullName
SELECT FullName
, Reference
, ROW_NUMBER() over(PARTITION BY Fullname ORDER BY Reference) AS ColNum
FROM YourTable
)
, uniqueColumns AS
(
-- get unique column numbers
SELECT DISTINCT ColNum
FROM colsByName
)
-- build comma separated list of names
SELECT #colList = STRING_AGG('Reference'+ CONVERT(VARCHAR, ColNum), ',')
FROM uniqueColumns
;
Note, for SQL Server 2016 use STUFF instead of STRING_AGG
...
-- build comma separated list of names
SELECT #colList = STUFF((
SELECT ',' + 'Reference'+ CONVERT(VARCHAR, ColNum)
FROM uniqueColumns
ORDER BY ColNum
FOR XML PATH('')
)
,1,1,'')
;
Then use it to build a dynamic PIVOT statement:
SET #query = 'SELECT FullName, ' + #colList + '
FROM (
SELECT FullName
, Reference
, ''Reference''+ CONVERT(VARCHAR, ROW_NUMBER() over(PARTITION BY Fullname ORDER BY Reference)) AS ColNum
FROM YourTable
) x
PIVOT
(
MAX(Reference)
FOR ColNum IN (' + #colList + ')
) p ';
EXECUTE(#query);
See also
SQL Server 2017+ Example - db<>fiddle
SQL Server 2016 Example - db<>fiddle
Results
FullName
Reference1
Reference2
Reference3
Joe Bloggs
3456789
T1234567
T2045292
John Hart
John Smith
T1234568
Karen Culford
T0999221
Sarah Edwards
T0239222
T1234567
Related
I need to be able to take a column from a SQL table and write a query that will output this in a sentence format.
example
column A
---------
Abraham
Jones
Henry
Walter
output would look like this
Abraham, Jones, Henry, Walter
If you are using SQL Server 2017 or above:
DECLARE #Table TABLE( ColumnName VARCHAR(25))
INSERT INTO #Table VALUES ('Abraham'),('Jones'),('Henry'),('Walter')
SELECT STRING_AGG(ColumnName,', ') sentence
FROM #Table
For SQL Server versions below 2017, use the For Xml Path approach:
SELECT STUFF((SELECT ', ' + ColumnName
FROM #Table t1
FOR XML PATH (''))
, 1, 1, '')
This Stackoverflow post explains more. Good luck!
Need SQL logic
I have table with Column name EID, Emp_name, 1,2,3,4,5,6,7....till 31 (Column 1 to 31 are calendar dates)
now i m trying to fetch only 3 column EID, EMP_name and date which is equals to sys date.
Example
Todays SYS_date is 2nd-Jan-2019
and i need column with value = 2 like this...
EID Emp_name 2 |
123 James SCOTT P |
133 Mark M A |
133 Mark Man P |
Try like this:
declare #sql nvarchar(max)
set #sql = 'select EID, Emp_name, [' + convert(nvarchar(2),day(getdate())) + '] as d from tableName '
exec(#sql)
One method to approach this is to unpivot the data. Here is one method:
select t.eid, t.emp_name, v.n
from t cross apply
(values ([1]), ([2]), ([3]), . . ., ([4])
) v(n)
where v.n = day(getdate());
I will caution that the column name is a fixed name, not 2. SQL queries have fixed column names. You would need to use dynamic sql to get a variable column name.
How can I change this table
Name subject Mark
Aswin physics 100
Aswin chemistry 300
Aswin maths 200
Into
Aswin Physics 100 Chemistry 300 Maths 200
Any one please help me.
you can use PIVOT operator to do this job in sql server.
check these links link1 and link2 they will show how to change row into column.
hope this helps you!
SQLFiddle demo
select Name,
sum(CASE
when [subject]='physics' then Mark
end) as Physics,
sum(CASE
when [subject]='chemistry' then Mark
end) as chemistry,
sum(CASE
when [subject]='maths' then Mark
end) as maths
from t group by Name
Or if you need it in one line:
SQLFiddle demo
SELECT
t1.name,
MemberList = substring((SELECT ( ', ' + subject+' - '+
cast(Mark as varchar(100)) )
FROM t t2
WHERE t1.name = t2.name
ORDER BY
name,
subject
FOR XML PATH( '' )
), 3, 1000 )FROM t t1
GROUP BY name
You need to use SQL Pivoting, check the examples at SQL SERVER – PIVOT and UNPIVOT Table Examples. Using Sql Pivoting you can change the rows to columns and Unpivoting is for columns to rows conversion.
Please note: I am checking if I can provide you exact script but for now the link would help you out.
UPDATE
Code example
Though I have not tested this with actual data but it parses fine.
-- Pivot Table ordered by Name of Student
SELECT Name, Physics, Chemistry, Maths
FROM (
SELECT Name, Subject, Mark
FROM Student) up
PIVOT (SUM(Mark) FOR Student IN (Physics, Chemistry, Maths)) AS pvt
ORDER BY Name
-- Result should be something like
----------------------------------
Name Physics Chemistry Maths
----------------------------------
Aswin 100 300 200
----------------------------------
For creating pivot you need to know the actual rows values to convert into columns.
I have wrote before about dynamic pivoting here if you find it useful.
It is not exactly clear if you want this data in separate columns or in one column.
If you want this in separate columns, then you can apply the PIVOT function which became available in SQL Server 2005.
If you know all of the values that you want to transform or have a limited number, then you can hard-code the query:
select *
from
(
select name, subject +' '+ cast(mark as varchar(9)) as sub_mark,
'Subject_'+cast(row_number() over(partition by name
order by subject) as varchar(10)) col_name
from subjects
) s
pivot
(
max(sub_mark)
for col_name in (Subject_1, Subject_2, Subject_3)
) piv;
See SQL Fiddle with Demo. You will notice that I did this slightly different from the other pivot answer. I placed both the subject/mark in the same column with a column name of Subject_1, etc.
If you have an unknown number of values, then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Subject_'+cast(row_number() over(partition by name
order by subject) as varchar(10)))
from subjects
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name,' + #cols + ' from
(
select name, subject +'' ''+ cast(mark as varchar(9)) as sub_mark,
''Subject_''+cast(row_number() over(partition by name
order by subject) as varchar(10)) col_name
from subjects
) x
pivot
(
max(sub_mark)
for col_name in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. The dynamic sql version will increase in the number of columns if a name has more than 3 subjects.
The result of both queries is:
| NAME | SUBJECT_1 | SUBJECT_2 | SUBJECT_3 |
---------------------------------------------------
| Aswin | chemistry 300 | maths 200 | physics 100 |
I have a SQL Table with a column called FullName which contains, for example, "John Smith".
How can order the data by the last name that appears in the FullName column?
For a long name like "Laurence John Fishburne", I would like to order the data by the word "Fishburne".
Thus, names are stored in the order
First Name
Middle Names
Last Name
I am using Microsoft SQL Server 2005.
I would do something like:
SELECT FullName
FROM TABLE
ORDER BY REVERSE(SUBSTRING(REVERSE(FullName), 0, CHARINDEX(' ', REVERSE(FullName))))
Instead of calculating what the last name is each time you want to run the query, you can have a computed column that persists the actual value into a column that can be used as you would any other column.
ALTER TABLE Customer
ADD LastName AS
RIGHT(FullName, CHARINDEX(' ', REVERSE(FullName)) - 1) PERSISTED
This Adds the column LastName to your Customer table, uses the function specified to calculate the value of the last name, and stores it onto the physical table. The keyword PERSISTED at the end is required for the value to be saved to the disk, else it will be computed each time a query is run.
Edit:
To deal with values with no spaces:
ALTER TABLE Customer
ADD LastName AS
case when CHARINDEX(' ', REVERSE(FullName)) > 0
then RIGHT(FullName, CHARINDEX(' ', REVERSE(FullName)) - 1)
else
FullName
end
PERSISTED
Though you can fiddle with this function as you will to determine what happens in this case. My point is to show that case statements can be used. You may also want to cast all output paths to the same type to avoid type mismatches.
try this, it uses the minimal number of functions to find the last space in the FullName string, which should help performance:
DECLARE #YourTable table (FullNamevarchar(30))
INSERT #YourTable VALUES ('Harry Smith');
INSERT #YourTable VALUES ('John Davis');
INSERT #YourTable VALUES ('Allision Thomas Williams');
SELECT
FullName
,RIGHT(FullName, CHARINDEX(' ', REVERSE(FullName)) - 1) AS SortBy
FROM #YourTable
ORDER BY RIGHT(FullName, CHARINDEX(' ', REVERSE(FullName)) - 1)
OUTPUT:
FullName SortBy
------------------------------ ------------------------------
John Davis Davis
Harry Smith Smith
Allision Thomas Williams Williams
(3 row(s) affected)
For the best performance and most accurate sort, you need to split out the name into Firstname, MiddleName, and LastName fields. Only the user entering the data can really understand what portion of FullName is the last name.
Query
SELECT stringrowname FROM tablename
ORDER BY SUBSTRING_INDEX((stringrowname)," ",-1);
It will return the strings order by last word.
create table foo(fullname varchar(100))
go
insert into foo values ('Harry Smith');
insert into foo values ('John Davis');
insert into foo values ('Allision Thomas Williams');
SELECT fullname
, REVERSE(left(REVERSE(fullname), charindex(' ',REVERSE(fullname))-1))
FROM foo
ORDER BY REVERSE(left(REVERSE(fullname), charindex(' ',REVERSE(fullname))-1))
Output:
fullname (No column name)
John Davis Davis
Harry Smith Smith
Allision Thomas Williams Williams
When in doubt, do it yourself:
Select
*
From
Users
Order By
LTrim(Reverse(Left(Reverse(FullName), CharIndex(' ', Reverse(FullName))))) Asc,
FullName Asc -- Fall-over for when there isn't a last name
This will do the trick:
create table #tmpTable
(
ID int identity(1,1) primary key,
FullName varchar(100) not null
);
insert into #tmpTable(FullName) values('Big John Sansom');
insert into #tmpTable(FullName) values('Mike Douglas Reid');
insert into #tmpTable(FullName) values('First Second Last');
insert into #tmpTable(FullName) values('JustOneTokenForName');
select
FullName,
LastName = case
when CHARINDEX(FullName,' ') = 0 THEN FullName
else RIGHT(FullName, CHARINDEX(' ', REVERSE(FullName)) - 1)
end
from #tmpTable
order by LastName
drop table #tmpTable
It really depends on how names are stored. Assuming "Last, First Middle" you could do something like
order by substring(0, charindex(',', FullName))
You may have to fiddle with it a little, but I believe that should work.
I am using MS SQL 2005
I have a problem, which currently I am battling to get a solution.
I have a Table, with a these columns : NameList;Time
The Namelist Column has comma delimited data in it. The table data is as such:
Namelist Time
John Smith, Jeremy Boyle, Robert Brits, George Aldrich 5
John Smith, Peter Hanson 15
Jeremy Boyle, Robert Brits 10
....
I need some sort of SQL expression that will provide me with this end result:
Name Total_Time
John Smith 20
Jeremy Boyle 15
Robert Brits 15
Etc......
Basically the expression must find All the names in the rows and math those names with the names in the other rows and add the times together for each user.
The Idea I have is to convert the comma delimited data into rows and count the distinct records of each then somehow know what the time for it is... then multiply..... but I have no idea on how to implement it
Any Help would be much appreciated
Thanks,
I prefer the number table approach to split a string in TSQL
For this method to work, you need to do this one time table setup:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(#SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = #SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
);
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')
OUTPUT:
ListValue
-----------------------
1
2
3
4
5
6777
(6 row(s) affected)
Your can now use a CROSS APPLY to split every row in your table like:
DECLARE #YourTable table (NameList varchar(5000), TimeOf int)
INSERT INTO #YourTable VALUES ('John Smith, Jeremy Boyle, Robert Brits, George Aldrich', 5)
INSERT INTO #YourTable VALUES ('John Smith, Peter Hanson', 15)
INSERT INTO #YourTable VALUES ('Jeremy Boyle, Robert Brits', 10)
SELECT
st.ListValue AS NameOf, SUM(o.TimeOf) AS TimeOf
FROM #YourTable o
CROSS APPLY dbo.FN_ListToTable(',',o.NameList) AS st
GROUP BY st.ListValue
ORDER BY st.ListValue
OUTPUT:
NameOf TimeOf
----------------------- -----------
George Aldrich 5
Jeremy Boyle 15
John Smith 20
Peter Hanson 15
Robert Brits 15
(5 row(s) affected)
Using this, I would recommend that you alter your table design and use this output to INSERT into a new table. That would be a more normalized approach. Also Don't use reserved words for column names, it makes it a hassle. Notice how I use "NameOf" and "TimeOf", so I avoid using reserved words.
Either: Search for other answers to fix your data on the fly, slowly, and repeatedly
Or: Normalise. Why do you think normalisation exists and why people bang on about it?
You could create a table-valued function to split the namelist into many rows:
if object_id('dbo.fnSplitNamelist') is not null
drop function dbo.fnSplitNamelist
go
create function dbo.fnSplitNamelist(
#namelist varchar(max))
returns #names table (
name varchar(50))
as
begin
declare #start int
declare #end int
set #start = 0
while IsNull(#end,0) <> len(#namelist) + 1
begin
set #end = charindex(',', #namelist, #start)
if #end = 0
set #end = len(#namelist) + 1
insert into #names select ltrim(rtrim(
substring(#namelist,#start,#end-#start)))
set #start = #end + 1
end
return
end
go
You can use a cross apply to return the names for each namelist. Then you can use group by to sum the time per user:
declare #YourTable table (namelist varchar(1000), time int)
insert into #YourTable
select 'John Smith, Jeremy Boyle, Robert Brits, George Aldrich', 5
union all select 'John Smith, Peter Hanson', 15
union all select 'Jeremy Boyle, Robert Brits', 10
select fn.name, sum(t.time)
from #YourTable t
cross apply fnSplitNamelist(t.namelist) fn
group by fn.name
This results in:
George Aldrich 5
Jeremy Boyle 15
John Smith 20
Peter Hanson 15
Robert Brits 15
The best option is to normalise the data. Then it would be a lot easier to work with.
The second best option would be to use a recursive query to pick a name at a time from each name list and return as a list of separate names and their respecive time from each record, then use grouping to sum the times for each name.
No need for user defined functions or pre-created tables. ;)
with NameTime ([Name], [Time], Namelist)
as (
select cast(null as varchar(100)), [Time], Namelist
from NamelistTime
union all
select
case when Pos = 0 then NameList else substring(Namelist, 1, Pos - 1) end,
[Time],
case when Pos = 0 then null else substring(NameList, Pos + 2, len(Namelist) - Pos - 1) end
from (
select [Time], Namelist, Pos = charindex(', ', Namelist)
from NameTime
) x
where Namelist is not null
)
select [Name], sum([Time])
from NameTime
where [Name] is not null
group by [Name]
In contrast, working with normalised data, it would be as simple as:
select p.Name, sum(n.Time)
from NamelistTime n
inner join Person p on p.PersonId = n.PersonId
group by p.Name