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

답변:


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가 언급했듯이 빠르고 더러운 것이 필요합니다.
Jonathan Fite

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 메소드와 비교해 보았을 때 약간의 비용으로 실행되는 것 같습니다!
Chris J

9

다른 옵션은 SQLCLR을 통해이를 처리하는 것입니다. TextInfo.ToTitleCase (in System.Globalization) : .NET에는 이미 사용 가능한 메소드가 있습니다 . 이 방법은 각 단어의 첫 글자를 대문자로하고 나머지 글자는 소문자로 만듭니다. 여기의 다른 제안과 달리, 대문자로되어있는 단어를 모두 대문자로 생략합니다. 물론이 동작이 필요한 경우 T-SQL 제안을 업데이트하여 쉽게 수행 할 수 있습니다.

.NET 방법의 한 가지 이점은 보조 문자 인 대문자를 사용할 수 있다는 것입니다. 예를 들어 : 데 저렛 SMALL LETTER OW는 의 대문자 매핑이 저렛 CAPITAL LETTER OW (내가 여기에 붙여 상자 등 모두 쇼 업) ,하지만 UPPER()기능은 경우에도 대문자로 소문자 버전을 변경하지 않습니다 현재 데이터베이스의 기본 데이터 정렬이로 설정되어 Latin1_General_100_CI_AS_SC있습니다. 이것은 데이터 정렬 : 데이터 정렬 및 유니 코드 지원 : 보충 문자를 사용할 때 다르게 동작하는 기능 차트 UPPER와 목록에없는 MSDN 설명서와 일치하는 것 같습니다 .LOWER_SC

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()방법에서는 대부분의 비 문자가 단어 분리 자로 간주되므로 "하나 & TWO"부분 처리의 차이가 있습니다.

두 구현 모두 시퀀스 결합을 올바르게 처리합니다. "üvÜlA"의 각 강조 문자는 기본 문자와 결합 분음 / 움라우트 (각 문자 위의 두 점)로 구성되며 두 테스트에서 다른 문자로 올바르게 변환됩니다.

마지막으로, SQLCLR 버전에 하나 예상치 못한 단점은 다양한 테스트와 함께 오는, 지금 된 동그라미 편지 (자사의 취급에 관한 .NET 코드에서 버그를 발견하다 보고 마이크로 소프트 연결에 - UPDATE를 : 연결이되었습니다 /dev/null말 그대로- 로 이동 했으므로 문제가 여전히 존재하면 다시 제출해야 할 수도 있습니다). .NET 라이브러리는 Circled Letters를 단어 구분 기호로 취급하므로 "ⓐDD"를 "Ⓐdd"로 바꾸지 않습니다.


참고로

TextInfo.ToTitleCase위에서 언급 한 방법을 캡슐화하는 미리 수행 된 SQLCLR 함수 는 이제 SQL # 무료 버전 ( String_ToTitleCaseString_ToTitleCase4k ) 에서 사용할 수 있습니다 .

😺


5

Mikael Eriksson의 답변에 대한 대안으로 , 다중 행 select 문에서 변수 설정의 독점적 T-SQL 처리 사용을 고려할 수 있습니다.

SQL Server에서 변수가 SELECT 문의 일부로 설정되면 각 행은 설정된 논리의 반복을 실행합니다.

지원되지 않고 공식적으로 문서화 된 문제 가 있지만 사람들은 종종이 방법사용하여 문자열을 연결 합니다 . 공식적인 문제는 특정 ORDER BY 특성과 관련이 있으므로 여기서는 필요하지 않으므로 안전한 옵션 일 수 있습니다.

여기에서 알파벳의 26 글자를 반복하고 공백이 있으면 대문자로 바꿉니다. (질문에서와 같이 첫 글자를 대문자로하고 나머지는 소문자로 만들어 문자열을 처음에 준비합니다.)

SQL은 Tally 테이블 (숫자 테이블)을 사용하여 수행중인 대체 작업을 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가 문자열 내에서 단어를 식별하고 각 단어를 대문자로 쓰는 방법에 관심이 있다고 생각합니다.
모든 거래의 존
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.