Escriba en mayúscula solo la primera letra de cada palabra de cada oración en SQL Server

18

Quiero poner en mayúscula solo la primera letra de cada palabra de cada oración en una columna SQL.

Por ejemplo, si la oración es:

'Me gustan las películas'

entonces necesito la salida:

'Me gustan las películas'

Consulta:

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]

Aquí escribí mayúscula, minúscula y mayúscula la primera letra solo en mi columna (aquí pongo solo una palabra al azar).

Aquí están mis resultados:

ingrese la descripción de la imagen aquí

¿Hay alguna posibilidad de hacer eso?

¿Alguna posibilidad de obtener resultados sin usar la función definida por el usuario?

Necesito la salida Qwerty Keyboard

Marin Mohanadas
fuente
11
¿Por qué quieres hacer esto dentro del servidor SQL? ¡Su capa de presentación debería manejar eso de manera eficiente!
Kin Shah
No siempre tiene una capa de presentación, por ejemplo, cuando limpia datos incorrectos importados a SQL Server, y no desea escribir un programa C # para hacerlo. Sí, podría invertir en una función CLR, pero ¿qué tal algo rápido y sucio que funcione?
Jeffrey Roughgarden

Respuestas:

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

Primero convierte la cadena a XML reemplazando todos los espacios con la etiqueta vacía <X/>. Luego tritura el XML para usar una palabra por fila nodes(). Para que las filas vuelvan a un valor, utiliza el for xml pathtruco.

Mikael Eriksson
fuente
8
Y ese código es exactamente por qué nunca haría eso en SQL. Sin decir que la respuesta es incorrecta, esto fue solicitado. Pero el SQL estándar es ridículamente inadecuado para este tipo de manipulación de cadenas. Una función basada en CLR funcionaría, o simplemente hacerlo en la capa de presentación.
TomTom
8
@TomTom Parece complicado, pero eso no es nada en comparación con el plan de consulta que produce y no será rápido según ningún estándar. Sin embargo, es educativo y divertido profundizar en lo que realmente está sucediendo en la consulta y por qué está escrito de la manera en que está. El problema podría resolverse con una función de división de cadena (tabla de números). Difícil de evitar el for xml pathtruco para la concatenación. A menos que elija CLR, que sería la mejor opción si la velocidad y la eficiencia son importantes.
Mikael Eriksson
15

En SQL Server 2016 puede hacer esto con R, p. Ej.

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

Si debería o no es una pregunta diferente :)

wBob
fuente
oh, definitivamente no deberías. A veces es la opción menos mala, o como mencionó el OP, necesitan una solución rápida y sucia.
Jonathan Fite
12

Tal vez estoy siendo tonto, pero comprobando la consulta a continuación que he escrito en contra de algunos de los proporcionados, esto parece ser un poco más eficiente (dependiendo de la indexación).

El código es un poco estúpido, pero no hay un dicho que diga que si parece estúpido pero funciona, entonces no es estúpido.

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
fuente
2
Esta es una gran y horrible respuesta. Particularmente me gusta el espacio que has agregado al principio y luego te quitas al final.
BradC
2
@BradC es horrible, pero cuando lo he probado en comparación con el método XML contra un conjunto de datos, ¡parece funcionar a una fracción del costo!
Chris J
9

Otra opción es manejar esto a través de SQLCLR. Incluso hay un método disponible en .NET que hace esto: TextInfo.ToTitleCase (in System.Globalization). Este método pondrá en mayúscula la primera letra de cada palabra y en minúscula las letras restantes. A diferencia de las otras propuestas aquí, también omite las palabras en mayúsculas, asumiendo que son siglas. Por supuesto, si se desea este comportamiento, sería bastante fácil actualizar cualquiera de las sugerencias de T-SQL para hacerlo también.

Una ventaja del método .NET es que puede poner letras mayúsculas que son caracteres suplementarios. Por ejemplo: DESERET SMALL LETTER OW tiene una asignación en mayúsculas de DESERET CAPITAL LETTER OW (ambas aparecen como cuadros cuando las pego aquí) , pero la UPPER()función no cambia la versión en minúsculas a mayúsculas, incluso cuando La clasificación predeterminada para la base de datos actual está establecida en Latin1_General_100_CI_AS_SC. Esto parece coherente con la documentación de MSDN que no aparece en la lista UPPERy LOWERen el cuadro de funciones que se comportan de manera diferente cuando se utiliza una _SCClasificación: Compatibilidad y Soporte Unicode: Caracteres suplementarios .

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]

Devoluciones (ampliada para que pueda ver el carácter suplementario):

El resultado de la consulta que muestra UPPER () no funciona con caracteres complementarios

Puede ver la lista completa (y actual) de caracteres en minúsculas y cambiar a mayúsculas usando la siguiente función de búsqueda en Unicode.org (puede ver los caracteres suplementarios desplazándose hacia abajo hasta llegar al "DESIERTO" sección, o simplemente presiona Control-Fy busca esa palabra):

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

Aunque, para ser sincero, este no es un gran beneficio, ya que es dudoso que alguien esté usando alguno de los Personajes suplementarios que pueden estar en mayúsculas. De cualquier manera, aquí está el código 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));
    }
}

