web-dev-qa-db-fra.com

Mettre en majuscule uniquement la première lettre de chaque mot de chaque phrase dans SQL Server

Je veux mettre en majuscule uniquement la première lettre de chaque mot de chaque phrase dans une colonne SQL.

Par exemple, si la phrase est:

'J'aime les films'

alors j'ai besoin de la sortie:

'J'aime les films'

Requete:

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]

Ici, j'ai fait en majuscule, en minuscule et en majuscule la première lettre uniquement dans ma colonne (ici, je mets juste un mot aléatoire).

Voici mes résultats:

enter image description here

Y a-t-il des possibilités de le faire?

Des possibilités d'obtenir des résultats sans utiliser la fonction définie par l'utilisateur?

J'ai besoin de la sortie Qwerty Keyboard

18
Marin Mohanadas
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];

Cela convertit d'abord la chaîne en XML en remplaçant tous les espaces par la balise vide <X/>. Ensuite, il détruit le XML pour obtenir un mot par ligne en utilisant nodes(). Pour ramener les lignes à une valeur, il utilise l'astuce for xml path.

26
Mikael Eriksson

Dans SQL Server 2016, vous pouvez le faire avec R, par exemple

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

Que vous deviez ou non est une question différente:)

15
wBob

Peut-être que je suis stupide, mais en vérifiant la requête ci-dessous que j'ai écrite par rapport à certaines des données fournies, cela semble être un peu plus efficace (selon l'indexation).

Le code est un peu stupide, mais n'y a-t-il pas un dicton selon lequel s'il semble stupide mais qu'il fonctionne, ce n'est pas stupide.

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
13
Chris J

Une autre option consiste à gérer cela via SQLCLR. Il existe même une méthode déjà disponible dans .NET qui fait cela: TextInfo.ToTitleCase (dans System.Globalization). Cette méthode mettra en majuscule la première lettre de chaque mot et minuscule les lettres restantes. Contrairement aux autres propositions ici, il saute également les mots qui sont en majuscules, en supposant qu'ils sont des acronymes. Bien sûr, si ce comportement est souhaité, il serait assez facile de mettre à jour l'une des suggestions T-SQL pour le faire également.

Un avantage de la méthode .NET est qu'elle peut utiliser des lettres majuscules qui sont des caractères supplémentaires. Par exemple: DESERET SMALL LETTER OW a un mappage en majuscules de DESERET CAPITAL LETTER OW (les deux apparaissent sous forme de boîtes lorsque je les colle ici), mais la fonction UPPER() ne change pas la version minuscule en majuscule, même lorsque le classement par défaut pour la base de données actuelle est défini sur Latin1_General_100_CI_AS_SC. Cela semble cohérent avec la documentation MSDN qui ne répertorie pas UPPER et LOWER dans le tableau des fonctions qui se comportent différemment lors de l'utilisation d'un classement _SC: classement et Unicode Prise en charge: caractères supplémentaires .

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]

Retours (agrandis pour que vous puissiez réellement voir le caractère supplémentaire):

Query result showing UPPER() not working with Supplementary Character

Vous pouvez voir la liste complète (et actuelle) des caractères en minuscules et passer en majuscules en utilisant la fonction de recherche suivante sur Unicode.org (vous pouvez voir les caractères supplémentaires en faisant défiler vers le bas jusqu'à ce que vous arriviez au "DESERET" section, ou appuyez simplement sur Control-F et recherchez ce mot):

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

Bien que pour être honnête, ce n'est pas un énorme avantage car il est douteux que quiconque utilise réellement l'un des personnages supplémentaires qui peuvent être placés dans un titre. Dans les deux cas, voici le code 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));
    }
}

Voici @ suggestion de MikaelEriksson - légèrement modifié pour gérer les données NVARCHAR ainsi que les mots qui sont tous en majuscules (pour correspondre plus étroitement au comportement de la méthode .NET) - - avec un test de cette implémentation T-SQL et de l'implémentation 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];

Query result showing output of T-SQL XML code and ToTitleCase via SQLCLR

