Прописывать только первую букву каждого слова каждого предложения в SQL Server

18

Я хочу использовать только первую букву каждого слова каждого предложения в столбце SQL.

Например, если предложение:

'Мне нравятся фильмы'

тогда мне нужен вывод:

'Мне нравятся фильмы'

Запрос:

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]

Здесь я сделал верхнюю, нижнюю и заглавную первую букву только в своем столбце (здесь я поместил только случайное слово).

Вот мои результаты:

введите описание изображения здесь

Есть ли возможность сделать это?

Есть ли возможность получить результаты без использования определенной пользователем функции?

Мне нужен выход Qwerty Keyboard

Марин Моханадас
источник
11
Почему вы хотите сделать это на сервере SQL? Ваш уровень представления должен справиться с этим эффективно!
Кин Шах
У вас не всегда есть уровень представления, например, при очистке неверных данных, импортированных в SQL Server, и вы не хотите писать программу на C # для этого. Да, вы можете инвестировать в функцию CLR, но как насчет чего-то быстрого и грязного, которое работает.
Джеффри Рафгарден

Ответы:

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

Сначала он преобразует строку в XML, заменяя все пробелы пустым тегом <X/>. Затем он уничтожает XML, чтобы получить по одному слову в строке nodes(). Чтобы вернуть строки к одному значению, он использует for xml pathхитрость.

Микаэль Эрикссон
источник
8
Именно из этого кода я бы никогда не сделал этого в SQL. Не сказать, что ответ неправильный - об этом просили. Но стандартный SQL смехотворно плохо подходит для этого типа манипулирования строками. Функция на основе CLR будет работать, или просто делать это на уровне представления.
TomTom
8
@ TomTom Это выглядит сложно, но это ничто по сравнению с планом запроса, который он производит, и он не будет быстрым по любым стандартам. Тем не менее, интересно узнать, что происходит в запросе и почему он написан таким, какой он есть. Проблема может быть решена с помощью функции разделения строк (таблица чисел). Трудно избежать for xml pathуловки для объединения. Если вы не выберете CLR, который будет лучшим вариантом, если важны скорость и эффективность.
Микаэль Эрикссон
15

В SQL Server 2016 вы можете сделать это с помощью R, например

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

Должен ты или нет - это другой вопрос :)

wBob
источник
о, ты определенно не должен Иногда это наименее плохой вариант, или, как упоминалось в ОП, они нужны быстро и грязно.
Джонатан Файт
12

Может быть, я идиот, но, проверяя приведенный ниже запрос с некоторыми из представленных, это кажется немного более эффективным (в зависимости от индексации).

Код немного глупый, но разве не говорят, что если он выглядит глупо, но работает, то он не глупый.

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
Это отличный и ужасный ответ. Мне особенно нравится пространство, которое вы закрепили в начале, а затем удалили в конце.
BradC
2
@BradC это отвратительно, но когда я попробовал это по сравнению с методом XML в сравнении с набором данных, это, кажется, работало за часть стоимости!
Крис Джей
9

Другой вариант - обработать это через SQLCLR. В .NET уже есть метод, который делает это: TextInfo.ToTitleCase (in System.Globalization). Этот метод будет в верхнем регистре первой буквы каждого слова, а в нижнем регистре остальные буквы. В отличие от других предложений, здесь также пропускаются слова в верхнем регистре, считая их аббревиатурами. Конечно, если это необходимо, было бы достаточно просто обновить любое из предложений T-SQL, чтобы сделать это.

Одним из преимуществ метода .NET является то, что он может использовать буквы верхнего регистра, которые являются дополнительными символами. Например: DESERET SMALL LETTER OW имеет отображение в верхнем регистре DESERET CAPITAL LETTER OW (оба отображаются в виде блоков, когда я вставляю их сюда) , но UPPER()функция не изменяет версию нижнего регистра на верхний регистр, даже когда Сортировка по умолчанию для текущей базы данных установлена ​​на Latin1_General_100_CI_AS_SC. Кажется, это согласуется с документацией MSDN, в которой нет перечня UPPERи LOWERв таблице функций, которые ведут себя по-разному при использовании параметров _SCCollation: Collation и Unicode: Дополнительные символы .

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]

Возвращает (увеличено, чтобы вы могли видеть дополнительный символ):

Результат запроса, показывающий, что UPPER () не работает с дополнительным символом

Вы можете увидеть полный (и текущий) список символов в нижнем регистре и изменить его в верхний регистр, используя следующую функцию поиска на Unicode.org (вы можете увидеть дополнительные символы, прокручивая вниз, пока не дойдете до «DESERET») раздел, или просто нажмите Control-Fи найдите это слово):

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

Хотя, честно говоря, это не является огромным преимуществом, так как сомнительно, что кто-то на самом деле использует какой-либо из дополнительных символов, которые могут быть в названии. В любом случае, вот код 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));
    }
}

Вот предложение @ MikaelEriksson - слегка измененное для обработки NVARCHARданных, а также для пропуска слов в верхнем регистре (чтобы более точно соответствовать поведению метода .NET) - вместе с тестом этой реализации T-SQL и реализация 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];

