在SQL Server中仅将每个句子的每个单词的首字母大写


18

我只想将SQL列中每个句子的每个单词的首字母大写。

例如,如果句子是:

我喜欢电影

然后我需要输出:

我喜欢电影

查询:

declare @a varchar(15) 

set @a = 'qWeRtY kEyBoArD'

select @a as [Normal text],
upper(@a) as [Uppercase text],
lower(@a) as [Lowercase text],
upper(left(@a,1)) + lower(substring(@a,2,len(@a))) as [Capitalize first letter only]

在这里,我只在专栏中对上,下和大写第一个字母(这里我只输入了一个随机单词)。

这是我的结果:

在此处输入图片说明

有这样做的可能性吗?

不使用用户定义的功能就可以得到结果吗?

我需要输出 Qwerty Keyboard


11
为什么要在sql server中执行此操作?您的表示层应该有效地处理!
金莎(Kin Shah)

您并不总是具有表示层,例如,在清理导入到SQL Server的不良数据时,并且您不想编写C#程序来做到这一点。是的,您可以投资CLR功能,但是如何快速又有效地工作。
Jeffrey Roughgarden

Answers:


26
declare @a varchar(30); 

set @a = 'qWeRtY kEyBoArD TEST<>&''"X';

select stuff((
       select ' '+upper(left(T3.V, 1))+lower(stuff(T3.V, 1, 1, ''))
       from (select cast(replace((select @a as '*' for xml path('')), ' ', '<X/>') as xml).query('.')) as T1(X)
         cross apply T1.X.nodes('text()') as T2(X)
         cross apply (select T2.X.value('.', 'varchar(30)')) as T3(V)
       for xml path(''), type
       ).value('text()[1]', 'varchar(30)'), 1, 1, '') as [Capitalize first letter only];

这首先通过将所有空格替换为空标记将字符串转换为XML <X/>。然后使用切碎XML以使每行得到一个单词nodes()。为了使行回到一个值,它使用了for xml path技巧。


8
该代码正是为什么我永远不会在SQL中做到这一点的原因。不是说答案是错误的-这是要求的。但是标准SQL不适用于这种类型的字符串操作。基于CLR的功能将起作用,或者仅在表示层上起作用。
TomTom

8
@TomTom它看起来很复杂,但是与它生成的查询计划相比并没有什么,而且按照任何标准它都不会很快。然而,探究查询中实际发生的情况以及为什么以这种方式编写查询是很有教育意义和乐趣的。可以使用字符串拆分功能(数字表)解决该问题。难以避免的for xml path级联技巧。除非您选择CLR,否则如果速度和效率很重要,那将是最佳选择。
Mikael Eriksson

15

在SQL Server 2016中,可以使用R执行此操作,例如

-- R capitalisation code stolen from here:
-- http://stackoverflow.com/questions/6364783/capitalize-the-first-letter-of-both-words-in-a-two-word-string

EXEC sp_execute_external_script
    @language = N'R',
    @script = N'
simpleCap <- function(x) {
  s <- strsplit(x, " ")[[1]]
  paste(toupper(substring(s, 1,1)), substring(s, 2),
        sep="", collapse=" ")
}             

OutputDataSet <- as.data.frame((sapply(as.vector(InputDataSet$xtext), simpleCap)))',
    @input_data_1 = N'SELECT LOWER(testString) xtext FROM dbo.testStrings'
WITH RESULT SETS ( ( properCase VARCHAR(50) NOT NULL ) );

是否应该是另一个问题:)


哦,你绝对不应该。有时,这是最糟糕的选择,或者正如OP所述,它们需要快速而肮脏的。
乔纳森·菲特

12

也许我很傻,但是根据提供的一些内容检查了下面编写的查询,这似乎效率更高(取决于索引编制)。

该代码有点愚蠢,但是并没有说,如果它看起来很愚蠢,但可以正常工作,那么它就不是愚蠢的。