Une autre différence de comportement est que cette implémentation T-SQL particulière se divise uniquement sur les espaces, tandis que la méthode ToTitleCase() considère la plupart des non-lettres comme des séparateurs Word (d'où la différence de gestion de la partie "one & TWO").

Les deux implémentations gèrent correctement la combinaison des séquences. Chacune des lettres accentuées de "üvÜlA" est composée d'une lettre de base et d'une combinaison tréma/tréma (les deux points au-dessus de chaque lettre), et elles sont correctement converties dans l'autre cas dans les deux tests.

Enfin, un inconvénient inattendu de la version SQLCLR est qu'en proposant différents tests, j'ai trouvé un bogue dans le code .NET lié à sa gestion des lettres encerclées (qui a maintenant été signalé sur Microsoft Connect - MISE À JOUR: Connect a été déplacé vers /dev/null - littéralement - donc je pourrais avoir besoin de soumettre à nouveau cela si le problème persiste). La bibliothèque .NET traite les lettres encerclées comme des séparateurs de mots, c'est pourquoi elle ne transforme pas le "ⓐDD" en "Ⓐdd" comme il se doit.


FYI

Une fonction SQLCLR prédéfinie encapsulant la méthode TextInfo.ToTitleCase Mentionnée ci-dessus est désormais disponible dans la version gratuite de SQL # (que j'ai écrite) sous la forme String_ToTitleCase et String_ToTitleCase4k .

????

9
Solomon Rutzky

Comme alternative à réponse de Mikael Eriksson , vous pourriez envisager d'utiliser la gestion T-SQL propriétaire du réglage des variables dans les instructions de sélection à plusieurs lignes.

Dans SQL Server, lorsqu'une variable est définie dans le cadre d'une instruction SELECT, chaque ligne exécute une itération de la logique définie.

Les gens utilisent souvent cette méthode pour concaténer des chaînes, bien qu'elle ne soit pas prise en charge et qu'il existe certains problèmes officiellement documentés avec elle . Le problème officiel est lié à des caractéristiques ORDER BY particulières, et nous n'en avons pas besoin ici, donc c'est peut-être une option sûre.

Ici, nous parcourons les 26 lettres de l'alphabet et les remplaçons par une version majuscule si elles sont précédées d'un espace. (Nous préparons la chaîne initialement en mettant en majuscule la première lettre et en mettant les autres en minuscules, comme vous l'avez fait dans votre question.)

Le SQL est un peu complexe car il nécessite l'utilisation d'une table Tally - une table de nombres - pour générer les 26 itérations de remplacement de ce qu'il fait. Vous pouvez créer une fonction définie par l'utilisateur (TVF) de table en ligne pratique pour produire cette table de nombres ou vous pouvez même utiliser une table physique.

Un inconvénient de cette option est qu'elle ne peut pas faire partie d'un TVF en ligne car elle doit impliquer la définition d'une variable. Donc, si vous vouliez appliquer cette méthode à une colonne de votre sortie, vous auriez besoin de l'envelopper dans un TVF multi-instructions ou une fonction définie par l'utilisateur scalaire.

Cependant, son plan de requête est beaucoup plus simple et il est probablement beaucoup plus rapide que la méthode XML. Vous pourriez dire que c'est aussi plus facile à comprendre (surtout si vous avez votre propre table de pointage).

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

(J'ai testé cela en utilisant une chaîne beaucoup plus grande et c'était environ 6 ms contre 14 ms pour la solution XML.)

Il existe un certain nombre de limitations supplémentaires avec cette solution. Tel qu'il est écrit, il suppose un classement insensible à la casse, bien que vous puissiez éliminer ce problème en en spécifiant un classement ou en exécutant LCASE sur le terme de recherche, au prix de certaines performances. Il ne traite également que les lettres standard ASCII et repose sur leur placement dans le jeu de caractères , donc il ne ferait rien avec ñ.

5
Riley Major

En supposant que vous cherchez uniquement à mettre des mots en majuscules après un espace, voici une autre façon de le faire.

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)
3
TLaV

Peut-être pas à l'épreuve des balles, mais j'espère que c'est une contribution utile à ce fil.

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
1
Simon Jones

S'appuyant sur la solution @ChrisJ, voici une simple fonction de valeur de table en ligne qui devrait améliorer les performances. Vous pouvez ajouter des REMPLACEMENTS supplémentaires pour gérer les caractères spéciaux - ponctuation ou caractères de contrôle - selon vos besoins. Cela fonctionne beaucoup mieux que les solutions CTE (certes cool) (par PlanExplorer, la ligne supérieure est CTE, le bas est REMPLACER):

enter image description here

CREATE FUNCTION fSelectInitialCaps(@InputString VARCHAR(MAX))
RETURNS TABLE
AS
RETURN
/*  Example usage:

    SELECT TOP(100)
        c.Company
        , company.OutputString
    FROM dbo.Contacts c
        CROSS APPLY dbo.fSelectInitialCaps(c.Company) company
    WHERE CHARINDEX(' ', c.Company) > 0;
*/
SELECT [OutputString] = 
    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(' ' + @InputString) ,
        ' a', Upper (' A')) ,
        ' b', Upper (' B')) ,
        ' c', Upper (' C')) ,
        ' d', Upper (' D')) ,
        ' e', Upper (' E')) ,
        ' f', Upper (' F')) ,
        ' g', Upper (' G')) ,
        ' h', Upper (' H')) ,
        ' i', Upper (' I')) ,
        ' j' , Upper(' J')) ,
        ' k' , Upper(' K')) ,
        ' l' , Upper(' L')) ,
        ' m' , Upper(' M')) ,
        ' n' , Upper(' N')) ,
        ' o' , Upper(' O')) ,
        ' p' , Upper(' P')) ,
        ' q' , Upper(' Q')) ,
        ' r' , Upper(' R')) ,
        ' s' , Upper(' S')) ,
        ' t' , Upper(' T')) ,
        ' u' , Upper(' U')) ,
        ' v' , Upper(' V')) , 
        ' w' , Upper(' W')) , 
        ' x' , Upper(' X')) ,
        ' y' , Upper(' Y')) , 
        ' z' , Upper(' Z')))
;
GO
0
Russell Fox

Voici la procédure que j'ai utilisée dans une base de données Firebird pour ce faire. Peut probablement être nettoyé beaucoup mais cela a fait le travail pour moi.

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
Jeffrey Elkins

Les CTE récursifs sont assez bons pour ce genre de choses.

Probablement pas particulièrement efficace pour les grandes opérations, mais permet ce type d'opération dans une instruction de sélection SQL pure:

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

Production:

The Quick Brown Fox Jumps   Over The Lazy Dog
0
Jerb

J'aime cette version. C'est simple, et peut être utilisé pour créer une fonction, il vous suffit d'avoir la bonne version de 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
0
Cristi