Wielką literą jest tylko pierwsza litera każdego słowa każdego zdania w programie SQL Server

18

Chcę używać tylko pierwszej litery każdego słowa każdego zdania w kolumnie SQL.

Na przykład jeśli zdanie brzmi:

'Lubię filmy'

następnie potrzebuję danych wyjściowych:

'Lubię filmy'

Pytanie:

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]

Tutaj zrobiłem górną, dolną i wielką literę tylko w mojej kolumnie (tutaj wstawiam tylko losowe słowo).

Oto moje wyniki:

wprowadź opis zdjęcia tutaj

Czy są na to jakieś możliwości?

Jakieś możliwości uzyskania wyników bez korzystania z funkcji zdefiniowanej przez użytkownika?

Potrzebuję wyjścia Qwerty Keyboard

Marin Mohanadas
źródło
11
Dlaczego chcesz to zrobić na serwerze SQL? Twoja warstwa prezentacji powinna sobie z tym poradzić efektywnie!
Kin Shah
Nie zawsze masz warstwę prezentacji, np. Podczas czyszczenia złych danych zaimportowanych do SQL Server, i nie chcesz do tego pisać programu w języku C #. Tak, możesz zainwestować w funkcję CLR, ale co powiesz na coś szybkiego i brudnego, co działa.
Jeffrey Roughgarden

Odpowiedzi:

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];

Najpierw konwertuje ciąg na XML, zastępując wszystkie spacje pustym znacznikiem <X/>. Następnie niszczy XML, aby uzyskać jedno słowo w wierszu za pomocą nodes(). Aby przywrócić wiersze do jednej wartości, wykorzystuje for xml pathlewę.

Mikael Eriksson
źródło
8
I właśnie ten kod nigdy nie zrobiłbym tego w SQL. Nie powiedzieć, że odpowiedź jest zła - o to poproszono. Ale standardowy SQL jest absurdalnie nieodpowiedni do tego typu manipulacji ciągami. Funkcja oparta na CLR działałaby lub po prostu działała na warstwie prezentacji.
TomTom
8
@TomTom Wygląda na skomplikowane, ale to nic w porównaniu z planem zapytań, który tworzy i nie będzie szybki według żadnego standardu. Jednak edukacyjne i zabawne jest zagłębianie się w to, co dzieje się w zapytaniu i dlaczego jest napisane tak, jak jest. Problem można rozwiązać za pomocą funkcji podziału na ciągi znaków (tablica liczb). Trudno ominąć for xml pathsztuczkę konkatenacji. Chyba że wybierzesz CLR, który byłby najlepszym rozwiązaniem, jeśli ważna jest szybkość i wydajność.
Mikael Eriksson
15

W SQL Server 2016 możesz to zrobić za pomocą R, np

-- 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 ) );

To, czy powinieneś, czy nie, to inne pytanie:)

wBob
źródło
och, zdecydowanie nie powinieneś. Czasami jest to najmniej zła opcja lub, jak wspomniano PO, potrzebują szybkiego i brudnego.
Jonathan Fite
12

Być może jestem głupi, ale sprawdzam poniższe zapytanie, które napisałem w stosunku do niektórych z podanych, wydaje się to nieco bardziej wydajne (w zależności od indeksowania).

Kod jest trochę głupi, ale nie ma powiedzenia, że ​​jeśli wygląda głupio, ale działa, to nie jest głupi.

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
Chris J
źródło
2
To świetna i okropna odpowiedź. Szczególnie podoba mi się przestrzeń, na którą zająłeś się na początku, a potem na końcu się rozebrał.
BradC
2
@BradC jest ohydny, ale kiedy wypróbowałem go w porównaniu z metodą XML w stosunku do zestawu danych, wydaje się, że działa on za ułamek kosztów!
Chris J
9

Inną opcją jest obsługiwanie tego za pośrednictwem SQLCLR. Istnieje nawet metoda dostępna w .NET, która to robi: TextInfo.ToTitleCase (in System.Globalization). W tej metodzie pierwsza litera każdego słowa będzie pisana dużymi literami, a pozostałe - małymi literami. W przeciwieństwie do innych propozycji tutaj, pomija także słowa pisane wielkimi literami, zakładając, że są to akronimy. Oczywiście, jeśli takie zachowanie jest pożądane, łatwo byłoby zaktualizować dowolną z sugestii T-SQL, aby to zrobić.