Begin

    Declare @text Varchar(30);

    Set @text = 'qWeRtY kEyBoArD TEST<>&''"X';

    Declare @1 Varchar(2)= ' a'
      , @2 Varchar(2)= ' b'
      , @3 Varchar(2)= ' c'
      , @4 Varchar(2)= ' d'
      , @5 Varchar(2)= ' e'
      , @6 Varchar(2)= ' f'
      , @7 Varchar(2)= ' g'
      , @8 Varchar(2)= ' h'
      , @9 Varchar(2)= ' i'
      , @10 Varchar(2)= ' j'
      , @11 Varchar(2)= ' k'
      , @12 Varchar(2)= ' l'
      , @13 Varchar(2)= ' m'
      , @14 Varchar(2)= ' n'
      , @15 Varchar(2)= ' o'
      , @16 Varchar(2)= ' p'
      , @17 Varchar(2)= ' q'
      , @18 Varchar(2)= ' r'
      , @19 Varchar(2)= ' s'
      , @20 Varchar(2)= ' t'
      , @21 Varchar(2)= ' u'
      , @22 Varchar(2)= ' v'
      , @23 Varchar(2)= ' w'
      , @24 Varchar(2)= ' x'
      , @25 Varchar(2)= ' y'
      , @26 Varchar(2)= ' z';

Set @text=' '+@text

    Select  LTrim(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(Lower(@text) ,
                                                              @1 , Upper(@1)) ,
                                                              @2 , Upper(@2)) ,
                                                              @3 , Upper(@3)) ,
                                                              @4 , Upper(@4)) ,
                                                              @5 , Upper(@5)) ,
                                                              @6 , Upper(@6)) ,
                                                              @7 , Upper(@7)) ,
                                                              @8 , Upper(@8)) ,
                                                              @9 , Upper(@9)) ,
                                                              @10 , Upper(@10)) ,
                                                              @11 , Upper(@11)) ,
                                                              @12 , Upper(@12)) ,
                                                              @13 , Upper(@13)) ,
                                                              @14 , Upper(@14)) ,
                                                              @15 , Upper(@15)) ,
                                                              @16 , Upper(@16)) ,
                                                              @17 , Upper(@17)) ,
                                                              @18 , Upper(@18)) ,
                                                              @19 , Upper(@19)) ,
                                                              @20 , Upper(@20)) ,
                                                            @21 , Upper(@21)) ,
                                                    @22 , Upper(@22)) , @23 ,
                                            Upper(@23)) , @24 , Upper(@24)) ,
                            @25 , Upper(@25)) , @26 , Upper(@26)));


end

2
这是一个伟大而可怕的答案。我特别喜欢您在一开始就紧紧抓住的空间,然后在结尾处去除空间。
BradC

2
@BradC令人毛骨悚然,但是当我尝试将它与针对数据集的XML方法相比时,它的运行成本似乎很小!
克里斯J

9

另一个选择是通过SQLCLR处理此问题。在.NET中甚至还可以使用一种方法来执行此操作:TextInfo.ToTitleCase(在中System.Globalization)。此方法将大写每个单词的第一个字母,小写其余的字母。与此处的其他建议不同,它会跳过全部大写的单词(假设它们是首字母缩写词)。当然,如果需要这种行为,那么更新任何T-SQL建议也可以很容易地做到这一点。

.NET方法的一个好处是它可以将大写字母作为补充字符。例如:DESERET SMALL LETTER OW具有DESERET CAPITAL LETTER OW 的大写映射(当我将其粘贴到此处时,两者都显示为框),但是该UPPER()功能不会将小写版本更改为大写,即使当前数据库的默认排序规则设置为Latin1_General_100_CI_AS_SC。这似乎与MSDN文档保持一致,该文档没有列出,UPPER并且LOWER在功能表中使用_SC归类:归类和Unicode支持:补充字符时行为不同。

SELECT N'DESERET SMALL LETTER OW' AS [Label], NCHAR(0xD801)+NCHAR(0xDC35) AS [Thing]
UNION ALL
SELECT N'DESERET CAPITAL LETTER OW' AS [Label], NCHAR(0xD801)+NCHAR(0xDC0D) AS [Thing]
UNION ALL
SELECT N'SmallButShouldBeCapital' AS [Label], UPPER(NCHAR(0xD801)+NCHAR(0xDC35)) AS [Thing]

