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

18

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 au hasard).

Voici mes résultats:

entrez la description de l'image ici

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

Marin Mohanadas
la source
11
Pourquoi voulez-vous faire cela dans le serveur SQL? Votre couche de présentation doit gérer cela efficacement!
Kin Shah
Vous n'avez pas toujours une couche de présentation, par exemple lors du nettoyage de données incorrectes importées dans SQL Server, et vous ne voulez pas écrire un programme C # pour le faire. Oui, vous pourriez investir dans une fonction CLR, mais que diriez-vous de quelque chose de rapide et sale qui fonctionne.
Jeffrey Roughgarden

Réponses:

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

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' for xml pathastuce.

Mikael Eriksson
la source
8
Et ce code est exactement la raison pour laquelle je ne ferais jamais cela en SQL. Ne pas dire que la réponse est fausse - cela a été demandé. Mais le SQL standard est ridiculement mal adapté à ce type de manipulation de chaînes. Une fonction basée sur CLR fonctionnerait, ou le ferait simplement sur la couche de présentation.
TomTom
8
@TomTom Cela a l'air compliqué, mais ce n'est rien comparé au plan de requête qu'il produit et il ne sera pas rapide par aucune norme. Il est cependant éducatif et amusant de creuser ce qui se passe réellement dans la requête et pourquoi il est écrit tel quel. Le problème pourrait être résolu avec une fonction de partage de chaîne (table numérique). Difficile d'éviter l' for xml pathastuce pour la concaténation. Sauf si vous optez pour CLR qui serait la meilleure option si la vitesse et l'efficacité sont importantes.
Mikael Eriksson
15

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

wBob
la source
oh, tu ne devrais certainement pas. Parfois, c'est la moins mauvaise option, ou comme l'OP l'a mentionné, ils ont besoin d'une solution rapide et sale.
Jonathan Fite
12

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 a l'air 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
Chris J
la source
2
C'est une excellente et horrible réponse. J'aime particulièrement l'espace que vous avez cloué au début, puis je me déshabille à la fin.
BradC
2
@BradC c'est hideux, mais quand je l'ai essayé par rapport à la méthode XML contre un ensemble de données, il semble fonctionner à une fraction du coût!
Chris J
9

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 (dansSystem.Globalization ). Cette méthode met en majuscule la première lettre de chaque mot et met en 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 s'affichent sous forme de cases lorsque je les colle ici) , mais la UPPER()fonction ne change pas la version en minuscules en majuscules, 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 UPPERet LOWERdans le tableau des fonctions qui se comportent différemment lors de l'utilisation d'un _SCclassement: classement et prise en charge Unicode: 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):

Résultat de la requête montrant que UPPER () ne fonctionne pas avec le caractère supplémentaire

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 atteigniez le "DESERET" section, ou appuyez simplement sur Control-Fet 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 caractères supplémentaires qui peuvent être placés dans le 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 la suggestion de @ MikaelEriksson - légèrement modifiée pour gérer les NVARCHARdonnées 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];

Résultat de la requête affichant la sortie du code XML T-SQL et ToTitleCase via SQLCLR

Une autre différence de comportement est que cette implémentation T-SQL particulière ne se divise que sur les espaces, alors que la ToTitleCase()méthode considère la plupart des non-lettres comme des séparateurs de mots (d'où la différence de gestion de la partie "un et deux").

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: la connexion 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 TextInfo.ToTitleCaseméthode mentionnée ci-dessus est désormais disponible dans la version gratuite de SQL # (que j'ai écrite) en tant que String_ToTitleCase et String_ToTitleCase4k .

😺

Solomon Rutzky
la source
5

Comme alternative à la réponse de Mikael Eriksson , vous pourriez envisager d'utiliser la gestion T-SQL propriétaire de la définition 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'elle comporte des problèmes officiellement documentés . 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 minuscule, comme vous l'avez fait dans votre question.)

Le SQL est un peu complexe car il nécessite l'utilisation d'une table de pointage - 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 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 ASCII standard et repose sur leur placement dans le jeu de caractères , il ne ferait donc rien avec ñ.

Riley Major
la source
3

En supposant que vous ne cherchiez qu'à mettre des mots en majuscule 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)
TLaV
la source
1

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
Simon Jones
la source
0

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 ;~
Jeffrey Elkins
la source
0

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

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
Jerb
la source
0

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
Cristi
la source
Laquelle est la bonne version?
dezso
SQL Server (à partir de 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 

J'espère que cela aiderait ...

Alpha k A
la source
Bienvenue aux administrateurs de bases de données! Veuillez expliquer comment votre requête résout le problème de l'auteur; les réponses sans explication ne sont généralement pas bien reçues.
Glorfindel
-3

Données de 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())

la mise en oeuvre

select substring(Upper(@word),1,1) + substring(@word, 2, LEN(@word))
Romiko Derbynew
la source
La mise en majuscule de mots déjà séparés est facile. Je pense que le PO s'intéresse à la façon d'identifier les mots dans une chaîne et de mettre en majuscule chacun d'eux.
Jon of All Trades, le