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?
-
5Just keep in mind that properly changing upper-case text to proper-case text may require manual corrections in some, well, cases. With names, for example: I do not appreciate applications that misspell my name. – Dave DuPlantis Oct 23 '08 at 15:29
-
2There will be no function on earth that would get 'DAVE DUPLANTIS' right. Uppercasing data is a big WTF itself, because most of the time it is merely a presentational issue. – Tomalak Oct 23 '08 at 16:33
-
1I know a Macdonald who becomes furious when he's styled as MacDonald. And I'd appreciate the proper casing of O'Keefe, too, please. – DOK Oct 23 '08 at 16:57
-
1@Tomalak: quite right, which is why you should accept mixed-case data and preserve it so when the choice is yours. Totally agree about the WTF part ... particularly if you accept "international" characters. – Dave DuPlantis Oct 23 '08 at 17:04
-
It is a cultural issue, too. In my experience, the British and the French are into the habit of uppercasing surnames wherever they get a chance to. I just don't get it, it's adding no value. – Tomalak Oct 24 '08 at 07:33
-
We do work for a bank that often provides us with all upper case name records and expects us to convert them to proper case. Wow. DuPont, TaNiqa, IbNSaa'ad...whew! – Brian Battles Oct 13 '17 at 14:13
25 Answers
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 Ææ

- 26,542
- 16
- 152
- 170