返回值(已放大,因此您实际上可以看到补充字符):

查询结果显示UPPER()无法与补充字符一起使用

您可以使用Unicode.org上的以下搜索功能,查看全部(和当前)小写字符并变为大写字符(您可以通过向下滚动直到找到“ DESERET”来查看补充字符)。部分,或者只是点击Control-F并搜索该词):

http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3AChanges_When_Titlecased%3DYes%3A%5D

虽然说实话,但这并不是一个巨大的好处,因为怀疑每个人是否实际上都在使用任何可以用标题区分大小写的补充字符。无论哪种方式,下面都是SQLCLR代码:

using System.Data.SqlTypes;
using System.Globalization;
using Microsoft.SqlServer.Server;

public class TitleCasing
{
    [return: SqlFacet(MaxSize = 4000)]
    [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true)]
    public static SqlString TitleCase([SqlFacet(MaxSize = 4000)] SqlString InputString)
    {
        TextInfo _TxtInf = new CultureInfo(InputString.LCID).TextInfo;
        return new SqlString (_TxtInf.ToTitleCase(InputString.Value));
    }
}

这是@MikaelEriksson的建议 -稍作修改以处理NVARCHAR数据以及所有大写的跳过单词(以更紧密地匹配.NET方法的行为)-以及对该T-SQL实现和SQLCLR实现:

SET NOCOUNT ON;
DECLARE @a NVARCHAR(50);

SET @a = N'qWeRtY kEyBoArD TEST<>&''"X one&TWO '
         + NCHAR(0xD801)+NCHAR(0xDC28)
         + N'pPLe '
         + NCHAR(0x24D0) -- ⓐ  Circled "a"
         + NCHAR(0xFF24) -- D  Full-width "D"
         + N'D u'
         + NCHAR(0x0308) -- ̈  (combining diaeresis / umlaut)
         + N'vU'
         + NCHAR(0x0308) -- ̈  (combining diaeresis / umlaut)
         + N'lA';
SELECT @a AS [Original];

SELECT STUFF((
       SELECT N' '
              + IIF(UPPER(T3.V) <> T3.V COLLATE Latin1_General_100_BIN2, 
                    UPPER(LEFT(T3.V COLLATE Latin1_General_100_CI_AS_SC, 1))
                    + LOWER(STUFF(T3.V COLLATE Latin1_General_100_CI_AS_SC, 1, 1, N'')),
                    T3.V)
       FROM (SELECT CAST(REPLACE((SELECT @a AS N'*' FOR XML PATH('')), N' ', N'<X/>')
                    AS XML).query('.')) AS T1(X)
       CROSS APPLY T1.X.nodes('text()') AS T2(X)
       CROSS APPLY (SELECT T2.X.value('.', 'NVARCHAR(70)')) AS T3(V)
       FOR XML PATH(''), TYPE
       ).value('text()[1]', 'NVARCHAR(70)') COLLATE Latin1_General_100_CI_AS_SC, 1, 1, N'')
                AS [Capitalize first letter only];

SELECT dbo.TitleCase(@a) AS [ToTitleCase];

查询结果显示通过SQLCLR输出的T-SQL XML代码和ToTitleCase

行为上的另一个差异是,此特定的T-SQL实现仅在空格上拆分,而该ToTitleCase()方法将大多数非字母视为单词分隔符(因此,“ one&TWO”部分的处理有所不同)。

两种实现方式都能正确处理组合序列。“üvÜlA”中的每个重音字母都由一个基本字母和一个组合的音调/变音符号(每个字母上方的两个点)组成,并且在两种测试中都正确转换为其他情况。

最后,SQLCLR版本的一个意外缺点是,在进行各种测试时,我发现了.NET代码中与处理圆圈字母有关的错误(现已在Microsoft Connect上进行了报道 -更新:Connect已被移至/dev/null-从字面上看-因此如果问题仍然存在,我可能需要重新提交)。.NET库将带圆圈的字母视为单词分隔符,这就是为什么它没有将“ⓐDD”转换为“Ⓐdd”的原因。


费耶