Результат запроса, показывающий вывод XML-кода T-SQL и ToTitleCase через SQLCLR

Другое различие в поведении состоит в том, что эта конкретная реализация T-SQL разделяется только на пробелы, тогда как ToTitleCase()метод рассматривает большинство не-букв как разделители слов (отсюда и разница в обработке части «one & TWO»).

Обе реализации правильно обрабатывают комбинируемые последовательности. Каждая из акцентированных букв в «üvÜlA» состоит из базовой буквы и сочетания диареза / умляута (две точки над каждой буквой), и они правильно преобразуются в другой случай в обоих тестах.

Наконец, один неожиданный недостаток версии SQLCLR заключается в том, что при создании различных тестов я обнаружил ошибку в коде .NET, связанную с обработкой кружковых букв (о которой теперь сообщалось в Microsoft Connect - UPDATE: Connect был переехал /dev/null- буквально - так что мне может понадобиться повторить это, если проблема все еще существует). Библиотека .NET рассматривает Обведенные буквы как разделители слов, поэтому она не превращает «ⓐDD» в «Ⓐdd», как следует.


FYI

Готовая функция SQLCLR, инкапсулирующая TextInfo.ToTitleCaseупомянутый выше метод, теперь доступна в бесплатной версии SQL # (которую я написал) как String_ToTitleCase и String_ToTitleCase4k .

😺

Соломон Руцкий
источник
5

В качестве альтернативы ответу Микаэля Эрикссона вы можете рассмотреть возможность использования проприетарной обработки переменных в T-SQL в операторах выбора нескольких строк.

В SQL Server, когда переменная устанавливается как часть инструкции SELECT, каждая строка будет выполнять итерацию заданной логики.

Люди часто используют этот метод для объединения строк, хотя он не поддерживается и есть некоторые официально задокументированные проблемы с ним . Официальная проблема связана с конкретными характеристиками ORDER BY, и нам здесь это не нужно, поэтому, возможно, это безопасный вариант.

Здесь мы перебираем 26 букв алфавита и заменяем их заглавной версией, если им предшествует пробел. (Первоначально мы подготавливаем строку, используя заглавные буквы и оставляя строчные буквы, как вы сделали в своем вопросе.)

SQL немного сложен, потому что требует использования таблицы подсчета - таблицы чисел - для генерации 26 итераций замены, которую он выполняет. Вы можете создать удобную встроенную определяемую пользователем табличную функцию (TVF) для создания этой таблицы чисел или даже использовать физическую таблицу.

Недостаток этой опции заключается в том, что она не может быть частью встроенного TVF, поскольку для этого необходимо установить переменную. Поэтому, если вы хотите применить этот метод к столбцу выходных данных, вам нужно будет обернуть его в TVF с несколькими операторами или скалярную пользовательскую функцию.

Тем не менее, его план запросов намного проще и, вероятно, значительно быстрее, чем метод XML. Вы могли бы поспорить, что это также легче понять (особенно, если у вас есть собственный подсчет).

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

(Я проверил это, используя намного большую строку, и для решения XML это было около 6 мс против 14 мс.)

У этого решения есть ряд дополнительных ограничений. Как написано, он предполагает сортировку без учета регистра, хотя эту проблему можно устранить, указав параметры сортировки или запустив LCASE в поисковом запросе, за счет некоторой производительности. Он также обращается только к стандартным буквам ASCII и полагается на их размещение в наборе символов , поэтому он не будет иметь ничего общего с ñ.

Райли Майор
источник
3

Предполагая, что вы ищете только заглавные слова после пробела, вот еще один способ сделать это.

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
источник
1

Не может быть пуленепробиваемым, но я надеюсь, что это полезный вклад в эту тему.

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

Ниже описана процедура, которую я использовал в базе данных Firebird для этого. Вероятно, можно много чего почистить, но это сделало работу за меня.

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

Рекурсивные CTE довольно хороши для такого рода вещей.

Вероятно, не особенно эффективен для больших операций, но допускает такую ​​операцию в чистом SQL-выражении 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

Выход:

The Quick Brown Fox Jumps   Over The Lazy Dog
Jerb
источник
0

Мне нравится эта версия. Это просто и может быть использовано для создания функции, вам просто нужно иметь правильную версию 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
источник
Какая версия является правильной?
Дезсо
SQL Server (начиная с 2016 года)
Кристи,
-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 

Я надеюсь, что поможет ...

Альфа К А
источник
Добро пожаловать в Администраторы базы данных! Пожалуйста, объясните, как ваш запрос решает проблему автора; ответы без объяснения обычно не принимаются хорошо.
Глорфиндель
-3

Тестовые данные

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

Реализация

select substring(Upper(@word),1,1) + substring(@word, 2, LEN(@word))
Ромико Дербынов
источник
Использование заглавных слов, которые уже являются отдельными, легко. Я полагаю, что ФП интересуется, как определить слова в строке и сделать их заглавными.
Джон всех профессий