- 332,285
- 67
- 532
- 628
-
1This is definitely the most international friendly solution. It has my vote. The only assumption here is that a space separates the words. – Cervo Oct 23 '08 at 16:13
-
True. The question author has not been specific on that, but it would be easy to allow other white space as well. Line breaks should also be accounted for, but this also depends on the input. Thanks for the vote. :-) – Tomalak Oct 23 '08 at 16:27
-
3Could it be the index should start at 1? The first substring( ,0,1) returns
. I'm running sqlserver2005 – jan Aug 19 '09 at 10:25 -
12You should probably include an apostrophe as a white-space character by default so a name like `O'DONNELL` won't change to `O'donnell`. – JustinStolle Sep 22 '11 at 20:15
-
1Thanks. Seemed that I will need to have LTRIM() for its return value. Using 2015. – Irawan Soetomo Jul 21 '15 at 04:00
-
@IrawanSoetomo I would not think so, the function does not add any whitespace to its output. – Tomalak Jul 21 '15 at 10:48
-
2@Tomalak the @i variable should start at one, otherwise a space is prepended to the output – Jakub Jul 30 '15 at 20:11
-
I've used extra condition to change only currently upper case strings `and Value COLLATE Latin1_General_CS_AS =UPPER(Value) `---http://stackoverflow.com/questions/3969059/sql-case-sensitive-string-compare. Also noticed that it doesn't work with zh-HK language – Michael Freidgeim Dec 06 '16 at 06:12
-
1@Michael Last time I checked there is no uppercase/lowercase in Chinese. What would you expect to happen? – Tomalak Dec 06 '16 at 07:59
-
I expected to see input string returned unchanged, but it returned '???' May be changing varchar to Nvarchar will fix it. But in my table I had localeid in a separate column and I excluded zh-HK rows from the query. Thank you for the useful function. – Michael Freidgeim Dec 06 '16 at 12:05
-
I'm pretty sure using a Unicode data type will make a difference. You can use `NVARCHAR(MAX)` for unlimited string length, too. – Tomalak Dec 06 '16 at 12:08
-
I've edited function to SET @i = 1 to avoid extra LTRIM call – Michael Freidgeim Dec 20 '16 at 00:21
-
-
Because that's the non-breaking space, it looks and works like a regular space character but most people do not think of it when defining where to break a string into words. – Tomalak May 22 '17 at 16:17
-
10Excellent little function. Not that the O.P. asked for it, but if anybody wants to change it so that it does *not* ignore already-lower-case words, and converts them as well, e.g. "tom bombadil" to "Tom Bombadil", just change this one line -- `SET @o = @o + @c` to `SET @o = @o + UPPER(@c)` . =) – NateJ Jun 14 '17 at 18:02
-
This has an issue if a capitalised string is bracketed. Any quick ideas? – Optimaximal May 02 '18 at 09:55
-
@Optimaximal Try to adjust the characters considered whitespace. This is a little bit tricky because square brackets have meaning to `LIKE`, so you will need to escape them if you want to match *actual* bracket characters. Modify the `LIKE` clause accordingly, compare [MSDN](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql?view=sql-server-2017#pattern-matching-with-the-escape-clause). – Tomalak May 02 '18 at 10:11
-
I've tried this function at SQL Server `14.0.1000.169`, but the text still all lower case. any solution or compatibility issue? – Wildan Muhlis May 25 '18 at 08:15
-
@WildanMuhlis That's not a compatibility issue. The function does not proper-case lowecase words. – Tomalak May 25 '18 at 09:14
-
1Azure Synapse does not allow SELECT within a function (which sucks), so this is a life saver. Thank you! – mateoc15 Mar 31 '22 at 12:06
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.

- 3,264
- 3
- 34
- 59

- 41,475
- 16
- 112
- 158
-
21
-
3Tried this with SQL Server 2008 and all kind of accents, worked like a charm. It depends in fact on the collation – Baptiste Dec 08 '16 at 11:23
UPDATE titles
SET title =
UPPER(LEFT(title, 1)) +
LOWER(RIGHT(title, LEN(title) - 1))

- 695
- 6
- 12
-
13FYI this works for single word values, but not multiple word values. So "NORTH CAROLINA" becomes "North carolina" instead of the exected "North Carolina". – molaro May 02 '14 at 13:56
-
6+1 as simple single word solution which worked well for me. The only - you can get error if `title` is empty. – Serg Aug 08 '14 at 07:59
-
@molaro do a split on space and operate with each word individually. Good solution but a little restrictive on the length of possibilities. For future viewers, may want to split on your sentence terminator and First-Case the first word in the sentence. – GoldBishop May 01 '17 at 15:56
-
2
-
1@Serg I edited the code so it doesn't run on error on zero length strings using `SUBSTRING` instead of `RIGHT` – robotik Jul 03 '20 at 09:30
-
This function could be called `dbo.fn_SentenceCase`, NOT ProperCase, since it only uppercases the very first letter of the whole string. – alejandrob Apr 20 '23 at 17:53
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.

- 133,383
- 43
- 204
- 250
-
I have to vote here too. IT is internationally safe and uses someone else's library which is probably full of all sorts of checks. YOu can't go wrong here :) – Cervo Oct 23 '08 at 23:27
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:

- 607
- 8
- 15
-
Can you describe why it's better then Tomalak's ToProperCase function http://stackoverflow.com/questions/230138/sql-server-make-all-upper-case-to-proper-case-title-case/230224#230224 ? – Michael Freidgeim Dec 06 '16 at 12:12
-
1Based on the examples given with this answer and Tomalak's along with Tomalak's description of what it does ("leaves lowercase words alone") this answer is better. I didn't verify Tomalak's, but this answer does Proper Case the input (as far as my needs can verify). "Proper case is any text that is written with each of the first letters of every word being capitalized." - https://www.computerhope.com/jargon/p/proper-case.htm – Morvael Apr 10 '19 at 09:41
-
1Instead of your `IF @Char IN (...)` line, you can use `if (patindex('[A-Za-z]', @Char) = 0)` – Andrew Jens Jun 01 '22 at 04:50
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

- 93
- 1
- 3
-
-
2Jack's House is not a person's name. O'Brian, O'Connell are names :) If you're not deal exclusively with people's name a change is necessary. – Alansoft Mar 06 '20 at 13:45
-
2Jack's House could easily be the name of a business, though. – John Luther Barnhart Nov 13 '20 at 19:33
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))

- 10,717
- 12
- 59
- 83

- 41
- 1
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

- 439
- 2
- 8
- 20
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

- 412
- 5
- 14
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

- 5,113
- 11
- 55
- 88
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

- 1,795
- 1
- 27
- 48
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
intoSt Elizabeth's
o'keefe
intoO'Keefe
o'sullivan
intoO'Sullivan
Others' comments that different solutions are preferable for non-English input remain the case.

- 352
- 1
- 3
- 16
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

- 181
- 1
- 1
- 7
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

- 9,092
- 3
- 40
- 55
-
1
-
Do you still view this as US-centric solution as it references unicode chars. I know the original posting used A-Z ascii resultset, but the point of the solution is that its a table driven pairing of upper:lower chars. The replace statement just refers to the table. – nathan_jr Oct 27 '08 at 16:35
-
edit: I updated example to use your sample input. Thanks for any feedback – nathan_jr Oct 27 '08 at 16:36
-
Is there an "un-elegant" way of handling most known variations for names that are not hyphenated? Like 'Mc', O'C, and what not? – Merritt Jun 17 '09 at 15:29
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

- 1,497
- 14
- 18
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.

- 27
- 1
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

- 2,333
- 21
- 23
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

- 3,112
- 1
- 24
- 27
-
2Yeah. Your alphabet has 26 characters only. Mine has more. How about Greek? Or Turkish? – Tomalak Oct 23 '08 at 15:54
-
I would argue that the other solutions do the same. But I have included the code generator. You can just add additional sections for your alphabet. You would have to have everything as NVARCHAR and use NCHAR and UNICODE to convert. – Cervo Oct 23 '08 at 16:00
-
I think that the more characters you have the less efficient it will get. But heck in some character sets each symbol represents an entire word so the whole proper case problem doesn't apply.... – Cervo Oct 23 '08 at 16:01
-
Also I doubt anyone uses every unicode character at once. So probably just code generate the replace for the 2 or 3 alphabets you use. – Cervo Oct 23 '08 at 16:03
-
2That doesn't mean you should use half-baked English-only solutions just because you expect the data "to be in your alphabet, probably". As far as the Latin writing system goes, there is lower/upper case defined for (almost) any character. Conversion functions are more about correctness than speed. – Tomalak Oct 23 '08 at 16:05
-
I will admit Tomalak your solution is better in that it lowercases all the characters except the first one in the word which is more portable. The other solution voted as the accepted answer seems to be english centric as well. In fact I need to vote yours up. – Cervo Oct 23 '08 at 16:10
-
But for the 99% of people who use only their own alphabet, it is better just to use a set based approach, especially if they have millions of records. – Cervo Oct 23 '08 at 16:11
-
IMHO, *that* is a dangerous misconception. Most of the time, one's own "view of the world" is incomplete. You end up creating solutions that are as limited as your experience/imagination, and that blow up when you encounter a case you have not thought of. – Tomalak Oct 23 '08 at 16:18
-
For example, I would never ever come up with my own implementation of UPPER() or LOWER(), because I know it will be wrong or incomplete. Even if my implementation was three orders of magnitude faster for ASCII characters than that of SQL Server. – Tomalak Oct 23 '08 at 16:21
-
My new generator depends entirely on SQL Server's Implementation of UPPER and LOWER along with the fact that there are 255 ASCII characters. For UNICODE you need to use unicode functions and the unicode upper limit (maybe not practical)...but here the ascii chars shouldn't change mostly. – Cervo Oct 23 '08 at 16:34
-
If they do change by applying a SQL Server patch, just rerun the code generator and re-create the function. – Cervo Oct 23 '08 at 16:34
-
BTW: Your second approach accounts for all the 255 characters *in one code page*. How about the other code pages? – Tomalak Oct 23 '08 at 16:36
-
I'm going to have to bow out now (no time). But my guess is that you could generate one expression per code page and do an if statement based on it. It is complicated but it is probably just another loop. Assuming there is a way to check the code page a variable uses. The approach is the same – Cervo Oct 23 '08 at 16:42
-
Just with more options and if statements so as not to do all that replacing in one super replace statements... It is getting more complicated but the main approach is to trade storage (code storage) for speed as opposed to doing it all dynamically.. – Cervo Oct 23 '08 at 16:44
-
-
Then again, nobody will, because once broken ("DUPLANTIS") *this* is impossible to fix. – Tomalak Oct 23 '08 at 17:03
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...

- 11,153
- 5
- 28
- 33
-
The whole naming issue came up as possible downsides where discussed - there is no indication that the question author refers to data that contains names. – Tomalak Oct 24 '08 at 07:35
Just learned about InitCap()
.
Here is some sample code:
SELECT ID
,InitCap(LastName ||', '|| FirstName ||' '|| Nvl(MiddleName,'')) AS RecipientName
FROM SomeTable

- 4,811
- 8
- 38
- 72

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

- 5,983
- 11
- 29
- 45
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

- 335
- 2
- 6
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

- 623
- 7
- 9
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!

- 550
- 4
- 6
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.

- 37
- 4