TextInfo.ToTitleCase现在,在SQL#的免费版本(我写过)中,封装了上述方法的SQLCLR函数已作为String_ToTitleCaseString_ToTitleCase4k提供

😺


5

作为Mikael Eriksson答案的替代方法,您可以考虑在多行选择语句中使用专有的T-SQL变量设置处理。

在SQL Server中,当将变量设置为SELECT语句的一部分时,每一行将执行设置逻辑的迭代。

人们通常使用此方法来连接字符串,尽管这种方法不受支持,并且存在一些正式记录的问题。官方问题与特定的ORDER BY特性有关,在这里我们不需要它,因此也许这是一个安全的选择。

在这里,我们遍历字母的26个字母,如果在字母前加空格,则将它们替换为大写字母。(如您在问题中所做的那样,我们首先通过将首字母大写并其余小写来准备字符串。)

SQL有点复杂,因为它需要使用Tally Table(一个数字表)来生成26次替换操作。您可以制作一个方便的内联表值用户定义函数(TVF)来生成该数字表,甚至可以使用物理表。

此选项的缺点是它不能成为嵌入式TVF的一部分,因为它需要涉及设置变量。因此,如果要将此方法应用于输出的列,则需要将其包装到多语句TVF或标量用户定义函数中。

但是,它的查询计划要简单得多,而且可能比XML方法要快得多。您可能会说它也更容易理解(尤其是如果您有自己的理货表)。

DECLARE
    @a VARCHAR(15) = 'qWeRtY kEyBoArD';

SELECT
    @a = UPPER(LEFT(@a,1)) + LOWER(SUBSTRING(@a,2,LEN(@a)));

WITH TallyTableBase AS
(
    SELECT
        0 AS n
    FROM    (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) AS t(n)
)
SELECT
    @a = REPLACE(@a, ' ' + CHAR(n.n), ' ' + CHAR(n.n))
FROM        (
                SELECT      TOP 26 ROW_NUMBER() OVER (ORDER BY (SELECT 1)) + 64 AS n
                FROM        TallyTableBase a
                CROSS JOIN  TallyTableBase b
            ) AS n;

SELECT
    @a AS [NewValue];

(我使用一个更大的字符串进行了测试,XML解决方案大约是6ms到14ms。)

此解决方案还有许多其他限制。如所写,它假定排序规则不区分大小写,尽管您可以通过指定排序规则或在搜索项上运行LCASE 来消除此问题,但会降低性能。它还仅处理标准ASCII字母,并依赖于它们在字符集中的位置,因此对ñ无效。


3

假设您只想在空格后大写单词,这是您可以使用的另一种方法。

DECLARE @String VARCHAR(1000)
SET @String = 'qWeRtY kEyBoArD tEst'

/*
Set the string to all lower case and
add a space at the beginning to ensure
the first letter gets capitalized
in the CTE
*/
SET @String = LOWER(' ' + @String)  

/*
Use a Tally "Table" as a means of
replacing the letter after the space
with the capitalize version of the
letter
*/
;WITH TallyTable
AS
(
    SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as N
    FROM master.sys.all_columns a CROSS JOIN master.sys.all_columns b

)
SELECT @String = REPLACE(@String,SUBSTRING(@String,CHARINDEX(' ',@String,N), 2),UPPER(SUBSTRING(@String,CHARINDEX(' ',@String,N), 2)))
FROM TallyTable
WHERE CHARINDEX(' ',@String,N) <> 0

--Remove the space added to the beginning of the string earlier
SET @String = RIGHT(@String,LEN(@String) - 1)

1

可能不是防弹的,但我希望它对此线程有帮助。

DECLARE @t VARCHAR(50) = 'the quick brown fox jumps over the lazy dog', @i INT = 0

DECLARE @chk VARCHAR(1)

WHILE @i <= LEN(@t)
BEGIN
    SELECT @chk=SUBSTRING(@t,@i,1)
        IF @chk = CHAR(32)
        BEGIN
            SET @t = STUFF(@t,@i+1,1,UPPER(SUBSTRING(@t,@i+1,1)))
        END
    SET @i=@i+1