Jedną z zalet metody .NET jest to, że może ona zawierać duże litery, które są znakami uzupełniającymi. Na przykład: DESERET SMALL LETTER OW ma mapowanie wielkich liter DESERET CAPITAL LETTER OW (oba pokazują się jako pola po wklejeniu ich tutaj) , ale UPPER()funkcja nie zmienia wersji małych liter na duże litery, nawet gdy domyślne sortowanie dla bieżącej bazy danych jest ustawione na Latin1_General_100_CI_AS_SC. Wydaje się to spójne z dokumentacją MSDN, która nie zawiera tej listy, UPPERoraz LOWERw zestawieniu funkcji, które zachowują się inaczej podczas korzystania z _SCCollation: Collation i Unicode: Supplementary Characters .

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]

Zwraca (powiększony, aby można było zobaczyć postać dodatkową):

Wynik zapytania pokazujący, że GÓRA () nie działa ze znakiem uzupełniającym

Możesz zobaczyć pełną (i bieżącą) listę znaków pisanych małymi literami i przechodzić na wielkie litery, korzystając z następującej funkcji wyszukiwania na Unicode.org (możesz zobaczyć dodatkowe znaki przewijając w dół, aż dojdziesz do „DESERET” lub po prostu naciśnij Control-Fi wyszukaj to słowo):

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

Chociaż szczerze mówiąc, nie jest to ogromna korzyść, ponieważ wątpliwe jest, aby ktokolwiek faktycznie używał któregokolwiek z Postaci Uzupełniających, które można umieścić w tytułach. Tak czy inaczej, oto kod 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));
    }
}

Oto sugestia @ MikaelEriksson - nieznacznie zmodyfikowana, aby obsługiwać NVARCHARdane, a także pomijać słowa pisane wielkimi literami (aby lepiej pasowały do ​​zachowania metody .NET) - wraz z testem tej implementacji T-SQL i implementacja 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];

Wynik zapytania pokazujący dane wyjściowe kodu T-SQL XML i ToTitleCase za pośrednictwem SQLCLR

Inną różnicą w zachowaniu jest to, że ta konkretna implementacja T-SQL dzieli się tylko na spacje, podczas gdy ToTitleCase()metoda uważa , że większość liter niebędących literami stanowi separatory słów (stąd różnica w obsłudze części „one & TWO”).

Obie implementacje poprawnie obsługują łączenie sekwencji. Każda z akcentowanych liter w „üvÜlA” składa się z litery podstawowej i łączącej diaeresis / umlaut (dwie kropki nad każdą literą) i w obu testach są one poprawnie konwertowane na drugi przypadek.

Wreszcie, jedną nieoczekiwaną wadą wersji SQLCLR jest to, że przy opracowywaniu różnych testów znalazłem błąd w kodzie .NET związany z obsługą listów w kółku (który został teraz zgłoszony w Microsoft Connect - AKTUALIZACJA: Connect został przeniósł się do /dev/null- dosłownie - więc może być konieczne ponowne przesłanie tego, jeśli problem nadal występuje). Biblioteka .NET traktuje litery w kółko jako separatory słów, dlatego nie zamienia „ⓐDD” w „Ⓐdd” tak, jak powinno.


FYI

Wstępnie wykonana funkcja SQLCLR enkapsulująca TextInfo.ToTitleCasepowyższą metodę jest teraz dostępna w bezpłatnej wersji SQL # (którą napisałem) jako String_ToTitleCase i String_ToTitleCase4k .

😺

Solomon Rutzky
źródło
5

Jako alternatywę dla odpowiedzi Mikaela Erikssona można rozważyć użycie zastrzeżonej obsługi T-SQL ustawiania zmiennych w instrukcjach wyboru w wielu wierszach.

