Scrivi in ​​maiuscolo solo la prima lettera di ogni parola di ogni frase in SQL Server


18

Voglio mettere in maiuscolo solo la prima lettera di ogni parola di ogni frase in una colonna SQL.

Ad esempio, se la frase è:

'Mi piacciono i film'

allora ho bisogno dell'output:

'Mi piacciono i film'

Query:

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]

Qui ho scritto maiuscole, minuscole e maiuscole solo nella mia colonna (qui ho inserito solo una parola a caso).

Ecco i miei risultati:

inserisci qui la descrizione dell'immagine

C'è qualche possibilità per farlo?

Qualche possibilità per ottenere risultati senza utilizzare la funzione definita dall'utente?

Ho bisogno dell'output Qwerty Keyboard


11
Perché vuoi farlo all'interno di SQL Server? Il tuo livello di presentazione dovrebbe gestirlo in modo efficiente!
Kin Shah,

Non sempre si dispone di un livello di presentazione, ad esempio quando si puliscono i dati errati importati in SQL Server e non si desidera scrivere un programma C # per farlo. Sì, potresti investire in una funzione CLR, ma che ne dici di qualcosa di veloce e sporco che funzioni.
Jeffrey Roughgarden,

Risposte:


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

Questo prima converte la stringa in XML sostituendo tutti gli spazi con il tag vuoto <X/>. Quindi distrugge l'XML per ottenere una parola per riga usando nodes(). Per riportare le righe a un valore usa il for xml pathtrucco.


8
E quel codice è esattamente il motivo per cui non lo farei mai in SQL. Non dire che la risposta è sbagliata - questo è stato chiesto. Ma SQL standard è ridicolmente inadatto per questo tipo di manipolazione di stringhe. Una funzione basata su CLR avrebbe funzionato, o semplicemente facendolo sul livello di presentazione.
TomTom,

8
@TomTom Sembra complicato ma non è nulla rispetto al piano di query che produce e non sarà veloce per nessuno standard. È comunque istruttivo e divertente approfondire ciò che sta realmente accadendo nella query e perché è scritto così com'è. Il problema potrebbe essere risolto con una funzione di suddivisione in stringhe (tabella dei numeri). Difficile evitare il for xml pathtrucco per la concatenazione. A meno che non si scelga il CLR, che sarebbe l'opzione migliore se la velocità e l'efficienza fossero importanti.
Mikael Eriksson,

15

In SQL Server 2016 è possibile farlo con R, ad es

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

Se dovresti o meno è una domanda diversa:)


oh, sicuramente non dovresti. A volte è l'opzione meno negativa, o come menzionato dall'OP, hanno bisogno di una soluzione rapida e sporca.
Jonathan Fite,

12

Forse sono sciocco, ma controllando la query di seguito che ho scritto con alcuni dei dati forniti, questo sembra essere un po 'più efficiente (a seconda dell'indicizzazione).

Il codice è un po 'stupido, ma non c'è un detto che se sembra stupido ma funziona, non è stupido.

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
Questa è una risposta fantastica e orribile. Mi piace in particolare lo spazio su cui hai puntato all'inizio e poi spogliato alla fine.
BradC,

2
@BradC è orribile, ma quando l'ho provato rispetto al metodo XML su un set di dati sembra funzionare a una frazione del costo!
Chris J,

9

Un'altra opzione è gestirla tramite SQLCLR. Esiste anche un metodo già disponibile in .NET che consente di: TextInfo.ToTitleCase (inSystem.Globalization ). Questo metodo farà in maiuscolo la prima lettera di ogni parola e in minuscolo le lettere rimanenti. A differenza delle altre proposte qui, salta anche le parole in maiuscolo, assumendole come acronimi. Naturalmente, se si desidera questo comportamento, sarebbe abbastanza facile aggiornare qualsiasi suggerimento T-SQL per fare altrettanto.

Un vantaggio del metodo .NET è che può lettere maiuscole che sono caratteri supplementari. Ad esempio: DESERET LETTERA PICCOLA OW ha una mappatura maiuscola di DESERET LETTERA MAIUSCOLA (entrambi vengono visualizzati come caselle quando li incollo qui) , ma la UPPER()funzione non cambia la versione minuscola in maiuscola, anche quando il confronto predefinito per il database corrente è impostato su Latin1_General_100_CI_AS_SC. Ciò sembra coerente con la documentazione MSDN che non elenca UPPERe LOWERnel diagramma delle funzioni che si comportano in modo diverso quando si utilizza una funzione di _SCconfronto: regole di confronto e Unicode: caratteri supplementari .

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]

Restituisce (ingrandito in modo da poter effettivamente vedere il carattere supplementare):

Il risultato della query mostra UPPER () che non funziona con il carattere supplementare

Puoi vedere l'elenco completo (e attuale) di caratteri che sono in minuscolo e che cambiano in maiuscolo usando la seguente funzione di ricerca su Unicode.org (puoi vedere i caratteri supplementari scorrendo verso il basso fino ad arrivare a "DESERET" sezione, o basta premere Control-Fe cercare quella parola):

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

Anche se ad essere onesti, questo non è un grande vantaggio poiché è dubbio che qualcuno stia effettivamente usando uno dei Personaggi Supplementari che possono essere inseriti nel titolo. Ad ogni modo, ecco il codice 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));
    }
}