END
PRINT @t

0

以下是我在Firebird数据库中用于执行此操作的过程。可能可以清理很多,但这为我完成了工作。

set term ~;

Create Procedure EachWordCap

As

Declare Variable lcaption varchar(33);
Declare Variable lcurrentpos integer;
Declare Variable lstringlen integer;
begin
    for select ' ' || trim(lower(imagedata.imagename)) from imagedata
    where imagedata.imagename is not null and imagedata.imagename != ''
    into :lcaption
    do 
    begin
        lcurrentpos = 0;
        lstringlen = char_length(lcaption);
        while (lcurrentpos != 1) do
        begin
            lcurrentpos = position(' ', lcaption, iif(lcurrentpos = 0, 1,lcurrentpos)) + 1 ;
            lcaption = left(lcaption,lcurrentpos - 1) || upper(substring(lcaption from lcurrentpos for 1)) || right(lcaption,lstringlen - lcurrentpos);
        end
        --Put what you want to do with the text in here
    end
end~
set term ;~

0

递归CTE对于这种事情非常有用。

对于大型操作来说可能不是特别有效,但是在纯SQL select语句中确实允许这种操作:

declare @a varchar(100) 

set @a = 'tHe qUiCk bRoWn FOX jumps   OvEr The lAZy dOG';

WITH [CTE] AS (
  SELECT CAST(upper(Left(@a,1)) + lower(substring(@a,2,len(@a))) AS VARCHAR(100)) AS TEXT,
         CHARINDEX(' ',@a) AS NEXT_SPACE
  UNION ALL
  SELECT CAST(Left(TEXT,NEXT_SPACE) + upper(SubString(TEXT,NEXT_SPACE+1,1)) + SubString(TEXT,NEXT_SPACE+2,1000) AS VARCHAR(100)),
         CHARINDEX(' ',TEXT, NEXT_SPACE+1)
  FROM [CTE]
  WHERE NEXT_SPACE <> 0
)

SELECT TEXT
FROM [CTE]
WHERE NEXT_SPACE = 0

输出:

The Quick Brown Fox Jumps   Over The Lazy Dog

0

我喜欢这个版本。它很简单,可以用来创建函数,您只需要拥有正确版本的SQL Server:

WITH words
AS (
    SELECT upper(left(Value, 1)) + lower(substring(Value, 2, len(Value))) AS word
    FROM STRING_SPLIT('Lorem ipsum dolor sit amet.', ' ')
    )
SELECT STRING_AGG(words.word, ' ')
FROM words

哪个是正确的版本?
dezso

SQL Server(从2016年开始)
Cristi

-2
DECLARE @someString NVARCHAR(MAX) = 'In this WHILE LOOP example' 

DECLARE @result NVARCHAR(MAX) =Upper(SUBSTRING(@someString, 1, 1))

DECLARE @index INT =2 

WHILE LEN(@someString)>@index

BEGIN

SET @result= @result+CASE WHEN CHARINDEX(' ',@someString,@index)<>0 THEN LOWER(SUBSTRING(@someString, @index, CHARINDEX(' ',@someString,@index)-@index+1)) +Upper(SUBSTRING(@someString, CHARINDEX(' ',@someString,@index)+1, 1)) ELSE  LOWER(SUBSTRING(@someString,@index, LEN(@someString) )) END

SET @index=CASE WHEN CHARINDEX(' ',@someString,@index)<>0 THEN CHARINDEX(' ',@someString,@index)+2 ELSE  LEN(@someString)+1  END

 END

SELECT  @result 

希望对您有帮助...


欢迎数据库管理员!请说明您的查询如何解决作者的问题;没有解释的答案通常不会被很好地接受。
Glorfindel

-3

测试数据

declare @word varchar(100)
with good as (select 'good' as a union select 'nice' union select 'fine')
select @word = (SELECT TOP 1 a FROM good ORDER BY NEWID())

实作

select substring(Upper(@word),1,1) + substring(@word, 2, LEN(@word))

大写已经分开的单词很容易。我相信OP对如何识别字符串中的单词并大写每个单词感兴趣。
所有行业的乔恩'18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.