SQL Server:将所有大写字母转换为正确大小写/标题大小写

130

我有一个表格,它是全部大写导入的,我想把它转换成 Proper Case(每个单词首字母大写)。你们中有谁使用过哪些脚本来完成这个操作?


6
请记住,将大写文本正确转换为首字母大写的文本可能需要在某些情况下进行手动更正。例如,在姓名方面:我不喜欢拼错我的名字的应用程序。 - Dave DuPlantis
3
地球上没有任何一个函数能够正确识别“DAVE DUPLANTIS”。将数据转换为大写本身就是一个很奇怪的问题,因为大多数情况下它只是一个表现问题。 - Tomalak
1
我认识一个人名叫麦克唐纳,当他的名字被写成MacDonald时会很生气。我也希望正确地将O'Keefe的大小写调整一下。 - DOK
1
@Tomalak:非常正确,这就是为什么你应该接受大小写数据并保留它,这样当选择是你的时候。完全同意关于WTF部分...特别是如果你接受“国际”字符。 - Dave DuPlantis
这也是一个文化问题。根据我的经验,英国人和法国人习惯于在任何机会都将姓氏大写。我真的不明白,这并没有增加任何价值。 - Tomalak
我们为一家银行工作,他们经常提供全大写的名称记录,并期望我们将它们转换为正确的大小写。哇。杜邦,塔尼卡,伊布·恩萨阿德... 哎呀! - Brian Battles
25个回答

137

这个函数:

  • 将由空格分隔的所有“大写单词”转化为“ Proper Case ”
  • 不对“小写单词”进行任何处理
  • 可以正确地处理非英语字母表
  • 具有可移植性,因为它不使用最近SQL server版本的高级功能
  • 可以轻松更改以使用NCHAR和NVARCHAR支持Unicode,以及任何您认为适合的参数长度
  • 可以配置空格定义
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

结果:

dbo.ToProperCase('ALL UPPER CASE and    SOME lower ÄÄ ÖÖ ÜÜ ÉÉ ØØ ĈĈ ÆÆ')
-----------------------------------------------------------------
All Upper Case and      Some lower Ää Öö Üü Éé Øø Cc Ææ

3
索引应该从1开始吗?第一个子字符串( ,0,1)返回<empty>。我正在运行sqlserver2005。 - jan
12
默认情况下,您应该将撇号包含在空格字符中,这样像O'DONNELL这样的名称就不会变成O'donnell - JustinStolle
1
谢谢。看起来我需要使用LTRIM()函数来获取其返回值。使用2015版本。 - Irawan Soetomo
2
@Tomalak,@i变量应该从1开始,否则输出前会添加一个空格。 - Jakub
10
非常好的小函数。虽然原始问题并未要求,但如果有人想更改它以使其不会忽略已经是小写的单词并将它们也转换为大写,例如“tom bombadil”转换为“Tom Bombadil”,只需更改这一行代码 -- SET @o = @o + @cSET @o = @o + UPPER(@c) 即可。=) - NateJ
显示剩余15条评论

116

这里有一个可以解决问题的UDF...

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

虽然如此,您仍需使用它来更新您的数据。


21
这将导致非英语输入的崩溃。 - Tomalak
谢谢!第一次就成功了,甚至在Azure SQL Server上也可以运行 :) - Aaron
3
尝试在SQL Server 2008中使用各种口音,效果非常好。实际上,这取决于排序规则。 - Baptiste
我该如何将这个函数应用到我的SQL表格中? - culldog88
2
这有点荒谬,现在已经是2022年了,这个还是缺失的。 - SteveC
显示剩余2条评论

54

13
这适用于单词数值,但不适用于多个单词的数值。因此,“NORTH CAROLINA”变成了“North carolina”,而不是预期的“North Carolina”。 - molaro
7
对我而言,简单的单词解决方案是+1。唯一的缺点是,如果“title”为空,您可能会遇到错误。 - Serg
@molaro 在空格上进行拆分,并对每个单词进行操作。这是一个不错的解决方案,但对可能性的长度有些限制。对于未来的观众,可以考虑在句子终止符上进行拆分,并将句子中的第一个单词大写。 - GoldBishop
2
在末尾添加 WHERE title IS NOT NULL 来解决 @Serg 的问题。 - user1945782
1
@Serg 我修改了代码,使用SUBSTRING代替RIGHT,以便在长度为零的字符串上不会出现错误。 - robotik
这个函数应该被称为dbo.fn_SentenceCase,而不是ProperCase,因为它只将整个字符串的第一个字母大写。 - alejandrob

20
如果您可以在SQL Server中启用CLR(需要2005或更高版本),那么您可以创建一个CLR函数,使用TextInfo.ToTitleCase内置函数,这将使您能够仅使用几行代码以区域文化感知方式执行此操作。

我也要在这里投票。这个程序是国际安全的,使用了别人的库,里面可能包含各种检查。你在这里不会出错 :) - Cervo

14

我知道这是在这个帖子中发布得很晚,但是很值得一看。这个函数每次对我都有用,所以想分享它。

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

测试调用:

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

结果:

输入图像描述


你能描述一下为什么它比Tomalak的ToProperCase函数更好吗?https://dev59.com/hnVC5IYBdhLWcg3woStW#230224 - Michael Freidgeim
1
基于此答案所提供的示例以及Tomalak的描述(“保留小写单词”),此答案更好。我没有验证Tomalak的,但是根据我的需求,此答案确实将输入转换为Proper Case(即每个单词的第一个字母大写)。“Proper case是指每个单词的第一个字母都大写的文本。”-https://www.computerhope.com/jargon/p/proper-case.htm - Morvael
1
你可以使用if (patindex('[A-Za-z]', @Char) = 0)代替你的IF @Char IN (...)这行代码。 - Andrew Jens

8

我可能有些晚到,但我认为这种方法更实用,并且适用于任何语言,包括俄语、德语、泰语、越南语等等。 它将会大写任何在引号、连字符、句点、括号或空格(显然:)后面的内容。

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

这个程序运行良好,除了“jack's house”变成了“Jack'S House”。 - Damien
2
杰克的房子不是一个人的名字。O'Brian,O'Connell才是名字 :) 如果你不仅仅处理人名,那么需要进行更改。 - Alansoft
2
杰克的房子很容易成为一个商业名称。 - John Luther Barnhart

5

如果你在使用SSIS导入数据时遇到大小写混合的情况,并且需要在一个有正确大小写的列上进行查找,你会发现当源是混合的而查找源是正确的时查找失败。你还会注意到,在SQL Server 2008r2中,不能在派生列中使用right和left函数。以下是我的解决方案:

UPPER(substring(input_column_name,1,1)) + LOWER(substring(input_column_name, 2, len(input_column_name)-1))

KISS原则的一个很好的例子!谢谢。 - pstraton

4

在Server 2016及更新版本中,您可以使用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

示例


3
这里提供了一个使用数字序列表而不是循环的版本。您可以修改 WHERE 子句以适应您个人的规则,以确定何时将字符转换为大写。我只包含了一个简单的设置,它将在非字母前面跟随的任何字母变成大写,但撇号除外。这意味着 123apple 中的 "a" 会匹配,因为 "3" 不是字母。如果您只想要空格(空格、制表符、回车符、换行符),则可以将模式 ''[^a-z]'' 替换为 ''[' + 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

2
在Oracle SQL或PostgreSQL中,只需执行以下操作:
SELECT INITCAP(title) FROM data;

在SQL Server中,首先按照以下方式定义函数,然后:

SELECT dbo.InitCap(title) FROM data;

定义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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接