Ecco il suggerimento di @ MikaelEriksson - modificato leggermente per gestire i NVARCHARdati e saltare le parole che sono tutte in maiuscolo (per abbinare più da vicino il comportamento del metodo .NET) - insieme a un test dell'implementazione T-SQL e di l'implementazione 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];

Risultato della query che mostra l'output del codice XML T-SQL e ToTitleCase tramite SQLCLR

Un'altra differenza nel comportamento è che questa particolare implementazione di T-SQL si divide solo in spazi, mentre il ToTitleCase()metodo considera la maggior parte delle non lettere come separatori di parole (da cui la differenza nella gestione della parte "one & TWO").

Entrambe le implementazioni gestiscono la combinazione corretta delle sequenze. Ognuna delle lettere accentate in "üvÜlA" sono composte da una lettera base e una combinazione diaeresi / umlaut (i due punti sopra ogni lettera) e sono convertite correttamente nell'altro caso in entrambi i test.

Infine, uno svantaggio inaspettato della versione SQLCLR è che, presentando vari test, ho trovato un bug nel codice .NET relativo alla gestione delle lettere cerchiate (che ora è stato segnalato su Microsoft Connect - UPDATE: Connect è stato spostato a /dev/null- letteralmente - quindi potrebbe essere necessario inviarlo nuovamente se il problema persiste). La libreria .NET tratta le lettere cerchiate come separatori di parole, motivo per cui non trasforma "ⓐDD" in "Ⓐdd" come dovrebbe.


FYI

Una funzione SQLCLR precompilata che incapsula il TextInfo.ToTitleCasemetodo sopra menzionato è ora disponibile nella versione gratuita di SQL # (che ho scritto) come String_ToTitleCase e String_ToTitleCase4k .

😺


5

In alternativa alla risposta di Mikael Eriksson , potresti prendere in considerazione l'uso della gestione proprietaria T-SQL dell'impostazione della variabile nelle istruzioni di selezione multi-riga.

In SQL Server, quando una variabile viene impostata come parte di un'istruzione SELECT, ogni riga eseguirà un'iterazione della logica impostata.

Le persone spesso usano questo metodo per concatenare le stringhe, anche se non è supportato e ci sono alcuni problemi ufficialmente documentati . Il problema ufficiale riguarda determinate caratteristiche ORDER BY, e non ne abbiamo bisogno qui, quindi forse è un'opzione sicura.

Qui, ripetiamo le 26 lettere dell'alfabeto e le sostituiamo con una versione maiuscola se sono precedute da uno spazio. (Inizialmente prepariamo la stringa capitalizzando la prima lettera e rendendo il resto in minuscolo, come hai fatto nella tua domanda.)

L'SQL è un po 'complesso perché richiede l'uso di una Tally Table - una tabella di numeri - per generare le 26 iterazioni di sostituzione che sta facendo. È possibile creare una pratica funzione definita dall'utente (TVF) con valori di tabella incorporata per produrre quella tabella di numeri oppure è anche possibile utilizzare una tabella fisica.

Uno svantaggio di questa opzione è che non può far parte di un TVF in linea in quanto deve comportare l'impostazione di una variabile. Quindi, se si desidera applicare questo metodo a una colonna dell'output, è necessario inserirlo in una TVF multiistruzione o in una funzione scalare definita dall'utente.

Tuttavia, il suo piano di query è molto più semplice ed è probabilmente significativamente più veloce del metodo XML. Potresti anche sostenere che è più facile da capire (specialmente se hai la tua tabella di conteggio).

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

(Ho provato questo usando una stringa molto più grande ed era circa 6ms contro 14ms per la soluzione XML.)

Ci sono una serie di limitazioni aggiuntive con questa soluzione. Come scritto, presuppone un confronto senza distinzione tra maiuscole e minuscole, sebbene sia possibile eliminare tale problema specificando un confronto o eseguendo LCASE sul termine di ricerca, a scapito di alcune prestazioni. Si rivolge anche solo alle lettere ASCII standard e si basa sul loro posizionamento nel set di caratteri , quindi non farebbe nulla con ñ.


3

Supponendo che stai solo cercando di scrivere in maiuscolo seguendo uno spazio, ecco un altro modo in cui potresti farlo.

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

Potrebbe non essere a prova di proiettile ma spero sia un contributo utile a questo thread.

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

Di seguito è la procedura che ho usato in un database Firebird per fare questo. Probabilmente può essere ripulito molto ma ha fatto il lavoro per me.

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

I CTE ricorsivi sono abbastanza buoni per questo genere di cose.

Probabilmente non particolarmente efficiente per le operazioni di grandi dimensioni, ma consente questo tipo di operazione in un'istruzione select SQL pura:

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

Produzione:

The Quick Brown Fox Jumps   Over The Lazy Dog

0

Mi piace questa versione. È semplice e può essere utilizzato per creare una funzione, devi solo avere la versione corretta di 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

Qual è la versione giusta?
dezso

SQL Server (a partire dal 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 

Spero possa aiutare ...


Benvenuti negli amministratori del database! Spiegare in che modo la query risolve il problema dell'autore; le risposte senza spiegazione generalmente non vengono ricevute bene.
Glorfindel,

-3

Dati di test

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

Implementazione

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

Scrivere in maiuscolo parole già separate è facile. Credo che l'OP sia interessato a come identificare le parole all'interno di una stringa e capitalizzare ognuna di esse.
Jon of All Trades,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.