W SQL Server, gdy zmienna jest ustawiana jako część instrukcji SELECT, każdy wiersz wykona iterację logiki zestawu.

Ludzie często używają tej metody do łączenia łańcuchów, choć nie jest ona obsługiwana i istnieją pewne oficjalnie udokumentowane problemy . Oficjalny problem dotyczy określonych cech ORDER BY i nie jest nam potrzebny tutaj, więc być może jest to bezpieczna opcja.

Tutaj iterujemy 26 liter alfabetu i zastępujemy je wielkimi literami, jeśli są poprzedzone spacją. (Początkowo przygotowujemy ciąg znaków, zaczynając od dużej litery od pierwszej litery, a resztę małymi literami, tak jak w pytaniu.)

SQL jest trochę skomplikowany, ponieważ wymaga użycia tabeli liczb - tabeli liczb - do wygenerowania 26 iteracji zastępowania tego, co robi. Możesz utworzyć przydatną wbudowaną funkcję zdefiniowaną przez użytkownika (TVF) do tworzenia tabeli liczb lub możesz nawet użyć tabeli fizycznej.

Wadą tej opcji jest to, że nie może być częścią wbudowanego TVF, ponieważ musi obejmować ustawienie zmiennej. Więc jeśli chcesz zastosować tę metodę do kolumny wyników, musisz zawinąć ją w wielowątkową TVF lub skalarną funkcję zdefiniowaną przez użytkownika.

Jednak jego plan zapytań jest znacznie prostszy i prawdopodobnie znacznie szybszy niż metoda XML. Można argumentować, że jest to również łatwiejsze do zrozumienia (zwłaszcza jeśli masz własny stół do sumowania).

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];

(Przetestowałem to przy użyciu znacznie większego ciągu i było to około 6 ms w porównaniu do 14 ms dla rozwiązania XML).

Z tym rozwiązaniem wiąże się wiele dodatkowych ograniczeń. Jak napisano, zakłada sortowanie bez rozróżniania wielkości liter, chociaż można wyeliminować ten problem, określając sortowanie lub uruchamiając LCASE dla wyszukiwanego hasła, kosztem pewnej wydajności. Zajmuje się także tylko standardowymi literami ASCII i polega na ich umieszczeniu w zestawie znaków , więc nie zrobiłby nic z ñ.

Riley Major
źródło
3

Zakładając, że szukasz tylko wielkich liter po spacji, oto inny sposób, w jaki możesz to zrobić.

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)
TLaV
źródło
1

Może nie być kuloodporny, ale mam nadzieję, że jest to pomocny wkład w ten wątek.

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
Simon Jones
źródło
0

Poniżej znajduje się procedura, której użyłem w bazie danych Firebird, aby to zrobić. Prawdopodobnie można go dużo posprzątać, ale wykonał za mnie pracę.

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 ;~
Jeffrey Elkins
źródło
0

Rekurencyjne CTE są całkiem dobre do tego rodzaju rzeczy.

Prawdopodobnie nie jest szczególnie wydajny w przypadku dużych operacji, ale pozwala na tego rodzaju operacje w instrukcji SQL typu 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

Wynik:

The Quick Brown Fox Jumps   Over The Lazy Dog
Jerb
źródło
0

Podoba mi się ta wersja. Jest prosty i można go użyć do utworzenia funkcji, wystarczy mieć odpowiednią wersję programu 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
Cristi
źródło
Która jest odpowiednią wersją?
dezso
SQL Server (od 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 

Mam nadzieję, że pomoże ...

Alpha k A
źródło
Witamy administratorów baz danych! Proszę wyjaśnić, w jaki sposób zapytanie rozwiązuje problem autora; odpowiedzi bez wyjaśnienia na ogół nie są dobrze odbierane.
Glorfindel
-3

Dane testowe

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())

Realizacja

select substring(Upper(@word),1,1) + substring(@word, 2, LEN(@word))
Romiko Derbynew
źródło
Wielkie słowa, które już są oddzielne, są łatwe. Uważam, że OP jest zainteresowany tym, jak rozpoznać słowa w ciągu i wykorzystać każde z nich.
Jon of All Trades