Великі літери використовують лише першу букву кожного слова кожного речення в 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 ) );

Потрібно чи ні, це інше питання:)


о, ви точно не повинні. Іноді це найменш поганий варіант, або, як згадував ОП, вони потребують швидкого та брудного.
Джонатан Фійт

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.ToTitleCaseSystem.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 Support: Додаткові символи .

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

Результат запиту, що показує вихід T-SQL XML-коду та 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, кожен рядок буде виконувати ітерацію заданої логіки.

Люди часто використовують цей метод для об'єднання рядків, хоча він не підтримується і з ним є деякі офіційно задокументовані проблеми . Офіційна проблема стосується конкретних характеристик ЗАМОВЛЕННЯ, і нам це не потрібно, тому, можливо, це безпечний варіант.

Тут ми повторюємо 26 букв алфавіту і замінюємо їх верхніми літерами, якщо їм передує пробіл. (Спочатку ми підготовляємо рядок, використовуючи велику літери, а решту - малі, як у вашому запитанні).

SQL є трохи складним, оскільки він вимагає використання таблиці Tally - таблиці чисел - для генерації 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];

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

Існує ряд додаткових обмежень щодо цього рішення. Як написано, воно передбачає нечутливе до регістру порівняння, хоча ви можете усунути цю проблему, вказавши порівняння або застосувавши 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)

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:

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

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

Яка правильна версія?
dezso

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

Великі літери, які вже відокремлюються, легко. Я вважаю, що ОП зацікавлена ​​в тому, як визначити слова в рядку та використовувати великі літери кожного з них.
Йон усіх торгів
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.