Changing lowercase to uppercase on specific characters - SQL Server [duplicate] - sql

I have a table that was imported as all UPPER CASE and I would like to turn it into Proper Case. What script have any of you used to complete this?

This function:
"Proper Cases" all "UPPER CASE" words that are delimited by white space
leaves "lower case words" alone
works properly even for non-English alphabets
is portable in that it does not use fancy features of recent SQL server versions
can be easily changed to use NCHAR and NVARCHAR for unicode support,as well as any parameter length you see fit
white space definition can be configured
CREATE FUNCTION ToProperCase(#string VARCHAR(255)) RETURNS VARCHAR(255)
AS
BEGIN
DECLARE #i INT -- index
DECLARE #l INT -- input length
DECLARE #c NCHAR(1) -- current char
DECLARE #f INT -- first letter flag (1/0)
DECLARE #o VARCHAR(255) -- output string
DECLARE #w VARCHAR(10) -- characters considered as white space
SET #w = '[' + CHAR(13) + CHAR(10) + CHAR(9) + CHAR(160) + ' ' + ']'
SET #i = 1
SET #l = LEN(#string)
SET #f = 1
SET #o = ''
WHILE #i <= #l
BEGIN
SET #c = SUBSTRING(#string, #i, 1)
IF #f = 1
BEGIN
SET #o = #o + #c
SET #f = 0
END
ELSE
BEGIN
SET #o = #o + LOWER(#c)
END
IF #c LIKE #w SET #f = 1
SET #i = #i + 1
END
RETURN #o
END
Result:
dbo.ToProperCase('ALL UPPER CASE and SOME lower ÄÄ ÖÖ ÜÜ ÉÉ ØØ ĈĈ ÆÆ')
-----------------------------------------------------------------
All Upper Case and Some lower Ää Öö Üü Éé Øø Cc Ææ

Here's a UDF that will do the trick...
create function ProperCase(#Text as varchar(8000))
returns varchar(8000)
as
begin
declare #Reset bit;
declare #Ret varchar(8000);
declare #i int;
declare #c char(1);
if #Text is null
return null;
select #Reset = 1, #i = 1, #Ret = '';
while (#i <= len(#Text))
select #c = substring(#Text, #i, 1),
#Ret = #Ret + case when #Reset = 1 then UPPER(#c) else LOWER(#c) end,
#Reset = case when #c like '[a-zA-Z]' then 0 else 1 end,
#i = #i + 1
return #Ret
end
You will still have to use it to update your data though.

UPDATE titles
SET title =
UPPER(LEFT(title, 1)) +
LOWER(RIGHT(title, LEN(title) - 1))
http://sqlmag.com/t-sql/how-title-case-column-value

If you can enable the CLR in SQL Server (requires 2005 or later) then you could create a CLR function that uses the TextInfo.ToTitleCase built-in function which would allow you to create a culture-aware way of doing this in only a few lines of code.

I know this is late post in this thread but, worth looking. This function works for me ever time. So thought of sharing it.
CREATE FUNCTION [dbo].[fnConvert_TitleCase] (#InputString VARCHAR(4000) )
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #OutputString VARCHAR(255)
SET #OutputString = LOWER(#InputString)
SET #Index = 2
SET #OutputString = STUFF(#OutputString, 1, 1,UPPER(SUBSTRING(#InputString,1,1)))
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
IF #Char IN (' ', ';', ':', '!', '?', ',', '.', '_', '-', '/', '&','''','(')
IF #Index + 1 <= LEN(#InputString)
BEGIN
IF #Char != ''''
OR
UPPER(SUBSTRING(#InputString, #Index + 1, 1)) != 'S'
SET #OutputString =
STUFF(#OutputString, #Index + 1, 1,UPPER(SUBSTRING(#InputString, #Index + 1, 1)))
END
SET #Index = #Index + 1
END
RETURN ISNULL(#OutputString,'')
END
Test calls:
select dbo.fnConvert_TitleCase(Upper('ÄÄ ÖÖ ÜÜ ÉÉ ØØ ĈĈ ÆÆ')) as test
select dbo.fnConvert_TitleCase(upper('Whatever the mind of man can conceive and believe, it can achieve. – Napoleon hill')) as test
Results:

I am a little late in the game, but I believe this is more functional and it works with any language, including Russian, German, Thai, Vietnamese etc.
It will make uppercase anything after ' or - or . or ( or ) or space (obviously :).
CREATE FUNCTION [dbo].[fnToProperCase]( #name nvarchar(500) )
RETURNS nvarchar(500)
AS
BEGIN
declare #pos int = 1
, #pos2 int
if (#name <> '')--or #name = lower(#name) collate SQL_Latin1_General_CP1_CS_AS or #name = upper(#name) collate SQL_Latin1_General_CP1_CS_AS)
begin
set #name = lower(rtrim(#name))
while (1 = 1)
begin
set #name = stuff(#name, #pos, 1, upper(substring(#name, #pos, 1)))
set #pos2 = patindex('%[- ''.)(]%', substring(#name, #pos, 500))
set #pos += #pos2
if (isnull(#pos2, 0) = 0 or #pos > len(#name))
break
end
end
return #name
END
GO

If you're in SSIS importing data that has mixed cased and need to do a lookup on a column with proper case, you'll notice that the lookup fails where the source is mixed and the lookup source is proper. You'll also notice you can't use the right and left functions is SSIS for SQL Server 2008r2 for derived columns. Here's a solution that works for me:
UPPER(substring(input_column_name,1,1)) + LOWER(substring(input_column_name, 2, len(input_column_name)-1))

Here is a version that uses a sequence or numbers table rather than a loop. You can modify the WHERE clause to suite your personal rules for when to convert a character to upper case. I have just included a simple set that will upper case any letter that is proceeded by a non-letter with the exception of apostrophes. This does how ever mean that 123apple would have a match on the "a" because "3" is not a letter. If you want just white-space (space, tab, carriage-return, line-feed), you can replace the pattern '[^a-z]' with '[' + Char(32) + Char(9) + Char(13) + Char(10) + ']'.
CREATE FUNCTION String.InitCap( #string nvarchar(4000) ) RETURNS nvarchar(4000) AS
BEGIN
-- 1. Convert all letters to lower case
DECLARE #InitCap nvarchar(4000); SET #InitCap = Lower(#string);
-- 2. Using a Sequence, replace the letters that should be upper case with their upper case version
SELECT #InitCap = Stuff( #InitCap, n, 1, Upper( SubString( #InitCap, n, 1 ) ) )
FROM (
SELECT (1 + n1.n + n10.n + n100.n + n1000.n) AS n
FROM (SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS n1
CROSS JOIN (SELECT 0 AS n UNION SELECT 10 UNION SELECT 20 UNION SELECT 30 UNION SELECT 40 UNION SELECT 50 UNION SELECT 60 UNION SELECT 70 UNION SELECT 80 UNION SELECT 90) AS n10
CROSS JOIN (SELECT 0 AS n UNION SELECT 100 UNION SELECT 200 UNION SELECT 300 UNION SELECT 400 UNION SELECT 500 UNION SELECT 600 UNION SELECT 700 UNION SELECT 800 UNION SELECT 900) AS n100
CROSS JOIN (SELECT 0 AS n UNION SELECT 1000 UNION SELECT 2000 UNION SELECT 3000) AS n1000
) AS Sequence
WHERE
n BETWEEN 1 AND Len( #InitCap )
AND SubString( #InitCap, n, 1 ) LIKE '[a-z]' /* this character is a letter */
AND (
n = 1 /* this character is the first `character` */
OR SubString( #InitCap, n-1, 1 ) LIKE '[^a-z]' /* the previous character is NOT a letter */
)
AND (
n < 3 /* only test the 3rd or greater characters for this exception */
OR SubString( #InitCap, n-2, 3 ) NOT LIKE '[a-z]''[a-z]' /* exception: The pattern <letter>'<letter> should not capatolize the letter following the apostrophy */
)
-- 3. Return the modified version of the input
RETURN #InitCap
END

On Server Server 2016 and newer, you can use STRING_SPLIT
with t as (
select 'GOOFYEAR Tire and Rubber Company' as n
union all
select 'THE HAPPY BEAR' as n
union all
select 'MONK HOUSE SALES' as n
union all
select 'FORUM COMMUNICATIONS' as n
)
select
n,
(
select ' ' + (
upper(left(value, 1))
+ lower(substring(value, 2, 999))
)
from (
select value
from string_split(t.n, ' ')
) as sq
for xml path ('')
) as title_cased
from t
Example

If you know all the data is just a single word here's a solution. First update the column to all lower and then run the following
update tableName set columnName =
upper(SUBSTRING(columnName, 1, 1)) + substring(columnName, 2, len(columnName)) from tableName

A slight modification to #Galwegian's answer - which turns e.g. St Elizabeth's into St Elizabeth'S.
This modification keeps apostrophe-s as lowercase where the s comes at the end of the string provided or the s is followed by a space (and only in those circumstances).
create function properCase(#text as varchar(8000))
returns varchar(8000)
as
begin
declare #reset int;
declare #ret varchar(8000);
declare #i int;
declare #c char(1);
declare #d char(1);
if #text is null
return null;
select #reset = 1, #i = 1, #ret = '';
while (#i <= len(#text))
select
#c = substring(#text, #i, 1),
#d = substring(#text, #i+1, 1),
#ret = #ret + case when #reset = 1 or (#reset=-1 and #c!='s') or (#reset=-1 and #c='s' and #d!=' ') then upper(#c) else lower(#c) end,
#reset = case when #c like '[a-za-z]' then 0 when #c='''' then -1 else 1 end,
#i = #i + 1
return #ret
end
It turns:
st elizabeth's into St Elizabeth's
o'keefe into O'Keefe
o'sullivan into O'Sullivan
Others' comments that different solutions are preferable for non-English input remain the case.

In Oracle SQL or PostgreSQL, just do:
SELECT INITCAP(title) FROM data;
In SQL Server, define the function first as in below, then:
SELECT dbo.InitCap(title) FROM data;
Define dbo.InitCap():
-- Drop the function if it already exists
IF OBJECT_ID('dbo.InitCap') IS NOT NULL
DROP FUNCTION dbo.InitCap;
GO
-- Implementing Oracle INITCAP function
CREATE FUNCTION dbo.InitCap (#inStr VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #outStr VARCHAR(8000) = LOWER(#inStr),
#char CHAR(1),
#alphanum BIT = 0,
#len INT = LEN(#inStr),
#pos INT = 1;
-- Iterate through all characters in the input string
WHILE #pos <= #len BEGIN
-- Get the next character
SET #char = SUBSTRING(#inStr, #pos, 1);
-- If the position is first, or the previous characater is not alphanumeric
-- convert the current character to upper case
IF #pos = 1 OR #alphanum = 0
SET #outStr = STUFF(#outStr, #pos, 1, UPPER(#char));
SET #pos = #pos + 1;
-- Define if the current character is non-alphanumeric
IF ASCII(#char) <= 47 OR (ASCII(#char) BETWEEN 58 AND 64) OR
(ASCII(#char) BETWEEN 91 AND 96) OR (ASCII(#char) BETWEEN 123 AND 126)
SET #alphanum = 0;
ELSE
SET #alphanum = 1;
END
RETURN #outStr;
END
GO

The link I posted above is a great option that addresses the main issue: that we can never programmatically account for all cases (Smith-Jones, von Haussen, John Smith M.D.), at least not in an elegant manner. Tony introduces the concept of an exception / break character to deal with these cases.
Anyways, building on Cervo's idea (upper all lower chars preceded by space), the replace statements could be wrapped up in a single table based replace instead. Really, any low/up character combination could be inserted into #alpha and the statement would not change:
declare #str nvarchar(8000)
declare #alpha table (low nchar(1), up nchar(1))
set #str = 'ALL UPPER CASE and SOME lower ÄÄ ÖÖ ÜÜ ÉÉ ØØ ĈĈ ÆÆ'
-- stage the alpha (needs number table)
insert into #alpha
-- A-Z / a-z
select nchar(n+32),
nchar(n)
from dbo.Number
where n between 65 and 90 or
n between 192 and 223
-- append space at start of str
set #str = lower(' ' + #str)
-- upper all lower case chars preceded by space
select #str = replace(#str, ' ' + low, ' ' + up)
from #Alpha
select #str

It would make sense to maintain a lookup of exceptions to take care of The von Neumann's, McCain's, DeGuzman's, and the Johnson-Smith's.

Borrowed and improved on #Richard Sayakanit answer. This handles multiple words. Like his answer, this doesn't use any UDFs, only built-in functions (STRING_SPLIT and STRING_AGG) and it's pretty fast. STRING_AGG requires SQL Server 2017 but you can always use the STUFF/XML trick. Won't handle every exception but can work great for many requirements.
SELECT StateName = 'North Carolina'
INTO #States
UNION ALL
SELECT 'Texas'
;WITH cteData AS
(
SELECT
UPPER(LEFT(value, 1)) +
LOWER(RIGHT(value, LEN(value) - 1)) value, op.StateName
FROM #States op
CROSS APPLY STRING_SPLIT(op.StateName, ' ') AS ss
)
SELECT
STRING_AGG(value, ' ')
FROM cteData c
GROUP BY StateName

Recently had to tackle this and came up with the following after nothing quite hit everything I wanted. This will do an entire sentence, cases for special word handling. We also had issues with single character 'words' that a lot of the simpler methods handle but not the more complicated methods. Single return variable, no loops or cursors either.
CREATE FUNCTION ProperCase(#Text AS NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS BEGIN
DECLARE #return NVARCHAR(MAX)
SELECT #return = COALESCE(#return + ' ', '') + Word FROM (
SELECT CASE
WHEN LOWER(value) = 'llc' THEN UPPER(value)
WHEN LOWER(value) = 'lp' THEN UPPER(value) --Add as many new special cases as needed
ELSE
CASE WHEN LEN(value) = 1
THEN UPPER(value)
ELSE UPPER(LEFT(value, 1)) + (LOWER(RIGHT(value, LEN(value) - 1)))
END
END AS Word
FROM STRING_SPLIT(#Text, ' ')
) tmp
RETURN #return
END

I think you will find that the following is more efficient:
IF OBJECT_ID('dbo.ProperCase') IS NOT NULL
DROP FUNCTION dbo.ProperCase
GO
CREATE FUNCTION dbo.PROPERCASE (
#str VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
SET #str = ' ' + #str
SET #str = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( #str, ' a', ' A'), ' b', ' B'), ' c', ' C'), ' d', ' D'), ' e', ' E'), ' f', ' F'), ' g', ' G'), ' h', ' H'), ' i', ' I'), ' j', ' J'), ' k', ' K'), ' l', ' L'), ' m', ' M'), ' n', ' N'), ' o', ' O'), ' p', ' P'), ' q', ' Q'), ' r', ' R'), ' s', ' S'), ' t', ' T'), ' u', ' U'), ' v', ' V'), ' w', ' W'), ' x', ' X'), ' y', ' Y'), ' z', ' Z')
RETURN RIGHT(#str, LEN(#str) - 1)
END
GO
The replace statement could be cut and pasted directly into a SQL query. It is ultra ugly, however by replacing #str with the column you are interested in, you will not pay a price for an implicit cursor like you will with the udfs thus posted. I find that even using my UDF it is much more efficient.
Oh and instead of generating the replace statement by hand use this:
-- Code Generator for expression
DECLARE #x INT,
#c CHAR(1),
#sql VARCHAR(8000)
SET #x = 0
SET #sql = '#str' -- actual variable/column you want to replace
WHILE #x < 26
BEGIN
SET #c = CHAR(ASCII('a') + #x)
SET #sql = 'REPLACE(' + #sql + ', '' ' + #c+ ''', '' ' + UPPER(#c) + ''')'
SET #x = #x + 1
END
PRINT #sql
Anyway it depends on the number of rows. I wish you could just do s/\b([a-z])/uc $1/, but oh well we work with the tools we have.
NOTE you would have to use this as you would have to use it as....SELECT dbo.ProperCase(LOWER(column)) since the column is in uppercase. It actually works pretty fast on my table of 5,000 entries (not even one second) even with the lower.
In response to the flurry of comments regarding internationalization I present the following implementation that handles every ascii character relying only on SQL Server's Implementation of upper and lower. Remember, the variables we are using here are VARCHAR which means that they can only hold ASCII values. In order to use further international alphabets, you have to use NVARCHAR. The logic would be similar but you would need to use UNICODE and NCHAR in place of ASCII AND CHAR and the replace statement would be much more huge....
-- Code Generator for expression
DECLARE #x INT,
#c CHAR(1),
#sql VARCHAR(8000),
#count INT
SEt #x = 0
SET #count = 0
SET #sql = '#str' -- actual variable you want to replace
WHILE #x < 256
BEGIN
SET #c = CHAR(#x)
-- Only generate replacement expression for characters where upper and lowercase differ
IF #x = ASCII(LOWER(#c)) AND #x != ASCII(UPPER(#c))
BEGIN
SET #sql = 'REPLACE(' + #sql + ', '' ' + #c+ ''', '' ' + UPPER(#c) + ''')'
SET #count = #count + 1
END
SET #x = #x + 1
END
PRINT #sql
PRINT 'Total characters substituted: ' + CONVERT(VARCHAR(255), #count)
Basically the premise of the my method is trading pre-computing for efficiency. The full ASCII implementation is as follows:
IF OBJECT_ID('dbo.ProperCase') IS NOT NULL
DROP FUNCTION dbo.ProperCase
GO
CREATE FUNCTION dbo.PROPERCASE (
#str VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
SET #str = ' ' + #str
SET #str = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#str, ' a', ' A'), ' b', ' B'), ' c', ' C'), ' d', ' D'), ' e', ' E'), ' f', ' F'), ' g', ' G'), ' h', ' H'), ' i', ' I'), ' j', ' J'), ' k', ' K'), ' l', ' L'), ' m', ' M'), ' n', ' N'), ' o', ' O'), ' p', ' P'), ' q', ' Q'), ' r', ' R'), ' s', ' S'), ' t', ' T'), ' u', ' U'), ' v', ' V'), ' w', ' W'), ' x', ' X'), ' y', ' Y'), ' z', ' Z'), ' š', ' Š'), ' œ', ' Œ'), ' ž', ' Ž'), ' à', ' À'), ' á', ' Á'), ' â', ' Â'), ' ã', ' Ã'), ' ä', ' Ä'), ' å', ' Å'), ' æ', ' Æ'), ' ç', ' Ç'), ' è', ' È'), ' é', ' É'), ' ê', ' Ê'), ' ë', ' Ë'), ' ì', ' Ì'), ' í', ' Í'), ' î', ' Î'), ' ï', ' Ï'), ' ð', ' Ð'), ' ñ', ' Ñ'), ' ò', ' Ò'), ' ó', ' Ó'), ' ô', ' Ô'), ' õ', ' Õ'), ' ö', ' Ö'), ' ø', ' Ø'), ' ù', ' Ù'), ' ú', ' Ú'), ' û', ' Û'), ' ü', ' Ü'), ' ý', ' Ý'), ' þ', ' Þ'), ' ÿ', ' Ÿ')
RETURN RIGHT(#str, LEN(#str) - 1)
END
GO

Is it too late to go back and get the un-uppercased data?
The von Neumann's, McCain's, DeGuzman's, and the Johnson-Smith's of your client base may not like the result of your processing...
Also, I'm guessing that this is intended to be a one-time upgrade of the data? It might be easier to export, filter/modify, and re-import the corrected names into the db, and then you can use non-SQL approaches to name fixing...

Here is another variation I found on the SQLTeam.com Forums #
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=47718
create FUNCTION PROPERCASE
(
--The string to be converted to proper case
#input varchar(8000)
)
--This function returns the proper case string of varchar type
RETURNS varchar(8000)
AS
BEGIN
IF #input IS NULL
BEGIN
--Just return NULL if input string is NULL
RETURN NULL
END
--Character variable declarations
DECLARE #output varchar(8000)
--Integer variable declarations
DECLARE #ctr int, #len int, #found_at int
--Constant declarations
DECLARE #LOWER_CASE_a int, #LOWER_CASE_z int, #Delimiter char(3), #UPPER_CASE_A int, #UPPER_CASE_Z int
--Variable/Constant initializations
SET #ctr = 1
SET #len = LEN(#input)
SET #output = ''
SET #LOWER_CASE_a = 97
SET #LOWER_CASE_z = 122
SET #Delimiter = ' ,-'
SET #UPPER_CASE_A = 65
SET #UPPER_CASE_Z = 90
WHILE #ctr <= #len
BEGIN
--This loop will take care of reccuring white spaces
WHILE CHARINDEX(SUBSTRING(#input,#ctr,1), #Delimiter) > 0
BEGIN
SET #output = #output + SUBSTRING(#input,#ctr,1)
SET #ctr = #ctr + 1
END
IF ASCII(SUBSTRING(#input,#ctr,1)) BETWEEN #LOWER_CASE_a AND #LOWER_CASE_z
BEGIN
--Converting the first character to upper case
SET #output = #output + UPPER(SUBSTRING(#input,#ctr,1))
END
ELSE
BEGIN
SET #output = #output + SUBSTRING(#input,#ctr,1)
END
SET #ctr = #ctr + 1
WHILE CHARINDEX(SUBSTRING(#input,#ctr,1), #Delimiter) = 0 AND (#ctr <= #len)
BEGIN
IF ASCII(SUBSTRING(#input,#ctr,1)) BETWEEN #UPPER_CASE_A AND #UPPER_CASE_Z
BEGIN
SET #output = #output + LOWER(SUBSTRING(#input,#ctr,1))
END
ELSE
BEGIN
SET #output = #output + SUBSTRING(#input,#ctr,1)
END
SET #ctr = #ctr + 1
END
END
RETURN #output
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

Just learned about InitCap().
Here is some sample code:
SELECT ID
,InitCap(LastName ||', '|| FirstName ||' '|| Nvl(MiddleName,'')) AS RecipientName
FROM SomeTable

This worked in SSMS:
Select Jobtitle,
concat(Upper(LEFT(jobtitle,1)), SUBSTRING(jobtitle,2,LEN(jobtitle))) as Propercase
From [HumanResources].[Employee]

Sadly, I am proposing yet another function. This one seems faster than most, but only capitalizes the first letter of words separated by spaces. I've checked that the input is not null, and that it works if you have multiple spaces somewhere in the middle of the string. I'm cross applying the length function so I don't have to call it twice. I would have thought that SQL Server would have cached that value. Caveat emptor.
CREATE OR ALTER FUNCTION dbo.ProperCase(#value varchar(MAX)) RETURNS varchar(MAX) AS
BEGIN
RETURN (SELECT STRING_AGG(CASE lv WHEN 0 THEN '' WHEN 1 THEN UPPER(value)
ELSE UPPER(LEFT(value,1)) + LOWER(RIGHT(value,lv-1)) END,' ')
FROM STRING_SPLIT(TRIM(#value),' ') AS ss
CROSS APPLY (SELECT LEN(VALUE) lv) AS reuse
WHERE #value IS NOT NULL)
END

This function has worked for me
create function [dbo].Pascal (#string varchar(max))
returns varchar(max)
as
begin
declare #Index int
,#ResultString varchar(max)
set #Index = 1
set #ResultString = ''
while (#Index < LEN(#string) + 1)
begin
if (#Index = 1)
begin
set #ResultString += UPPER(SUBSTRING(#string, #Index, 1))
set #Index += 1
end
else if (
(
SUBSTRING(#string, #Index - 1, 1) = ' '
or SUBSTRING(#string, #Index - 1, 1) = '-'
or SUBSTRING(#string, #Index + 1, 1) = '-'
)
and #Index + 1 <> LEN(#string) + 1
)
begin
set #ResultString += UPPER(SUBSTRING(#string, #Index, 1))
set #Index += 1
end
else
begin
set #ResultString += LOWER(SUBSTRING(#string, #Index, 1))
set #Index += 1
end
end
if (##ERROR <> 0)
begin
set #ResultString = #string
end
return replace(replace(replace(#ResultString, ' ii', ' II'), ' iii', ' III'), ' iv', ' IV')
end

Copy and paste your data into MS Word and use built in text-conversion to "Capitalize Each Word". Compare against your original data to address exceptions. Can't see any way around manually sidestepping "MacDonald" and "IBM" type exceptions but this was how I got it done FWIW.

I know the devil is in the detail (especially where people's personal data is concerned), and that it would be very nice to have properly capitalised names, but the above kind of hassle is why the pragmatic, time-conscious amongst us use the following:
SELECT UPPER('Put YoUR O'So oddLy casED McWeird-nAme von rightHERE here')
In my experience, people are fine seeing THEIR NAME ... even when it's half way through a sentence.
Refer to: the Russians used a pencil!

Related

SQL Server capitalize every first letter of every word with loop [duplicate]

What’s the best way to capitalize the first letter of each word in a string in SQL Server.
From http://www.sql-server-helper.com/functions/initcap.aspx
CREATE FUNCTION [dbo].[InitCap] ( #InputString varchar(4000) )
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #PrevChar CHAR(1)
DECLARE #OutputString VARCHAR(255)
SET #OutputString = LOWER(#InputString)
SET #Index = 1
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
SET #PrevChar = CASE WHEN #Index = 1 THEN ' '
ELSE SUBSTRING(#InputString, #Index - 1, 1)
END
IF #PrevChar IN (' ', ';', ':', '!', '?', ',', '.', '_', '-', '/', '&', '''', '(')
BEGIN
IF #PrevChar != '''' OR UPPER(#Char) != 'S'
SET #OutputString = STUFF(#OutputString, #Index, 1, UPPER(#Char))
END
SET #Index = #Index + 1
END
RETURN #OutputString
END
GO
There is a simpler/smaller one here (but doesn't work if any row doesn't have spaces, "Invalid length parameter passed to the RIGHT function."):
http://www.devx.com/tips/Tip/17608
As a table-valued function:
CREATE FUNCTION dbo.InitCap(#v AS VARCHAR(MAX))
RETURNS TABLE
AS
RETURN
WITH a AS (
SELECT (
SELECT UPPER(LEFT(value, 1)) + LOWER(SUBSTRING(value, 2, LEN(value))) AS 'data()'
FROM string_split(#v, ' ')
ORDER BY CHARINDEX(value,#v)
FOR XML PATH (''), TYPE) ret)
SELECT CAST(a.ret AS varchar(MAX)) ret from a
GO
Note that string_split requires COMPATIBILITY_LEVEL 130.
A variation of the one I've been using for quite some time is:
CREATE FUNCTION [widget].[properCase](#string varchar(8000)) RETURNS varchar(8000) AS
BEGIN
SET #string = LOWER(#string)
DECLARE #i INT
SET #i = ASCII('a')
WHILE #i <= ASCII('z')
BEGIN
SET #string = REPLACE( #string, ' ' + CHAR(#i), ' ' + CHAR(#i-32))
SET #i = #i + 1
END
SET #string = CHAR(ASCII(LEFT(#string, 1))-32) + RIGHT(#string, LEN(#string)-1)
RETURN #string
END
You can easily modify to handle characters after items other than spaces if you wanted to.
Another solution without using the loop - pure set-based approach with recursive CTE
create function [dbo].InitCap (#value varchar(max))
returns varchar(max) as
begin
declare
#separator char(1) = ' ',
#result varchar(max) = '';
with r as (
select value, cast(null as varchar(max)) [x], cast('' as varchar(max)) [char], 0 [no] from (select rtrim(cast(#value as varchar(max))) [value]) as j
union all
select right(value, len(value)-case charindex(#separator, value) when 0 then len(value) else charindex(#separator, value) end) [value]
, left(r.[value], case charindex(#separator, r.value) when 0 then len(r.value) else abs(charindex(#separator, r.[value])-1) end ) [x]
, left(r.[value], 1)
, [no] + 1 [no]
from r where value > '')
select #result = #result +
case
when ascii([char]) between 97 and 122
then stuff(x, 1, 1, char(ascii([char])-32))
else x
end + #separator
from r where x is not null;
set #result = rtrim(#result);
return #result;
end
If you are looking for the answer to the same question in Oracle/PLSQL then you may use the function INITCAP. Below is an example for the attribute dname from a table department which has the values ('sales', 'management', 'production', 'development').
SQL> select INITCAP(dname) from department;
INITCAP(DNAME)
--------------------------------------------------
Sales
Management
Production
Development
;WITH StudentList(Name) AS (
SELECT CONVERT(varchar(50), 'Carl-VAN')
UNION SELECT 'Dean o''brian'
UNION SELECT 'Andrew-le-Smith'
UNION SELECT 'Eddy thompson'
UNION SELECT 'BOBs-your-Uncle'
), Student AS (
SELECT CONVERT(varchar(50), UPPER(LEFT(Name, 1)) + LOWER(SUBSTRING(Name, 2, LEN(Name)))) Name,
pos = PATINDEX('%[-'' ]%', Name)
FROM StudentList
UNION ALL
SELECT CONVERT(varchar(50), LEFT(Name, pos) + UPPER(SUBSTRING(Name, pos + 1, 1)) + SUBSTRING(Name, pos + 2, LEN(Name))) Name,
pos = CASE WHEN PATINDEX('%[-'' ]%', RIGHT(Name, LEN(Name) - pos)) = 0 THEN 0 ELSE pos + PATINDEX('%[-'' ]%', RIGHT(Name, LEN(Name) - pos)) END
FROM Student
WHERE pos > 0
)
SELECT Name
FROM Student
WHERE pos = 0
ORDER BY Name
This will result in:
Andrew-Le-Smith
Bobs-Your-Uncle
Carl-Van
Dean O'Brian
Eddy Thompson
Using a recursive CTE set based query should out perform a procedural while loop query.
Here I also have made my separate to be 3 different characters [-' ] instead of 1 for a more advanced example. Using PATINDEX as I have done allows me to look for many characters. You could also use CHARINDEX on a single character and this function excepts a third parameter StartFromPosition so I could further simply my 2nd part of the recursion of the pos formula to (assuming a space): pos = CHARINDEX(' ', Name, pos + 1).
The suggested function works fine, however, if you do not want to create any function this is how I do it:
select ID,Name
,string_agg(concat(upper(substring(value,1,1)),lower(substring(value,2,len(value)-1))),' ') as ModifiedName
from Table_Customer
cross apply String_Split(replace(trim(Name),' ',' '),' ')
where Name is not null
group by ID,Name;
The above query split the words by space (' ') and create different rows of each having one substring, then convert the first letter of each substring to upper and keep remaining as lower. The final step is to string aggregate based on the key.
BEGIN
DECLARE #string varchar(100) = 'asdsadsd asdad asd'
DECLARE #ResultString varchar(200) = ''
DECLARE #index int = 1
DECLARE #flag bit = 0
DECLARE #temp varchar(2) = ''
WHILE (#Index <LEN(#string)+1)
BEGIN
SET #temp = SUBSTRING(#string, #Index-1, 1)
--select #temp
IF #temp = ' ' OR #index = 1
BEGIN
SET #ResultString = #ResultString + UPPER(SUBSTRING(#string, #Index, 1))
END
ELSE
BEGIN
SET #ResultString = #ResultString + LOWER(SUBSTRING(#string, #Index, 1))
END
SET #Index = #Index+ 1--increase the index
END
SELECT #ResultString
END
It can be as simple as this:
DECLARE #Name VARCHAR(500) = 'Roger';
SELECT #Name AS Name, UPPER(LEFT(#Name, 1)) + SUBSTRING(#Name, 2, LEN(#Name)) AS CapitalizedName;
fname is column name if fname value is akhil then UPPER(left(fname,1)) provide capital First letter(A) and substring function SUBSTRING(fname,2,LEN(fname)) provide(khil) concate both using + then result is (Akhil)
select UPPER(left(fname,1))+SUBSTRING(fname,2,LEN(fname)) as fname
FROM [dbo].[akhil]
On SQL Server 2016+ using JSON which gives guaranteed order of the words:
CREATE FUNCTION [dbo].[InitCap](#Text NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
RETURN STUFF((
SELECT ' ' + UPPER(LEFT(s.value,1)) + LOWER(SUBSTRING(s.value,2,LEN(s.value)))
FROM OPENJSON('["' + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#Text,'\','\\'),'"','\"'),CHAR(9),'\t'),CHAR(10),'\n'),' ','","') + '"]') s
ORDER BY s.[key]
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'');
END
GO
CREATE FUNCTION [dbo].[Capitalize](#text NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) AS
BEGIN
DECLARE #result NVARCHAR(MAX) = '';
DECLARE #c NVARCHAR(1);
DECLARE #i INT = 1;
DECLARE #isPrevSpace BIT = 1;
WHILE #i <= LEN(#text)
BEGIN
SET #c = SUBSTRING(#text, #i, 1);
SET #result += IIF(#isPrevSpace = 1, UPPER(#c), LOWER(#c));
SET #isPrevSpace = IIF(#c LIKE '[ -]', 1, 0);
SET #i += 1;
END
RETURN #result;
END
GO
DECLARE #sentence NVARCHAR(100) = N'i-thINK-this soLUTION-works-LiKe-a charm';
PRINT dbo.Capitalize(#sentence);
-- I-Think-This Solution-Works-Like-A Charm
Here is the simplest one-liner to do this:
SELECT LEFT(column, 1)+ lower(RIGHT(column, len(column)-1) ) FROM [tablename]
I was looking for the best way to capitalize and i recreate simple sql script
how to use SELECT dbo.Capitalyze('this is a test with multiple spaces')
result "This Is A Test With Multiple Spaces"
CREATE FUNCTION Capitalyze(#input varchar(100) )
returns varchar(100)
as
begin
declare #index int=0
declare #char as varchar(1)=' '
declare #prevCharIsSpace as bit=1
declare #Result as varchar(100)=''
set #input=UPPER(LEFT(#input,1))+LOWER(SUBSTRING(#input, 2, LEN(#input)))
set #index=PATINDEX('% _%',#input)
if #index=0
set #index=len(#input)
set #Result=substring(#input,0,#index+1)
WHILE (#index < len(#input))
BEGIN
SET #index = #index + 1
SET #char=substring(#input,#index,1)
if (#prevCharIsSpace=1)
begin
set #char=UPPER(#char)
if (#char=' ')
set #char=''
end
if (#char=' ')
set #prevCharIsSpace=1
else
set #prevCharIsSpace=0
set #Result=#Result+#char
--print #Result
END
--print #Result
return #Result
end
IF OBJECT_ID ('dbo.fnCapitalizeFirstLetterAndChangeDelimiter') IS NOT NULL
DROP FUNCTION dbo.fnCapitalizeFirstLetterAndChangeDelimiter
GO
CREATE FUNCTION [dbo].[fnCapitalizeFirstLetterAndChangeDelimiter] (#string NVARCHAR(MAX), #delimiter NCHAR(1), #new_delimeter NCHAR(1))
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #result NVARCHAR(MAX)
SELECT #result = '';
IF (LEN(#string) > 0)
DECLARE #curr INT
DECLARE #next INT
BEGIN
SELECT #curr = 1
SELECT #next = CHARINDEX(#delimiter, #string)
WHILE (LEN(#string) > 0)
BEGIN
SELECT #result =
#result +
CASE WHEN LEN(#result) > 0 THEN #new_delimeter ELSE '' END +
UPPER(SUBSTRING(#string, #curr, 1)) +
CASE
WHEN #next <> 0
THEN LOWER(SUBSTRING(#string, #curr+1, #next-2))
ELSE LOWER(SUBSTRING(#string, #curr+1, LEN(#string)-#curr))
END
IF (#next > 0)
BEGIN
SELECT #string = SUBSTRING(#string, #next+1, LEN(#string)-#next)
SELECT #next = CHARINDEX(#delimiter, #string)
END
ELSE
SELECT #string = ''
END
END
RETURN #result
END
GO

Proper/Title Case a Column with Exceptions table in SQL Server

I am trying to convert a column which is in upper case to proper case but with exceptions like certain acronyms, abbreviations. I am following the below code to implement that. But looks like this will be an ongoing process and so, I want to create a table with the exceptions in order to make it easy to clean the data and I want to be able to call the exceptions table from the function. It would be great if anyone can help me with any codes they have which is similar to this or any ideas on how to implement it.
ALTER FUNCTION [dbo].[Business_ProperCase]
(#Text AS VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
-- declare some variables
DECLARE #Reset BIT; DECLARE #Ret VARCHAR(8000); DECLARE #i INT;
DECLARE #c0 CHAR(1); DECLARE #c1 CHAR(1); DECLARE #c2 CHAR(1);
DECLARE #CaseLen INT;
DECLARE #CaseExceptions VARCHAR(8000);
DECLARE #CaseValue VARCHAR(8000);
-- Set some default values
SELECT #Reset = 1, #i=1, #Ret = '';
-- only apply if all characters are already in uppercase
IF (UPPER(#Text)=#Text COLLATE Latin1_General_CS_AI)
BEGIN
-- add a leading and trailing space to indicate word delimiters (bol & eol)
SET #Text = ' ' + #Text + ' ';
-- cycle through each character,
-- if non-alpha, uppercase next alpha character.
-- if alpha then lowercase subsequent alphas.
WHILE (#i <= LEN(#Text))
SELECT
#c0=SUBSTRING(#Text,#i-2,1), #c1=SUBSTRING(#Text,#i-1,1), #c2=SUBSTRING(#Text,#i,1),
#Ret = #Ret + CASE WHEN #Reset=1 THEN UPPER(#c2) ELSE LOWER(#c2) END,
#Reset = CASE
WHEN #c0 = ' ' AND #c1 = 'M' AND #c2 = 'c' THEN 1
WHEN #c0 = ' ' AND #c1 IN ('D', 'I', 'O') AND #c2 = '''' THEN 1
WHEN #c2 LIKE '[a-zA-Z'']' THEN 0 -- Apply LOWER to any character after alphas or apostrophes
ELSE 1 -- Apply UPPER to any character after symbols/punctuation
END,
#i = #i +1
-- add a trailing space in case the previous rule changed this.
SET #Ret = #Ret + ' ';
-- custom exceptions: this search is case-insensitive and will
-- replace the word to the case as it is written in the list.
-- NOTE: this list has to end with a comma!
SELECT #i=0, #CaseLen=0,
#CaseExceptions = 'ABS,LLC,MD,MBA,MA,
--Want to create a table for these exceptions and call them from this function
-- Loop through exception cases
WHILE CHARINDEX(',', #CaseExceptions, #i+1)>0
BEGIN
-- get the delimited word
SET #CaseLen = CHARINDEX(',', #CaseExceptions, #i+1) - #i
SET #CaseValue = SUBSTRING(#CaseExceptions, #i, #CaseLen)
-- replace it in the original text
SET #Ret = REPLACE(#Ret, ' '+#CaseValue+' ', ' '+#CaseValue+' ')
-- get position of next word
SET #i = CHARINDEX(',', #CaseExceptions, #i+#CaseLen) +1
END
-- remove any leading and trailing spaces
SET #Ret = LTRIM(RTRIM(#Ret));
-- capitalize first character of data irrespective of previous rules
SET #Ret = UPPER(SUBSTRING(#Ret,1,1)) + SUBSTRING(#Ret,2,LEN(#Ret));
END
ELSE
BEGIN
-- return the string unaffected if it is not in uppercase
SET #Ret=#Text
END
RETURN #Ret
END
Create a table (I use TITLE_CASE_EXCEPTION as my example) with a column EXCEPTION
Then it is data driven from there.
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[GUI].[fn_TITLE_CASE]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [GUI].[fn_TITLE_CASE]
GO
CREATE FUNCTION [GUI].[fn_TITLE_CASE]
(
#STRING VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
SET QUOTED_IDENTIFIER OFF
DECLARE #RESET BIT
DECLARE #_OUT_STRING VARCHAR(MAX)
DECLARE #I INT
DECLARE #C CHAR(1)
DECLARE #CASE_LEN INT = 0
DECLARE #CASE_EXCEPTIONS VARCHAR(MAX) = ''
DECLARE #CASE_VALUE VARCHAR(MAX) = ''
IF #STRING IS NULL
RETURN NULL
IF #STRING = ''
RETURN #STRING
SELECT #STRING = LOWER(RTRIM(#STRING)), #RESET = 1, #I = 1, #_OUT_STRING = ''
WHILE (#I <= LEN(#STRING))
SELECT
#C = SUBSTRING(#STRING, #I, 1),
#_OUT_STRING = #_OUT_STRING + CASE WHEN #RESET = 1 THEN UPPER(#C) ELSE #C END,
#RESET = CASE WHEN #C LIKE '[a-zA-Z'']' THEN 0 ELSE 1 END,
#I = #I + 1
SELECT #I = 0, #_OUT_STRING = #_OUT_STRING + ' '
SELECT #CASE_EXCEPTIONS = #CASE_EXCEPTIONS + RTRIM(EXCEPTION) + ',' FROM [LOOKUP].TITLE_CASE_EXCEPTION
WHILE CHARINDEX(',', #CASE_EXCEPTIONS, #I + 1) > 0
BEGIN
-- get the delimited word
SET #CASE_LEN = CHARINDEX(',', #CASE_EXCEPTIONS, #I + 1) - #I
SET #CASE_VALUE = SUBSTRING(#CASE_EXCEPTIONS, #I, #CASE_LEN)
-- replace it in the original text
SET #_OUT_STRING = REPLACE(#_OUT_STRING, ' ' + #CASE_VALUE + ' ', ' ' + #CASE_VALUE + ' ')
-- get position of next word
SET #I = CHARINDEX(',', #CASE_EXCEPTIONS, #I + #CASE_LEN) + 1
END
RETURN RTRIM(#_OUT_STRING)
END
GO
Here's an example for you to reference:
declare #s varchar(256) = 'This is a SQL test';
declare #t table (ignore varchar(256) not null);
insert into #t (ignore) values ('SQL');
declare #pos int = 1;
declare #nextpos int;
declare #w varchar(256);
while #pos <= len(#s)
begin
set #nextpos = charindex(' ', #s + ' ', #pos);
set #w = substring(#s, #pos, #nextpos - #pos);
if not exists (select 1 from #t where ignore = #w)
set #s = stuff(
#s, #pos, #nextpos - #pos,
stuff(lower(#w), 1, 1, upper(left(#w, 1)))
);
set #pos = #nextpos + 1;
select #s;
end
To answer the original request.. set up a table "Exceptions" with a single column ConcatList of type nvarchar (100) and add the exceptions to this table... then create a view with to concatenate them together...
create table exceptions (ConcatList nvarchar(100))
create view [dbo].vExceptions
as
Select distinct
substring(
(
Select ','+ up.ConcatList AS [text()]
From exceptions up
ORDER BY up.ConcatList
For XML PATH ('')
), 2, 4000) [exceptions]
From exceptions p
Here is a slightly enhanced version of the stored procedure from the question.
(although an admittedly inelegant solution) to account for:
Lower case words (of, the, an, etc)
Hhyphenated acronyms
Exceptions that are immediately preceeded or followed with a dash or comma.
alter FUNCTION [dbo].[Business_ProperCase]
(#Text AS VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
-- declare some variables
DECLARE #Reset BIT; DECLARE #Ret VARCHAR(8000); DECLARE #i INT;
DECLARE #c0 CHAR(1); DECLARE #c1 CHAR(1); DECLARE #c2 CHAR(1);
DECLARE #CaseLen INT;
DECLARE #CaseExceptions VARCHAR(8000);
DECLARE #CaseValue VARCHAR(8000);
-- Set some default values
SELECT #Reset = 1, #i=1, #Ret = '';
-- only apply if all characters are already in uppercase
IF (UPPER(#Text)=#Text COLLATE Latin1_General_CS_AI)
BEGIN
-- add a leading and trailing space to indicate word delimiters (bol & eol)
SET #Text = ' ' + #Text + ' ';
-- cycle through each character,
-- if non-alpha, uppercase next alpha character.
-- if alpha then lowercase subsequent alphas.
WHILE (#i <= LEN(#Text))
SELECT
#c0=SUBSTRING(#Text,#i-2,1), #c1=SUBSTRING(#Text,#i-1,1), #c2=SUBSTRING(#Text,#i,1),
#Ret = #Ret + CASE WHEN #Reset=1 THEN UPPER(#c2) ELSE LOWER(#c2) END,
#Reset = CASE WHEN #c0 = ' ' AND #c1 = 'M' AND #c2 = 'c' THEN 1
WHEN #c0 = ' ' AND #c1 IN ('D', 'I', 'O') AND #c2 = '''' THEN 1
WHEN #c2 LIKE '[a-zA-Z'']' THEN 0 -- Apply LOWER to any character after alphas or apostrophes
ELSE 1 -- Apply UPPER to any character after symbols/punctuation
END,
#i = #i +1
-- add a trailing space in case the previous rule changed this.
SET #Ret = #Ret + ' ';
-- custom exceptions: this search is case-insensitive and will
-- replace the word to the case as it is written in the list.
-- NOTE: this list has to end with a comma!
SELECT #i=0, #CaseLen=0,
#CaseExceptions = exceptions from vExceptions
--Want to create a table for these exceptions and call them from this function
-- Loop through exception cases
WHILE CHARINDEX(',', #CaseExceptions, #i+1)>0
BEGIN
-- get the delimited word
SET #CaseLen = CHARINDEX(',', #CaseExceptions, #i+1) - #i
SET #CaseValue = SUBSTRING(#CaseExceptions, #i, #CaseLen)
if (#CaseValue = 'OF' or #CaseValue = 'AND' or #CaseValue ='THE' or #CaseValue='FOR')
begin
--replace with lower case 'of', 'and', 'the', 'for'
SET #Ret = REPLACE(#Ret, ' '+#CaseValue+' ', ' '+lower(#CaseValue)+' ')
end
else
begin
if (CHARINDEX(' '+ #CaseValue +' ', #Ret)>0 )
begin
-- replace it in the original text
SET #Ret = REPLACE(#Ret, ' '+#CaseValue+' ', ' '+#CaseValue+' ')
end
else if (CHARINDEX(' '+#CaseValue+',', #Ret)>0 )
begin
--replace text (with no spaces around it)
SET #Ret = REPLACE(#Ret, ' '+#CaseValue+',', ' '+#CaseValue+',')
end
else if (CHARINDEX(' '+#CaseValue+'-', #Ret)>0 )
begin
--replace text (with no spaces around it)
SET #Ret = REPLACE(#Ret, ' '+#CaseValue+'-', ' '+#CaseValue+'-')
end
else if (CHARINDEX('-'+#CaseValue+' ', #Ret)>0 )
begin
--replace text (with no spaces around it)
SET #Ret = REPLACE(#Ret, '-'+#CaseValue+' ', '-'+#CaseValue+' ')
end
else if (CHARINDEX(','+#CaseValue+' ', #Ret)>0 )
begin
--replace text (with no spaces around it)
SET #Ret = REPLACE(#Ret, ','+#CaseValue+' ', '-'+#CaseValue+' ')
end
end
-- get position of next word
SET #i = CHARINDEX(',', #CaseExceptions, #i+#CaseLen) +1
END
-- remove any leading and trailing spaces
SET #Ret = LTRIM(RTRIM(#Ret));
-- capitalize first character of data irrespective of previous rules
SET #Ret = UPPER(SUBSTRING(#Ret,1,1)) + SUBSTRING(#Ret,2,LEN(#Ret));
END
ELSE
BEGIN
-- return the string unaffected if it is not in uppercase
SET #Ret=#Text
END
RETURN #Ret
END
Create a table (I use ExceptionsTable as my example) with a column WordExcepts. Then add the following after your last DECLARE at the top of the page:
DECLARE #sql nvarchar(2000);
SET #sql = 'N select WordExcepts from ExceptionsTable'
Then down below adjust your exceptions to be:
#CaseExceptions = #sql
Just add to your table as needed and they get filtered out of the function.

sql to pick apart a string of a persons name and output the initials

how can i get SQL to take a sting and return the first letter of each word passed into it.
I want to use this UDF for generating initials for peoples names I have in the DB.
names can be 2 (fname, lname)or 3(...mname) words
i am using sql2005
This should work for both "Firstname Lastname" and "Firstname Middlename Lastname" combinations.
DECLARE #name AS NVARCHAR(50)
SET #name = 'Firstname Middle Lastname'
SELECT SUBSTRING(#name, 1, 1) + --First initial
SUBSTRING(#name, CHARINDEX(' ', #name) + 1, 1) + --Middle/Last initial
CASE WHEN 0 <> CHARINDEX(' ', #name, CHARINDEX(' ', #name) + 1) -- More than two words
THEN SUBSTRING(#name, CHARINDEX(' ', #name, CHARINDEX(' ', #name) + 1) + 1, 1) --Last initial
ELSE '' --Have to add empty string to avoid NULLing entire result
END
Of course, if users have a space in one of their names for some reason you will have an issue parsing this out, but I suspect that would be the case anyways when not storing your names in separate fields.
For SQL Server 2017 and newer:
CREATE FUNCTION dbo.fnGetFirstChars (#string NVARCHAR(max), #seperator NVARCHAR(MAX))
RETURNS NVARCHAR(max)
AS BEGIN
DECLARE #result NVARCHAR(MAX)
SELECT #result = STRING_AGG(SUBSTRING(value, 1, 1), '')
FROM STRING_SPLIT(#string, #seperator)
RETURN #result
END;
CREATE FUNCTION dbo.GetFirstLetter ( #Array VARCHAR(1000), #separator VARCHAR(10))
RETURNS #resultTable TABLE
(parseValue VARCHAR(100))
AS
BEGIN
DECLARE #separator_position INT
DECLARE #array_value VARCHAR(1000)
SET #array = #array + #separator
WHILE patindex('%' + #separator + '%' , #array) <> 0
BEGIN
SELECT #separator_position = patindex('%' + #separator + '%', #array)
SELECT #array_value = left(#array, #separator_position - 1)
INSERT #resultTable
VALUES (SUBSTRING(Cast(#array_value AS varchar), 1, 1))
SELECT #array = stuff(#array, 1, #separator_position, '')
END
RETURN
END
Here's my solution, and it has these features/peculiarities:
It can process however many names there are in the string. (That is, both less than two and more than three.)
All spaces between the names are preserved.
I know the OP has specified that there can only be 2 or 3 names in his case. I don't mind. I'm just sharing a solution that works, and if it's not best for the particular problem, it's fine.
So, here's the function:
CREATE FUNCTION dbo.fnGetInitials (#name varchar(max))
RETURNS varchar(max)
AS BEGIN
DECLARE #cutpos int, #spacepos int, #result varchar(max);
DECLARE #cutlist TABLE (CutPos int, SpacePos int);
SET #result = LTRIM(RTRIM(#name));
SET #cutpos = 2;
SET #spacepos = CHARINDEX(' ', #result);
WHILE #spacepos > 0 BEGIN
INSERT INTO #cutlist VALUES (#cutpos, #spacepos);
SET #spacepos = #spacepos + 1;
SET #cutpos = #spacepos + 1;
SET #spacepos = CHARINDEX(' ', #result, #spacepos);
END;
DELETE FROM #cutlist WHERE CutPos >= SpacePos;
SELECT #result = STUFF(#result, CutPos, SpacePos - CutPos, '')
FROM #cutlist
ORDER BY CutPos DESC;
RETURN #result;
END;
And here's a test call:
SELECT dbo.fnGetInitials(' John Ronald Reuel Tolkien ');
and the result:
----------------------------------------------------------------------------------------------------
J R R Tolkien
You can achieve it via xquery as well.
Declare #Xml XML
Declare #String Varchar(Max)
Declare #firstletter Varchar(Max)
Declare #delimiter Varchar(5)
SET #delimiter=' '
SET #String= 'THIS IS SQL'
SET #Xml = cast(('<a>'+replace(#String,#delimiter,'</a><a>')+'</a>') AS XML)
;WITH CTE AS
(SELECT A.value('.', 'varchar(max)') as [Column]FROM #Xml.nodes('a') AS FN(a) )
SELECT Stuff((SELECT '' + LEFT([Column],1)from CTE
FOR XML PATH ('') ),1,0,'')
Here is the complete solution.
http://raresql.com/2013/04/12/sql-server-get-the-first-letter-of-each-word-in-a-string-column/
A Picture is 100 times better than description. Here is an example of UDF declaration:
CREATE FUNCTION dbo.GetOnlyFirstLetters(#str NVARCHAR(4000),#sep NVARCHAR(10) )
RETURNS NVARCHAR(100)
AS
BEGIN
DECLARE #textXML XML
SELECT #textXML = CAST('<d>' + replace(#str, #sep, '</d><d>') + '</d>' AS XML)
DECLARE #result VARCHAR(8000)
SET #result = ''
SELECT #result = #result + LEFT(T.split.value ('.', 'nvarchar(max)'), 1)
FROM #textXML.nodes ('/d') T (split)
RETURN #result
END
GO
Here is how to call:
SELECT dbo.GetOnlyFirstLetters('Humayoun Kabir Sohel',' ');
Result will be:
HKS

SQL Server: Make all UPPER case to Proper Case/Title Case

I have a table that was imported as all UPPER CASE and I would like to turn it into Proper Case. What script have any of you used to complete this?
This function:
"Proper Cases" all "UPPER CASE" words that are delimited by white space
leaves "lower case words" alone
works properly even for non-English alphabets
is portable in that it does not use fancy features of recent SQL server versions
can be easily changed to use NCHAR and NVARCHAR for unicode support,as well as any parameter length you see fit
white space definition can be configured
CREATE FUNCTION ToProperCase(#string VARCHAR(255)) RETURNS VARCHAR(255)
AS
BEGIN
DECLARE #i INT -- index
DECLARE #l INT -- input length
DECLARE #c NCHAR(1) -- current char
DECLARE #f INT -- first letter flag (1/0)
DECLARE #o VARCHAR(255) -- output string
DECLARE #w VARCHAR(10) -- characters considered as white space
SET #w = '[' + CHAR(13) + CHAR(10) + CHAR(9) + CHAR(160) + ' ' + ']'
SET #i = 1
SET #l = LEN(#string)
SET #f = 1
SET #o = ''
WHILE #i <= #l
BEGIN
SET #c = SUBSTRING(#string, #i, 1)
IF #f = 1
BEGIN
SET #o = #o + #c
SET #f = 0
END
ELSE
BEGIN
SET #o = #o + LOWER(#c)
END
IF #c LIKE #w SET #f = 1
SET #i = #i + 1
END
RETURN #o
END
Result:
dbo.ToProperCase('ALL UPPER CASE and SOME lower ÄÄ ÖÖ ÜÜ ÉÉ ØØ ĈĈ ÆÆ')
-----------------------------------------------------------------
All Upper Case and Some lower Ää Öö Üü Éé Øø Cc Ææ
Here's a UDF that will do the trick...
create function ProperCase(#Text as varchar(8000))
returns varchar(8000)
as
begin
declare #Reset bit;
declare #Ret varchar(8000);
declare #i int;
declare #c char(1);
if #Text is null
return null;
select #Reset = 1, #i = 1, #Ret = '';
while (#i <= len(#Text))
select #c = substring(#Text, #i, 1),
#Ret = #Ret + case when #Reset = 1 then UPPER(#c) else LOWER(#c) end,
#Reset = case when #c like '[a-zA-Z]' then 0 else 1 end,
#i = #i + 1
return #Ret
end
You will still have to use it to update your data though.
UPDATE titles
SET title =
UPPER(LEFT(title, 1)) +
LOWER(RIGHT(title, LEN(title) - 1))
http://sqlmag.com/t-sql/how-title-case-column-value
If you can enable the CLR in SQL Server (requires 2005 or later) then you could create a CLR function that uses the TextInfo.ToTitleCase built-in function which would allow you to create a culture-aware way of doing this in only a few lines of code.
I know this is late post in this thread but, worth looking. This function works for me ever time. So thought of sharing it.
CREATE FUNCTION [dbo].[fnConvert_TitleCase] (#InputString VARCHAR(4000) )
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #OutputString VARCHAR(255)
SET #OutputString = LOWER(#InputString)
SET #Index = 2
SET #OutputString = STUFF(#OutputString, 1, 1,UPPER(SUBSTRING(#InputString,1,1)))
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
IF #Char IN (' ', ';', ':', '!', '?', ',', '.', '_', '-', '/', '&','''','(')
IF #Index + 1 <= LEN(#InputString)
BEGIN
IF #Char != ''''
OR
UPPER(SUBSTRING(#InputString, #Index + 1, 1)) != 'S'
SET #OutputString =
STUFF(#OutputString, #Index + 1, 1,UPPER(SUBSTRING(#InputString, #Index + 1, 1)))
END
SET #Index = #Index + 1
END
RETURN ISNULL(#OutputString,'')
END
Test calls:
select dbo.fnConvert_TitleCase(Upper('ÄÄ ÖÖ ÜÜ ÉÉ ØØ ĈĈ ÆÆ')) as test
select dbo.fnConvert_TitleCase(upper('Whatever the mind of man can conceive and believe, it can achieve. – Napoleon hill')) as test
Results:
I am a little late in the game, but I believe this is more functional and it works with any language, including Russian, German, Thai, Vietnamese etc.
It will make uppercase anything after ' or - or . or ( or ) or space (obviously :).
CREATE FUNCTION [dbo].[fnToProperCase]( #name nvarchar(500) )
RETURNS nvarchar(500)
AS
BEGIN
declare #pos int = 1
, #pos2 int
if (#name <> '')--or #name = lower(#name) collate SQL_Latin1_General_CP1_CS_AS or #name = upper(#name) collate SQL_Latin1_General_CP1_CS_AS)
begin
set #name = lower(rtrim(#name))
while (1 = 1)
begin
set #name = stuff(#name, #pos, 1, upper(substring(#name, #pos, 1)))
set #pos2 = patindex('%[- ''.)(]%', substring(#name, #pos, 500))
set #pos += #pos2
if (isnull(#pos2, 0) = 0 or #pos > len(#name))
break
end
end
return #name
END
GO
If you're in SSIS importing data that has mixed cased and need to do a lookup on a column with proper case, you'll notice that the lookup fails where the source is mixed and the lookup source is proper. You'll also notice you can't use the right and left functions is SSIS for SQL Server 2008r2 for derived columns. Here's a solution that works for me:
UPPER(substring(input_column_name,1,1)) + LOWER(substring(input_column_name, 2, len(input_column_name)-1))
Here is a version that uses a sequence or numbers table rather than a loop. You can modify the WHERE clause to suite your personal rules for when to convert a character to upper case. I have just included a simple set that will upper case any letter that is proceeded by a non-letter with the exception of apostrophes. This does how ever mean that 123apple would have a match on the "a" because "3" is not a letter. If you want just white-space (space, tab, carriage-return, line-feed), you can replace the pattern '[^a-z]' with '[' + Char(32) + Char(9) + Char(13) + Char(10) + ']'.
CREATE FUNCTION String.InitCap( #string nvarchar(4000) ) RETURNS nvarchar(4000) AS
BEGIN
-- 1. Convert all letters to lower case
DECLARE #InitCap nvarchar(4000); SET #InitCap = Lower(#string);
-- 2. Using a Sequence, replace the letters that should be upper case with their upper case version
SELECT #InitCap = Stuff( #InitCap, n, 1, Upper( SubString( #InitCap, n, 1 ) ) )
FROM (
SELECT (1 + n1.n + n10.n + n100.n + n1000.n) AS n
FROM (SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS n1
CROSS JOIN (SELECT 0 AS n UNION SELECT 10 UNION SELECT 20 UNION SELECT 30 UNION SELECT 40 UNION SELECT 50 UNION SELECT 60 UNION SELECT 70 UNION SELECT 80 UNION SELECT 90) AS n10
CROSS JOIN (SELECT 0 AS n UNION SELECT 100 UNION SELECT 200 UNION SELECT 300 UNION SELECT 400 UNION SELECT 500 UNION SELECT 600 UNION SELECT 700 UNION SELECT 800 UNION SELECT 900) AS n100
CROSS JOIN (SELECT 0 AS n UNION SELECT 1000 UNION SELECT 2000 UNION SELECT 3000) AS n1000
) AS Sequence
WHERE
n BETWEEN 1 AND Len( #InitCap )
AND SubString( #InitCap, n, 1 ) LIKE '[a-z]' /* this character is a letter */
AND (
n = 1 /* this character is the first `character` */
OR SubString( #InitCap, n-1, 1 ) LIKE '[^a-z]' /* the previous character is NOT a letter */
)
AND (
n < 3 /* only test the 3rd or greater characters for this exception */
OR SubString( #InitCap, n-2, 3 ) NOT LIKE '[a-z]''[a-z]' /* exception: The pattern <letter>'<letter> should not capatolize the letter following the apostrophy */
)
-- 3. Return the modified version of the input
RETURN #InitCap
END
On Server Server 2016 and newer, you can use STRING_SPLIT
with t as (
select 'GOOFYEAR Tire and Rubber Company' as n
union all
select 'THE HAPPY BEAR' as n
union all
select 'MONK HOUSE SALES' as n
union all
select 'FORUM COMMUNICATIONS' as n
)
select
n,
(
select ' ' + (
upper(left(value, 1))
+ lower(substring(value, 2, 999))
)
from (
select value
from string_split(t.n, ' ')
) as sq
for xml path ('')
) as title_cased
from t
Example
If you know all the data is just a single word here's a solution. First update the column to all lower and then run the following
update tableName set columnName =
upper(SUBSTRING(columnName, 1, 1)) + substring(columnName, 2, len(columnName)) from tableName
A slight modification to #Galwegian's answer - which turns e.g. St Elizabeth's into St Elizabeth'S.
This modification keeps apostrophe-s as lowercase where the s comes at the end of the string provided or the s is followed by a space (and only in those circumstances).
create function properCase(#text as varchar(8000))
returns varchar(8000)
as
begin
declare #reset int;
declare #ret varchar(8000);
declare #i int;
declare #c char(1);
declare #d char(1);
if #text is null
return null;
select #reset = 1, #i = 1, #ret = '';
while (#i <= len(#text))
select
#c = substring(#text, #i, 1),
#d = substring(#text, #i+1, 1),
#ret = #ret + case when #reset = 1 or (#reset=-1 and #c!='s') or (#reset=-1 and #c='s' and #d!=' ') then upper(#c) else lower(#c) end,
#reset = case when #c like '[a-za-z]' then 0 when #c='''' then -1 else 1 end,
#i = #i + 1
return #ret
end
It turns:
st elizabeth's into St Elizabeth's
o'keefe into O'Keefe
o'sullivan into O'Sullivan
Others' comments that different solutions are preferable for non-English input remain the case.
In Oracle SQL or PostgreSQL, just do:
SELECT INITCAP(title) FROM data;
In SQL Server, define the function first as in below, then:
SELECT dbo.InitCap(title) FROM data;
Define dbo.InitCap():
-- Drop the function if it already exists
IF OBJECT_ID('dbo.InitCap') IS NOT NULL
DROP FUNCTION dbo.InitCap;
GO
-- Implementing Oracle INITCAP function
CREATE FUNCTION dbo.InitCap (#inStr VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #outStr VARCHAR(8000) = LOWER(#inStr),
#char CHAR(1),
#alphanum BIT = 0,
#len INT = LEN(#inStr),
#pos INT = 1;
-- Iterate through all characters in the input string
WHILE #pos <= #len BEGIN
-- Get the next character
SET #char = SUBSTRING(#inStr, #pos, 1);
-- If the position is first, or the previous characater is not alphanumeric
-- convert the current character to upper case
IF #pos = 1 OR #alphanum = 0
SET #outStr = STUFF(#outStr, #pos, 1, UPPER(#char));
SET #pos = #pos + 1;
-- Define if the current character is non-alphanumeric
IF ASCII(#char) <= 47 OR (ASCII(#char) BETWEEN 58 AND 64) OR
(ASCII(#char) BETWEEN 91 AND 96) OR (ASCII(#char) BETWEEN 123 AND 126)
SET #alphanum = 0;
ELSE
SET #alphanum = 1;
END
RETURN #outStr;
END
GO
The link I posted above is a great option that addresses the main issue: that we can never programmatically account for all cases (Smith-Jones, von Haussen, John Smith M.D.), at least not in an elegant manner. Tony introduces the concept of an exception / break character to deal with these cases.
Anyways, building on Cervo's idea (upper all lower chars preceded by space), the replace statements could be wrapped up in a single table based replace instead. Really, any low/up character combination could be inserted into #alpha and the statement would not change:
declare #str nvarchar(8000)
declare #alpha table (low nchar(1), up nchar(1))
set #str = 'ALL UPPER CASE and SOME lower ÄÄ ÖÖ ÜÜ ÉÉ ØØ ĈĈ ÆÆ'
-- stage the alpha (needs number table)
insert into #alpha
-- A-Z / a-z
select nchar(n+32),
nchar(n)
from dbo.Number
where n between 65 and 90 or
n between 192 and 223
-- append space at start of str
set #str = lower(' ' + #str)
-- upper all lower case chars preceded by space
select #str = replace(#str, ' ' + low, ' ' + up)
from #Alpha
select #str
It would make sense to maintain a lookup of exceptions to take care of The von Neumann's, McCain's, DeGuzman's, and the Johnson-Smith's.
Borrowed and improved on #Richard Sayakanit answer. This handles multiple words. Like his answer, this doesn't use any UDFs, only built-in functions (STRING_SPLIT and STRING_AGG) and it's pretty fast. STRING_AGG requires SQL Server 2017 but you can always use the STUFF/XML trick. Won't handle every exception but can work great for many requirements.
SELECT StateName = 'North Carolina'
INTO #States
UNION ALL
SELECT 'Texas'
;WITH cteData AS
(
SELECT
UPPER(LEFT(value, 1)) +
LOWER(RIGHT(value, LEN(value) - 1)) value, op.StateName
FROM #States op
CROSS APPLY STRING_SPLIT(op.StateName, ' ') AS ss
)
SELECT
STRING_AGG(value, ' ')
FROM cteData c
GROUP BY StateName
Recently had to tackle this and came up with the following after nothing quite hit everything I wanted. This will do an entire sentence, cases for special word handling. We also had issues with single character 'words' that a lot of the simpler methods handle but not the more complicated methods. Single return variable, no loops or cursors either.
CREATE FUNCTION ProperCase(#Text AS NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS BEGIN
DECLARE #return NVARCHAR(MAX)
SELECT #return = COALESCE(#return + ' ', '') + Word FROM (
SELECT CASE
WHEN LOWER(value) = 'llc' THEN UPPER(value)
WHEN LOWER(value) = 'lp' THEN UPPER(value) --Add as many new special cases as needed
ELSE
CASE WHEN LEN(value) = 1
THEN UPPER(value)
ELSE UPPER(LEFT(value, 1)) + (LOWER(RIGHT(value, LEN(value) - 1)))
END
END AS Word
FROM STRING_SPLIT(#Text, ' ')
) tmp
RETURN #return
END
I think you will find that the following is more efficient:
IF OBJECT_ID('dbo.ProperCase') IS NOT NULL
DROP FUNCTION dbo.ProperCase
GO
CREATE FUNCTION dbo.PROPERCASE (
#str VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
SET #str = ' ' + #str
SET #str = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( #str, ' a', ' A'), ' b', ' B'), ' c', ' C'), ' d', ' D'), ' e', ' E'), ' f', ' F'), ' g', ' G'), ' h', ' H'), ' i', ' I'), ' j', ' J'), ' k', ' K'), ' l', ' L'), ' m', ' M'), ' n', ' N'), ' o', ' O'), ' p', ' P'), ' q', ' Q'), ' r', ' R'), ' s', ' S'), ' t', ' T'), ' u', ' U'), ' v', ' V'), ' w', ' W'), ' x', ' X'), ' y', ' Y'), ' z', ' Z')
RETURN RIGHT(#str, LEN(#str) - 1)
END
GO
The replace statement could be cut and pasted directly into a SQL query. It is ultra ugly, however by replacing #str with the column you are interested in, you will not pay a price for an implicit cursor like you will with the udfs thus posted. I find that even using my UDF it is much more efficient.
Oh and instead of generating the replace statement by hand use this:
-- Code Generator for expression
DECLARE #x INT,
#c CHAR(1),
#sql VARCHAR(8000)
SET #x = 0
SET #sql = '#str' -- actual variable/column you want to replace
WHILE #x < 26
BEGIN
SET #c = CHAR(ASCII('a') + #x)
SET #sql = 'REPLACE(' + #sql + ', '' ' + #c+ ''', '' ' + UPPER(#c) + ''')'
SET #x = #x + 1
END
PRINT #sql
Anyway it depends on the number of rows. I wish you could just do s/\b([a-z])/uc $1/, but oh well we work with the tools we have.
NOTE you would have to use this as you would have to use it as....SELECT dbo.ProperCase(LOWER(column)) since the column is in uppercase. It actually works pretty fast on my table of 5,000 entries (not even one second) even with the lower.
In response to the flurry of comments regarding internationalization I present the following implementation that handles every ascii character relying only on SQL Server's Implementation of upper and lower. Remember, the variables we are using here are VARCHAR which means that they can only hold ASCII values. In order to use further international alphabets, you have to use NVARCHAR. The logic would be similar but you would need to use UNICODE and NCHAR in place of ASCII AND CHAR and the replace statement would be much more huge....
-- Code Generator for expression
DECLARE #x INT,
#c CHAR(1),
#sql VARCHAR(8000),
#count INT
SEt #x = 0
SET #count = 0
SET #sql = '#str' -- actual variable you want to replace
WHILE #x < 256
BEGIN
SET #c = CHAR(#x)
-- Only generate replacement expression for characters where upper and lowercase differ
IF #x = ASCII(LOWER(#c)) AND #x != ASCII(UPPER(#c))
BEGIN
SET #sql = 'REPLACE(' + #sql + ', '' ' + #c+ ''', '' ' + UPPER(#c) + ''')'
SET #count = #count + 1
END
SET #x = #x + 1
END
PRINT #sql
PRINT 'Total characters substituted: ' + CONVERT(VARCHAR(255), #count)
Basically the premise of the my method is trading pre-computing for efficiency. The full ASCII implementation is as follows:
IF OBJECT_ID('dbo.ProperCase') IS NOT NULL
DROP FUNCTION dbo.ProperCase
GO
CREATE FUNCTION dbo.PROPERCASE (
#str VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN
SET #str = ' ' + #str
SET #str = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#str, ' a', ' A'), ' b', ' B'), ' c', ' C'), ' d', ' D'), ' e', ' E'), ' f', ' F'), ' g', ' G'), ' h', ' H'), ' i', ' I'), ' j', ' J'), ' k', ' K'), ' l', ' L'), ' m', ' M'), ' n', ' N'), ' o', ' O'), ' p', ' P'), ' q', ' Q'), ' r', ' R'), ' s', ' S'), ' t', ' T'), ' u', ' U'), ' v', ' V'), ' w', ' W'), ' x', ' X'), ' y', ' Y'), ' z', ' Z'), ' š', ' Š'), ' œ', ' Œ'), ' ž', ' Ž'), ' à', ' À'), ' á', ' Á'), ' â', ' Â'), ' ã', ' Ã'), ' ä', ' Ä'), ' å', ' Å'), ' æ', ' Æ'), ' ç', ' Ç'), ' è', ' È'), ' é', ' É'), ' ê', ' Ê'), ' ë', ' Ë'), ' ì', ' Ì'), ' í', ' Í'), ' î', ' Î'), ' ï', ' Ï'), ' ð', ' Ð'), ' ñ', ' Ñ'), ' ò', ' Ò'), ' ó', ' Ó'), ' ô', ' Ô'), ' õ', ' Õ'), ' ö', ' Ö'), ' ø', ' Ø'), ' ù', ' Ù'), ' ú', ' Ú'), ' û', ' Û'), ' ü', ' Ü'), ' ý', ' Ý'), ' þ', ' Þ'), ' ÿ', ' Ÿ')
RETURN RIGHT(#str, LEN(#str) - 1)
END
GO
Is it too late to go back and get the un-uppercased data?
The von Neumann's, McCain's, DeGuzman's, and the Johnson-Smith's of your client base may not like the result of your processing...
Also, I'm guessing that this is intended to be a one-time upgrade of the data? It might be easier to export, filter/modify, and re-import the corrected names into the db, and then you can use non-SQL approaches to name fixing...
Here is another variation I found on the SQLTeam.com Forums #
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=47718
create FUNCTION PROPERCASE
(
--The string to be converted to proper case
#input varchar(8000)
)
--This function returns the proper case string of varchar type
RETURNS varchar(8000)
AS
BEGIN
IF #input IS NULL
BEGIN
--Just return NULL if input string is NULL
RETURN NULL
END
--Character variable declarations
DECLARE #output varchar(8000)
--Integer variable declarations
DECLARE #ctr int, #len int, #found_at int
--Constant declarations
DECLARE #LOWER_CASE_a int, #LOWER_CASE_z int, #Delimiter char(3), #UPPER_CASE_A int, #UPPER_CASE_Z int
--Variable/Constant initializations
SET #ctr = 1
SET #len = LEN(#input)
SET #output = ''
SET #LOWER_CASE_a = 97
SET #LOWER_CASE_z = 122
SET #Delimiter = ' ,-'
SET #UPPER_CASE_A = 65
SET #UPPER_CASE_Z = 90
WHILE #ctr <= #len
BEGIN
--This loop will take care of reccuring white spaces
WHILE CHARINDEX(SUBSTRING(#input,#ctr,1), #Delimiter) > 0
BEGIN
SET #output = #output + SUBSTRING(#input,#ctr,1)
SET #ctr = #ctr + 1
END
IF ASCII(SUBSTRING(#input,#ctr,1)) BETWEEN #LOWER_CASE_a AND #LOWER_CASE_z
BEGIN
--Converting the first character to upper case
SET #output = #output + UPPER(SUBSTRING(#input,#ctr,1))
END
ELSE
BEGIN
SET #output = #output + SUBSTRING(#input,#ctr,1)
END
SET #ctr = #ctr + 1
WHILE CHARINDEX(SUBSTRING(#input,#ctr,1), #Delimiter) = 0 AND (#ctr <= #len)
BEGIN
IF ASCII(SUBSTRING(#input,#ctr,1)) BETWEEN #UPPER_CASE_A AND #UPPER_CASE_Z
BEGIN
SET #output = #output + LOWER(SUBSTRING(#input,#ctr,1))
END
ELSE
BEGIN
SET #output = #output + SUBSTRING(#input,#ctr,1)
END
SET #ctr = #ctr + 1
END
END
RETURN #output
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
Just learned about InitCap().
Here is some sample code:
SELECT ID
,InitCap(LastName ||', '|| FirstName ||' '|| Nvl(MiddleName,'')) AS RecipientName
FROM SomeTable
This worked in SSMS:
Select Jobtitle,
concat(Upper(LEFT(jobtitle,1)), SUBSTRING(jobtitle,2,LEN(jobtitle))) as Propercase
From [HumanResources].[Employee]
Sadly, I am proposing yet another function. This one seems faster than most, but only capitalizes the first letter of words separated by spaces. I've checked that the input is not null, and that it works if you have multiple spaces somewhere in the middle of the string. I'm cross applying the length function so I don't have to call it twice. I would have thought that SQL Server would have cached that value. Caveat emptor.
CREATE OR ALTER FUNCTION dbo.ProperCase(#value varchar(MAX)) RETURNS varchar(MAX) AS
BEGIN
RETURN (SELECT STRING_AGG(CASE lv WHEN 0 THEN '' WHEN 1 THEN UPPER(value)
ELSE UPPER(LEFT(value,1)) + LOWER(RIGHT(value,lv-1)) END,' ')
FROM STRING_SPLIT(TRIM(#value),' ') AS ss
CROSS APPLY (SELECT LEN(VALUE) lv) AS reuse
WHERE #value IS NOT NULL)
END
This function has worked for me
create function [dbo].Pascal (#string varchar(max))
returns varchar(max)
as
begin
declare #Index int
,#ResultString varchar(max)
set #Index = 1
set #ResultString = ''
while (#Index < LEN(#string) + 1)
begin
if (#Index = 1)
begin
set #ResultString += UPPER(SUBSTRING(#string, #Index, 1))
set #Index += 1
end
else if (
(
SUBSTRING(#string, #Index - 1, 1) = ' '
or SUBSTRING(#string, #Index - 1, 1) = '-'
or SUBSTRING(#string, #Index + 1, 1) = '-'
)
and #Index + 1 <> LEN(#string) + 1
)
begin
set #ResultString += UPPER(SUBSTRING(#string, #Index, 1))
set #Index += 1
end
else
begin
set #ResultString += LOWER(SUBSTRING(#string, #Index, 1))
set #Index += 1
end
end
if (##ERROR <> 0)
begin
set #ResultString = #string
end
return replace(replace(replace(#ResultString, ' ii', ' II'), ' iii', ' III'), ' iv', ' IV')
end
Copy and paste your data into MS Word and use built in text-conversion to "Capitalize Each Word". Compare against your original data to address exceptions. Can't see any way around manually sidestepping "MacDonald" and "IBM" type exceptions but this was how I got it done FWIW.
I know the devil is in the detail (especially where people's personal data is concerned), and that it would be very nice to have properly capitalised names, but the above kind of hassle is why the pragmatic, time-conscious amongst us use the following:
SELECT UPPER('Put YoUR O'So oddLy casED McWeird-nAme von rightHERE here')
In my experience, people are fine seeing THEIR NAME ... even when it's half way through a sentence.
Refer to: the Russians used a pencil!

How to capitalize the first letter of each word in a string in SQL Server

What’s the best way to capitalize the first letter of each word in a string in SQL Server.
From http://www.sql-server-helper.com/functions/initcap.aspx
CREATE FUNCTION [dbo].[InitCap] ( #InputString varchar(4000) )
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #PrevChar CHAR(1)
DECLARE #OutputString VARCHAR(255)
SET #OutputString = LOWER(#InputString)
SET #Index = 1
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
SET #PrevChar = CASE WHEN #Index = 1 THEN ' '
ELSE SUBSTRING(#InputString, #Index - 1, 1)
END
IF #PrevChar IN (' ', ';', ':', '!', '?', ',', '.', '_', '-', '/', '&', '''', '(')
BEGIN
IF #PrevChar != '''' OR UPPER(#Char) != 'S'
SET #OutputString = STUFF(#OutputString, #Index, 1, UPPER(#Char))
END
SET #Index = #Index + 1
END
RETURN #OutputString
END
GO
There is a simpler/smaller one here (but doesn't work if any row doesn't have spaces, "Invalid length parameter passed to the RIGHT function."):
http://www.devx.com/tips/Tip/17608
As a table-valued function:
CREATE FUNCTION dbo.InitCap(#v AS VARCHAR(MAX))
RETURNS TABLE
AS
RETURN
WITH a AS (
SELECT (
SELECT UPPER(LEFT(value, 1)) + LOWER(SUBSTRING(value, 2, LEN(value))) AS 'data()'
FROM string_split(#v, ' ')
ORDER BY CHARINDEX(value,#v)
FOR XML PATH (''), TYPE) ret)
SELECT CAST(a.ret AS varchar(MAX)) ret from a
GO
Note that string_split requires COMPATIBILITY_LEVEL 130.
A variation of the one I've been using for quite some time is:
CREATE FUNCTION [widget].[properCase](#string varchar(8000)) RETURNS varchar(8000) AS
BEGIN
SET #string = LOWER(#string)
DECLARE #i INT
SET #i = ASCII('a')
WHILE #i <= ASCII('z')
BEGIN
SET #string = REPLACE( #string, ' ' + CHAR(#i), ' ' + CHAR(#i-32))
SET #i = #i + 1
END
SET #string = CHAR(ASCII(LEFT(#string, 1))-32) + RIGHT(#string, LEN(#string)-1)
RETURN #string
END
You can easily modify to handle characters after items other than spaces if you wanted to.
Another solution without using the loop - pure set-based approach with recursive CTE
create function [dbo].InitCap (#value varchar(max))
returns varchar(max) as
begin
declare
#separator char(1) = ' ',
#result varchar(max) = '';
with r as (
select value, cast(null as varchar(max)) [x], cast('' as varchar(max)) [char], 0 [no] from (select rtrim(cast(#value as varchar(max))) [value]) as j
union all
select right(value, len(value)-case charindex(#separator, value) when 0 then len(value) else charindex(#separator, value) end) [value]
, left(r.[value], case charindex(#separator, r.value) when 0 then len(r.value) else abs(charindex(#separator, r.[value])-1) end ) [x]
, left(r.[value], 1)
, [no] + 1 [no]
from r where value > '')
select #result = #result +
case
when ascii([char]) between 97 and 122
then stuff(x, 1, 1, char(ascii([char])-32))
else x
end + #separator
from r where x is not null;
set #result = rtrim(#result);
return #result;
end
If you are looking for the answer to the same question in Oracle/PLSQL then you may use the function INITCAP. Below is an example for the attribute dname from a table department which has the values ('sales', 'management', 'production', 'development').
SQL> select INITCAP(dname) from department;
INITCAP(DNAME)
--------------------------------------------------
Sales
Management
Production
Development
;WITH StudentList(Name) AS (
SELECT CONVERT(varchar(50), 'Carl-VAN')
UNION SELECT 'Dean o''brian'
UNION SELECT 'Andrew-le-Smith'
UNION SELECT 'Eddy thompson'
UNION SELECT 'BOBs-your-Uncle'
), Student AS (
SELECT CONVERT(varchar(50), UPPER(LEFT(Name, 1)) + LOWER(SUBSTRING(Name, 2, LEN(Name)))) Name,
pos = PATINDEX('%[-'' ]%', Name)
FROM StudentList
UNION ALL
SELECT CONVERT(varchar(50), LEFT(Name, pos) + UPPER(SUBSTRING(Name, pos + 1, 1)) + SUBSTRING(Name, pos + 2, LEN(Name))) Name,
pos = CASE WHEN PATINDEX('%[-'' ]%', RIGHT(Name, LEN(Name) - pos)) = 0 THEN 0 ELSE pos + PATINDEX('%[-'' ]%', RIGHT(Name, LEN(Name) - pos)) END
FROM Student
WHERE pos > 0
)
SELECT Name
FROM Student
WHERE pos = 0
ORDER BY Name
This will result in:
Andrew-Le-Smith
Bobs-Your-Uncle
Carl-Van
Dean O'Brian
Eddy Thompson
Using a recursive CTE set based query should out perform a procedural while loop query.
Here I also have made my separate to be 3 different characters [-' ] instead of 1 for a more advanced example. Using PATINDEX as I have done allows me to look for many characters. You could also use CHARINDEX on a single character and this function excepts a third parameter StartFromPosition so I could further simply my 2nd part of the recursion of the pos formula to (assuming a space): pos = CHARINDEX(' ', Name, pos + 1).
The suggested function works fine, however, if you do not want to create any function this is how I do it:
select ID,Name
,string_agg(concat(upper(substring(value,1,1)),lower(substring(value,2,len(value)-1))),' ') as ModifiedName
from Table_Customer
cross apply String_Split(replace(trim(Name),' ',' '),' ')
where Name is not null
group by ID,Name;
The above query split the words by space (' ') and create different rows of each having one substring, then convert the first letter of each substring to upper and keep remaining as lower. The final step is to string aggregate based on the key.
BEGIN
DECLARE #string varchar(100) = 'asdsadsd asdad asd'
DECLARE #ResultString varchar(200) = ''
DECLARE #index int = 1
DECLARE #flag bit = 0
DECLARE #temp varchar(2) = ''
WHILE (#Index <LEN(#string)+1)
BEGIN
SET #temp = SUBSTRING(#string, #Index-1, 1)
--select #temp
IF #temp = ' ' OR #index = 1
BEGIN
SET #ResultString = #ResultString + UPPER(SUBSTRING(#string, #Index, 1))
END
ELSE
BEGIN
SET #ResultString = #ResultString + LOWER(SUBSTRING(#string, #Index, 1))
END
SET #Index = #Index+ 1--increase the index
END
SELECT #ResultString
END
It can be as simple as this:
DECLARE #Name VARCHAR(500) = 'Roger';
SELECT #Name AS Name, UPPER(LEFT(#Name, 1)) + SUBSTRING(#Name, 2, LEN(#Name)) AS CapitalizedName;
fname is column name if fname value is akhil then UPPER(left(fname,1)) provide capital First letter(A) and substring function SUBSTRING(fname,2,LEN(fname)) provide(khil) concate both using + then result is (Akhil)
select UPPER(left(fname,1))+SUBSTRING(fname,2,LEN(fname)) as fname
FROM [dbo].[akhil]
On SQL Server 2016+ using JSON which gives guaranteed order of the words:
CREATE FUNCTION [dbo].[InitCap](#Text NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
RETURN STUFF((
SELECT ' ' + UPPER(LEFT(s.value,1)) + LOWER(SUBSTRING(s.value,2,LEN(s.value)))
FROM OPENJSON('["' + REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#Text,'\','\\'),'"','\"'),CHAR(9),'\t'),CHAR(10),'\n'),' ','","') + '"]') s
ORDER BY s.[key]
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'');
END
GO
CREATE FUNCTION [dbo].[Capitalize](#text NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) AS
BEGIN
DECLARE #result NVARCHAR(MAX) = '';
DECLARE #c NVARCHAR(1);
DECLARE #i INT = 1;
DECLARE #isPrevSpace BIT = 1;
WHILE #i <= LEN(#text)
BEGIN
SET #c = SUBSTRING(#text, #i, 1);
SET #result += IIF(#isPrevSpace = 1, UPPER(#c), LOWER(#c));
SET #isPrevSpace = IIF(#c LIKE '[ -]', 1, 0);
SET #i += 1;
END
RETURN #result;
END
GO
DECLARE #sentence NVARCHAR(100) = N'i-thINK-this soLUTION-works-LiKe-a charm';
PRINT dbo.Capitalize(#sentence);
-- I-Think-This Solution-Works-Like-A Charm
Here is the simplest one-liner to do this:
SELECT LEFT(column, 1)+ lower(RIGHT(column, len(column)-1) ) FROM [tablename]
I was looking for the best way to capitalize and i recreate simple sql script
how to use SELECT dbo.Capitalyze('this is a test with multiple spaces')
result "This Is A Test With Multiple Spaces"
CREATE FUNCTION Capitalyze(#input varchar(100) )
returns varchar(100)
as
begin
declare #index int=0
declare #char as varchar(1)=' '
declare #prevCharIsSpace as bit=1
declare #Result as varchar(100)=''
set #input=UPPER(LEFT(#input,1))+LOWER(SUBSTRING(#input, 2, LEN(#input)))
set #index=PATINDEX('% _%',#input)
if #index=0
set #index=len(#input)
set #Result=substring(#input,0,#index+1)
WHILE (#index < len(#input))
BEGIN
SET #index = #index + 1
SET #char=substring(#input,#index,1)
if (#prevCharIsSpace=1)
begin
set #char=UPPER(#char)
if (#char=' ')
set #char=''
end
if (#char=' ')
set #prevCharIsSpace=1
else
set #prevCharIsSpace=0
set #Result=#Result+#char
--print #Result
END
--print #Result
return #Result
end
IF OBJECT_ID ('dbo.fnCapitalizeFirstLetterAndChangeDelimiter') IS NOT NULL
DROP FUNCTION dbo.fnCapitalizeFirstLetterAndChangeDelimiter
GO
CREATE FUNCTION [dbo].[fnCapitalizeFirstLetterAndChangeDelimiter] (#string NVARCHAR(MAX), #delimiter NCHAR(1), #new_delimeter NCHAR(1))
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #result NVARCHAR(MAX)
SELECT #result = '';
IF (LEN(#string) > 0)
DECLARE #curr INT
DECLARE #next INT
BEGIN
SELECT #curr = 1
SELECT #next = CHARINDEX(#delimiter, #string)
WHILE (LEN(#string) > 0)
BEGIN
SELECT #result =
#result +
CASE WHEN LEN(#result) > 0 THEN #new_delimeter ELSE '' END +
UPPER(SUBSTRING(#string, #curr, 1)) +
CASE
WHEN #next <> 0
THEN LOWER(SUBSTRING(#string, #curr+1, #next-2))
ELSE LOWER(SUBSTRING(#string, #curr+1, LEN(#string)-#curr))
END
IF (#next > 0)
BEGIN
SELECT #string = SUBSTRING(#string, #next+1, LEN(#string)-#next)
SELECT #next = CHARINDEX(#delimiter, #string)
END
ELSE
SELECT #string = ''
END
END
RETURN #result
END
GO