How to order by last name on a full name column? - sql

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.

Related

Creating a distinct list of names with dynamic columns

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

Splitting a Full Name into First and Last Name

I have a list of customer whose name is given as a full name.
I want to create a function that takes the full name as parameter and returns the first and last name separately. If this is not possible I can have two separate functions one that returns the first name and the other that returns the last name. The full name list contains names that have a maximum of three words.
What I want is this:-
When a full name is composed of two words. The first one should be
the name and the second one should be the last name.
When a full name is composed of three words. The first and middle words should be the first name while the third word should be the last name.
Example:-
**Full Name**
John Paul White
Peter Smith
Ann Marie Brown
Jack Black
Sam Olaf Turner
Result:-
**First Name Last Name**
John Paul White
Peter Smith
Ann Marie Brown
Jack Black
Sam Olaf Turner
I have search and found solutions that are not working as intended and would like some advice.
Keeping it short and simple
DECLARE #t TABLE(Fullname varchar(40))
INSERT #t VALUES('John Paul White'),('Peter Smith'),('Thomas')
SELECT
LEFT(Fullname, LEN(Fullname) - CHARINDEX(' ', REVERSE(FullName))) FirstName,
STUFF(RIGHT(FullName, CHARINDEX(' ', REVERSE(FullName))),1,1,'') LastName
FROM
#t
Result:
FirstName LastName
John Paul White
Peter Smith
Thomas NULL
If you are certain that your names will only ever be two or three words, with single spaces, then we can rely on the base string functions to extract the first and last name components.
SELECT
CASE WHEN LEN(col) = LEN(REPLACE(col, ' ', '')) + 2
THEN SUBSTRING(col, 1,
CHARINDEX(' ', col, CHARINDEX(' ', col) + 1) - 1)
ELSE SUBSTRING(col, 1, CHARINDEX(' ', col) - 1)
END AS first,
CASE WHEN LEN(col) = LEN(REPLACE(col, ' ', '')) + 2
THEN SUBSTRING(col,
CHARINDEX(' ', col, CHARINDEX(' ', col) + 1) + 1,
LEN(col) - CHARINDEX(' ', col, CHARINDEX(' ', col)))
ELSE SUBSTRING(col,
CHARINDEX(' ', col) + 1,
LEN(col) - CHARINDEX(' ', col))
END AS last
FROM yourTable;
Yuck, but it seems to work. My feeling is that you should fix your data model at some point. A more ideal place to scrub your name data would be outside the database, e.g. in Java. Or, better yet, fix the source of your data such that you record proper first and last names from the very beginning.
Demo here:
Rextester
Another option (just for fun) is to use a little XML in concert with an CROSS APPLY
Example
Select FirstName = ltrim(reverse(concat(Pos2,' ',Pos3,' ',Pos4,' ',Pos5)))
,LastName = reverse(Pos1)
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)')
From (Select Cast('<x>' + replace(reverse(A.[Full Name]),' ','</x><x>')+'</x>' as xml) as xDim) XMLData
) B
Returns
FirstName LastName
John Paul White
Peter Smith
Ann Marie Brown
Jack Black
Sam Olaf Turner
Cher
Sally Anne Bella Donna Baxter
You're trying to do two things at once...I won't solve for you, but here's the direction I'd take:
1) Check this out for string splitting: https://ole.michelsen.dk/blog/split-string-to-table-using-transact-sql.html. This will allow you to parse the name into a temp table and you can perform your logic on it to create names based on your rules
2) Create this as a table-valued function so that you can return a single row of parsed FirstName, LastName from your parameter. That way you can join to it and include in your results
Have you tried by Using PARSENAME Function?
The last method in splitting a full name into its corresponding first name and last name is the use of the PARSENAME string function, as can be seen from the following script:
DECLARE #FullName VARCHAR(100)
SET #FullName = 'John White Doe'
SELECT CONCAT(PARSENAME(REPLACE(#FullName, ' ', '.'), 3),' ',PARSENAME(REPLACE(#FullName, ' ', '.'), 2)) AS [FirstName],
PARSENAME(REPLACE(#FullName, ' ', '.'), 1) AS [LastName]
For more information, Goto this Site
This is the output..
Make it a table-valued function.
see here for an example
And this is the code you need to create your function. Basically you just need to split your LastName
IF OBJECT_ID(N'dbo.ufnParseName', N'TF') IS NOT NULL
DROP FUNCTION dbo.ufnParseName;
GO
CREATE FUNCTION dbo.ufnParseName(#FullName VARCHAR(300))
RETURNS #retParseName TABLE
(
-- Columns returned by the function
FirstName nvarchar(150) NULL,
LastName nvarchar(50) NULL
)
AS
-- Returns the spliced last name.
BEGIN
DECLARE
#FirstName nvarchar(250),
#LastName nvarchar(250);
-- Get common contact information
SELECT #LastName = RTRIM(RIGHT(#FullName, CHARINDEX(' ', REVERSE(#FullName)) - 1));
SELECT #FirstName = LTRIM(RTRIM(Replace(#FullName, #LastName, '')))
INSERT #retParseName
SELECT #FirstName, #LastName;
RETURN;
END
You can run as SELECT * FROM dbo.ufnParseName('M J K');
Why Table-Valued-Function
You can get rid off the duplication of your sql query and achieve DRY
You can try the below query. It is written as per your requirement and it only handles full_name with 2 or 3 parts in it.
;WITH cte AS(
SELECT full_name, (LEN(full_name) - LEN(REPLACE(full_name, ' ', '')) + 1) AS size FROM #temp
)
SELECT FirstName =
CASE
WHEN size=3 THEN PARSENAME(REPLACE(full_name, ' ', '.'), 3) + ' ' + PARSENAME(REPLACE(full_name, ' ', '.'), 2)
ELSE PARSENAME(REPLACE(full_name, ' ', '.'), 2)
END,
PARSENAME(REPLACE(full_name, ' ', '.'), 1) AS LastName
FROM cte

deleting second comma in data

Ok so I have a table called PEOPLE that has a name column. In the name column is a name, but its totally a mess. For some reason its not listed such as last, first middle. It's sitting like last,first,middle and last first (and middle if there) are separated by a comma.. two commas if the person has a middle name.
example:
smith,steve
smith,steve,j
smith,ryan,tom
I'd like the second comma taken away (for parsing reason ) spaces put after existing first comma so the above would come out looking like:
smith, steve
smith, steve j
smith, ryan tom
Ultimately I'd like to be able to parse the names into first, middle, and last name fields, but that's for another post :_0. I appreciate any help.
thank you.
Drop table T1;
Create table T1(Name varchar(100));
Insert T1 Values
('smith,steve'),
('smith,steve,j'),
('smith,ryan,tom');
UPDATE T1
SET Name=
CASE CHARINDEX(',',name, CHARINDEX(',',name)+1) WHEN
0 THEN Name
ELSE
LEFT(name,CHARINDEX(',',name, CHARINDEX(',',name)+1)-1)+' ' +
RIGHT(name,LEN(Name)-CHARINDEX(',',name, CHARINDEX(',',name)+1))
END
Select * from T1
This seems to work. Not the most concise but avoids cursors.
DECLARE #people TABLE (name varchar(50))
INSERT INTO #people
SELECT 'smith,steve'
UNION
SELECT 'smith,steve,j'
UNION
SELECT 'smith,ryan,tom'
UNION
SELECT 'commaless'
SELECT name,
CASE
WHEN CHARINDEX(',',name) > 0 THEN
CASE
WHEN CHARINDEX(',',name,CHARINDEX(',',name) + 1) > 0 THEN
STUFF(STUFF(name, CHARINDEX(',',name,CHARINDEX(',',name) + 1), 1, ' '),CHARINDEX(',',name),1,', ')
ELSE
STUFF(name,CHARINDEX(',',name),1,', ')
END
ELSE name
END AS name2
FROM #people
Using a table function to split apart the names with a delimiter and for XML Path to stitch them back together, we can get what you're looking for! Hope this helps!
Declare #People table(FullName varchar(200))
Insert Into #People Values ('smith,steve')
Insert Into #People Values ('smith,steve,j')
Insert Into #People Values ('smith,ryan,tom')
Insert Into #People Values ('smith,john,joseph Jr')
Select p.*,stuff(fn.FullName,1,2,'') as ModifiedFullName
From #People p
Cross Apply (
select
Case When np.posID<=2 Then ', ' Else ' ' End+np.Val
From #People n
Cross Apply Custom.SplitValues(n.FullName,',') np
Where n.FullName=p.FullName
For XML Path('')
) fn(FullName)
Output:
ModifiedFullName
smith, steve
smith, steve j
smith, ryan tom
smith, john joseph Jr
SplitValues table function definition:
/*
This Function takes a delimited list of values and returns a table containing
each individual value and its position.
*/
CREATE FUNCTION [Custom].[SplitValues]
(
#List varchar(max)
, #Delimiter varchar(1)
)
RETURNS
#ValuesTable table
(
posID int
,val varchar(1000)
)
AS
BEGIN
WITH Cte AS
(
SELECT CAST('<v>' + REPLACE(#List, #Delimiter, '</v><v>') + '</v>' AS XML) AS val
)
INSERT #ValuesTable (posID,val)
SELECT row_number() over(Order By x) as posID, RTRIM(LTRIM(Split.x.value('.', 'VARCHAR(1000)'))) AS val
FROM Cte
CROSS APPLY val.nodes('/v') Split(x)
RETURN
END
GO
String manipulation in SQLServer, outside of writing your own User Defined Function, is limited but you can use the PARSENAME function for your purposes here. It takes a string, splits it on the period character, and returns the segment you specify.
Try this:
DECLARE #name VARCHAR(100) = 'smith,ryan,tom'
SELECT REVERSE(PARSENAME(REPLACE(REVERSE(#name), ',', '.'), 1)) + ', ' +
REVERSE(PARSENAME(REPLACE(REVERSE(#name), ',', '.'), 2)) +
COALESCE(' ' + REVERSE(PARSENAME(REPLACE(REVERSE(#name), ',', '.'), 3)), '')
Result: smith, ryan tom
If you set #name to 'smith,steve' instead, you'll get:
Result: smith, steve
Segment 1 actually gives you the last segment, segment 2 the second to last etc. Hence I've used REVERSE to get the order you want. In the case of 'steve,smith', segment 3 will be null, hence the COALESCE to add an empty string if that is the case. The REPLACE of course changes the commas to periods so that the split will work.
Note that this is a bit of a hack. PARSENAME will not work if there are more than four parts and this will fail if the name happens to contain a period. However if your data conforms to these limitations, hopefully it provides you with a solution.
Caveat: it sounds like your data may be inconsistently formatted. In that case, applying any automated treatment to it is going to be risky. However, you could try:
UPDATE people SET name = REPLACE(name, ',', ' ')
UPDATE people SET name = LEFT(name, CHARINDEX(' ', name)-1)+ ', '
+ RIGHT(name, LEN(name) - CHARINDEX(' ', name)
That'll work for the three examples you give. What it will do to the rest of your set is another question.
Here's an example with CHARINDEX() and SUBSTRING
WITH yourTable
AS
(
SELECT names
FROM
(
VALUES ('smith,steve'),('smith,steve,j'),('smith,ryan,tom')
) A(names)
)
SELECT names AS old,
CASE
WHEN comma > 0
THEN SUBSTRING(spaced_names,0,comma + 1) --before the comma
+ SUBSTRING(spaced_names,comma + 2,1000) --after the comma
ELSE spaced_names
END AS new
FROM yourTable
CROSS APPLY(SELECT CHARINDEX(',',names,CHARINDEX(',',names) + 1),REPLACE(names,',',', ')) AS CA(comma,spaced_names)

How to split a row into two strings and copy the second string in a separate column in SQL?

I have a column which is named "Firstname" and contains
Firstname Lastname
and I want to create another column called Lastname and cut the Lastname from the "Firstaname" column and paste it on the "Lastname" column, for multiple rows.
before
Firstname
---------
Bob Weller
after
Firstname | Lastname
----------|---------
Bob | Weller
edit:
after Lastname there might be a number or other strings such as ( ) etc.. which should go to the Lastname column
This query splits it into FirstName and LastName at the first space.
SELECT FirstNameLastName,
substring(FirstNameLastName, 0, charindex(' ', FirstNameLastName) -1) as FirstName,
substring(FirstNameLastName, charindex(' ', FirstNameLastName) -1,len(FirstNameLastName)) as LastName
from [Table]
To modify your table and move the old column into new columns, you can do the following:
Alter your table first to create your new columns:
Alter Table [TableName]
Add FirstName varchar(50),
LastName Varchar(50)
Then use an update statement to move the values into it
Update Table [TableName]
Set FirstName = substring(FirstNameLastName, 0, charindex(' ', FirstNameLastName)-1),
LastName = substring(FirstNameLastName, charindex(' ', FirstNameLastName)-1,len(FirstNameLastName))
This should move the values from the old column into the two new columns.
The following is similar to the accepted answer, except:
- It deals with strings that don't contain a space
- It uses OUTER APPLY to avoid re-writing the CHARINDEX multiple times
- It doesn't include the space as the last character of the new first name
- It doesn't include the space as the first character of the new last name
SELECT
*,
CASE
WHEN lookup.first_space = 0 THEN ''
ELSE SUBSTRING(yourTable.firstName, 1, lookup.first_space - 1)
END AS new_firstName,
CASE
WHEN lookup.first_space = 0 THEN yourTable.firstName
ELSE SUBSTRING(yourTable.firstName, lookup.first_space + 1, 9999)
END AS new_lastName
FROM
yourTable
OUTER APPLY
(SELECT CHARINDEX(' ', yourTable.firstName, 0) AS first_space) AS lookup
If you want then split into 2 columns not rows then something like this should do:
SELECT
SUBSTRING(WholeName, 1, CHARINDEX(' ', WholeName) - 1) AS Firstname,
SUBSTRING(WholeName, CHARINDEX(' ', WholeName) + 1, len(FirstName)) AS LastName
FROM MyTable
NOTE: This is assuming that EVERY record has a space between first and last names.

set column value to part of a string in another column

i have a table with a column named CustomerName which stores a customer's full name(thats both names of a customer - first name, last name and any other names).
I want to redesign this table such that instead of just having CustomerName field, i should have CustomerFirstName, CustomerLastName and CustomerOtherNames(then customerName can be a concatenation of the 3 fields).
i already have customer records in the table with CustomerNames in format below
CustomerName
Tom John
Mary Joy
San Roy
now i need to run an update query that will set Tom, Mary and San as CustomerFirstName and set John, Joy and Roy as CustomerLastName for their respective rows but am stuck on this.
The following integrates the formulas suggested by Matt (Lima) into the actual UPDATE query; it also deals with various cases such as
when there is only a FirstName,
when there are leading or trailing spaces
when there's more than one space separating first name from last name
Alternatively, one may do away with the extra tests aimed at ensuring that there is a space, by adding a WHERE clause (WHERE CustomerName like ('% %').
-- Ensure no leading nor trailing spaces
UPDATE myTable
SET CustomerName = TRIM(CustomerName)
UPDATE myTable
SET FirstName = TRIM(LEFT(TRIM(CustomerName),
CASE CHARINDEX(' ', CustomerName)
WHEN 0 THEN LEN(CustomerName)
ELSE CHARINDEX(' ', CustomerName) -1
END)),
LastName = CASE CHARINDEX(' ', CustomerName)
WHEN 0 THEN ''
ELSE TRIM(SUBSTRING(CustomerName, CHARINDEX(' ', CustomerName) + 1, LEN(CustomerName))
END
You might be able to use something like:
SELECT SUBSTRING(CustomerName, 1, CHARINDEX(' ', CustomerName) - 1) AS [FirstName],
SUBSTRING(CustomerName, CHARINDEX(' ', CustomerName) + 1, LEN(CustomerName)) AS [LastName]
FROM yourTableName
I got this solution from: http://www.sql-server-helper.com/tips/split-name.aspx
Hope this helps.
Matt
Try something like:
SELECT SUBSTR(CustomerName, 1 ,INSTR(CustomerName, ' ', 1, 1)-1) as first_name, SUBSTR(CustomerName, INSTR(CustomerName,' ',1,1)) as last_name from yourTableName
(oracle)