Aquí está la sugerencia de @MikaelEriksson, modificada ligeramente para manejar NVARCHARdatos y omitir palabras en mayúsculas (para que coincida más estrechamente con el comportamiento del método .NET), junto con una prueba de esa implementación T-SQL y de La implementación de 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];

Resultado de la consulta que muestra la salida del código XML T-SQL y ToTitleCase a través de SQLCLR

Otra diferencia en el comportamiento es que esta implementación particular de T-SQL se divide solo en espacios, mientras que el ToTitleCase()método considera que la mayoría de las no letras son separadores de palabras (de ahí la diferencia en el manejo de la parte "uno y DOS").

Ambas implementaciones manejan secuencias combinadas correctamente. Cada una de las letras acentuadas en "üvÜlA" se compone de una letra base y una diéresis / diéresis combinadas (los dos puntos sobre cada letra), y se convierten correctamente al otro caso en ambas pruebas.

Finalmente, una desventaja inesperada de la versión SQLCLR es que al realizar varias pruebas, encontré un error en el código .NET relacionado con su manejo de las letras en círculo (que ahora se ha informado en Microsoft Connect - ACTUALIZACIÓN: Connect ha sido se movió a /dev/null, literalmente, por lo que es posible que deba volver a enviar esto si el problema persiste). La biblioteca .NET trata las letras en círculo como separadores de palabras, por lo que no convierte la "ⓐDD" en "Ⓐdd" como debería.


FYI

Una función SQLCLR pre-realizada que encapsula el TextInfo.ToTitleCasemétodo mencionado anteriormente ahora está disponible en la versión gratuita de SQL # (que escribí) como String_ToTitleCase y String_ToTitleCase4k .

😺

Solomon Rutzky
fuente
5

Como alternativa a la respuesta de Mikael Eriksson , podría considerar usar el manejo patentado de T-SQL de la configuración de variables en sentencias de selección de varias filas.

En SQL Server, cuando se establece una variable como parte de una instrucción SELECT, cada fila ejecutará una iteración de la lógica establecida.

La gente a menudo usa este método para concatenar cadenas, aunque no es compatible y hay algunos problemas documentados oficialmente con él . El problema oficial se relaciona con las características particulares de ORDER BY, y no necesitamos eso aquí, por lo que quizás sea una opción segura.

Aquí, iteramos sobre las 26 letras del alfabeto y las reemplazamos con una versión en mayúscula si están precedidas por un espacio. (Preparamos la cadena inicialmente escribiendo en mayúscula la primera letra y haciendo el resto en minúscula, como lo hizo en su pregunta).

El SQL es un poco complejo porque requiere el uso de una Tabla de conteo, una tabla de números, para generar las 26 iteraciones de reemplazo que está haciendo. Puede hacer una práctica función definida por el usuario (TVF) con valores de tabla en línea para producir esa tabla de números o incluso podría usar una tabla física.

Un inconveniente de esta opción es que no puede ser parte de un TVF en línea, ya que debe implicar el establecimiento de una variable. Entonces, si quisiera aplicar este método a una columna de su salida, necesitaría envolverlo en un TVF de múltiples declaraciones o en una función escalar definida por el usuario.

Sin embargo, su plan de consulta es mucho más simple y probablemente es significativamente más rápido que el método XML. También podría argumentar que es más fácil de entender (especialmente si tiene su propia tabla de conteo).

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

(Probé esto usando una cadena mucho más grande y era de aproximadamente 6 ms frente a 14 ms para la solución XML).

Existen varias limitaciones adicionales con esta solución. Tal como está escrito, supone una intercalación que no distingue entre mayúsculas y minúsculas, aunque podría eliminar ese problema especificando una intercalación o ejecutando LCASE en el término de búsqueda, a costa de algún rendimiento. También solo aborda las letras ASCII estándar y se basa en su ubicación en el conjunto de caracteres , por lo que no haría nada con ñ.

Riley Major
fuente
3

Suponiendo que solo está buscando capitalizar las palabras después de un espacio, aquí hay otra forma de hacerlo.

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
fuente
1

Puede que no sea a prueba de balas, pero espero que sea una contribución útil a este hilo.

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
fuente
0

A continuación se muestra el procedimiento que utilicé en una base de datos Firebird para hacer esto. Probablemente se pueda limpiar mucho, pero hizo el trabajo por mí.

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
fuente
0

Los CTE recursivos son bastante buenos para este tipo de cosas.

Probablemente no sea particularmente eficiente para operaciones grandes, pero permite este tipo de operación en una declaración de selección 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

Salida:

The Quick Brown Fox Jumps   Over The Lazy Dog
Jerb
fuente
0

Me gusta esta version Es simple y puede usarse para crear una función, solo tiene que tener la versión correcta 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
fuente
¿Cuál es la versión correcta?
dezso
SQL Server (a 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 

Espero te ayude ...

Alpha k A
fuente
¡Bienvenido a los administradores de bases de datos! Explique cómo su consulta resuelve el problema del autor; las respuestas sin explicación generalmente no se reciben bien.
Glorfindel
-3

Datos de prueba

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

Implementación

select substring(Upper(@word),1,1) + substring(@word, 2, LEN(@word))
Romiko Derbynew
fuente
Capitalizar palabras que ya están separadas es fácil. Creo que el OP está interesado en cómo identificar palabras dentro de una cadena, y capitalizar cada una de ellas.
Jon of All Trades