web-dev-qa-db-ja.com

SQL Serverでデータを適切なケースに変換する方法

SQL Serverには、文字列データを表示および更新するためのシステム関数が含まれています。大文字と小文字の両方を使用できますが、大文字と小文字は区別されません。この操作がアプリケーション層ではなくSQL Serverで行われることを望む理由はいくつかあります。私の場合、複数のソースからのグローバルHRデータの統合中に、データのクリーンアップを実行していました。

インターネットを検索すると、このタスクに対する複数の解決策が見つかりますが、多くの場合、制限的な警告があるか、関数で例外を定義できないようです。

[〜#〜] note [〜#〜]:以下のコメントで述べたように、SQL Serverはこの変換を実行するための理想的な場所ではありません。他の方法も提案されました-たとえばCLRなど。私の意見では、この投稿はその目的を果たしています-ランダムにちらほら見られるのとは対照的に、これらすべての考えを1か所にまとめることは素晴らしいことです。皆さんありがとうございます。

5
SQL_Underworld

私が出会った最良の解決策は here です。

スクリプトを少し変更しました。場合によっては、スクリプトが値の後にスペースを追加していたため、LTRIMとRTRIMを戻り値に追加しました。

大文字のデータから適切なケースへの変換をプレビューするための使用例(例外あり):

SELECT <column>,[dbo].[fProperCase](<column>,'|APT|HWY|BOX|',NULL)
FROM <table> WHERE <column>=UPPER(<column>)

このスクリプトの本当にシンプルでありながら強力な側面は、関数呼び出し自体の中で例外を定義できることです。

ただし、注意点が1つあります。
現在記述されているように、スクリプトはMc [A-Z]%、Mac [A-Z]%などの姓を正しく処理しません。私は現在、そのシナリオを処理するための編集に取り組んでいます。

回避策として、関数の返されるパラメーターを変更しました:REPLACE(REPLACE(LTRIM(RTRIM((@ ProperCaseText)))、 'Mcd'、 'McD')、 'Mci'、 'McI')など...

この方法は明らかにデータの事前知識を必要とし、理想的ではありません。これを解く方法は確かにありますが、私はコンバージョンの真っ最中なので、この1つの厄介な問題に専念する時間はありません。

これがコードです:

CREATE FUNCTION [dbo].[fProperCase](@Value varchar(8000), @Exceptions varchar(8000),@UCASEWordLength tinyint)
returns varchar(8000)
as
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Function Purpose: To convert text to Proper Case.
Created By:             David Wiseman
Website:                http://www.wisesoft.co.uk
Created:                2005-10-03
Updated:                2006-06-22
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
INPUTS:

@Value :                This is the text to be converted to Proper Case
@Exceptions:            A list of exceptions to the default Proper Case rules. e.g. |RAM|CPU|HDD|TFT|
                              Without exception list they would display as Ram, Cpu, Hdd and Tft
                              Note the use of the Pipe "|" symbol to separate exceptions.
                              (You can change the @sep variable to something else if you prefer)
@UCASEWordLength: You can specify that words less than a certain length are automatically displayed in UPPERCASE

USAGE1:

Convert text to ProperCase, without any exceptions

select dbo.fProperCase('THIS FUNCTION WAS CREATED BY DAVID WISEMAN',null,null)
>> This Function Was Created By David Wiseman

USAGE2:

Convert text to Proper Case, with exception for WiseSoft

select dbo.fProperCase('THIS FUNCTION WAS CREATED BY DAVID WISEMAN @ WISESOFT','|WiseSoft|',null)
>> This Function Was Created By David Wiseman @ WiseSoft

USAGE3:

Convert text to Proper Case and default words less than 3 chars to UPPERCASE

select dbo.fProperCase('SIMPSON, HJ',null,3)
>> Simpson, HJ

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
begin
      declare @sep char(1) -- Seperator character for exceptions
      declare @i int -- counter
      declare @ProperCaseText varchar(5000) -- Used to build our Proper Case string for Function return
      declare @Word varchar(1000) -- Temporary storage for each Word
      declare @IsWhiteSpace as bit -- Used to indicate whitespace character/start of new Word
      declare @c char(1) -- Temp storage location for each character

      set @Word = ''
      set @i = 1
      set @IsWhiteSpace = 1
      set @ProperCaseText = ''
      set @sep = '|'

      -- Set default UPPERCASEWord Length
      if @UCASEWordLength is null set @UCASEWordLength = 1
      -- Convert user input to lower case (This function will UPPERCASE words as required)
      set @Value = LOWER(@Value)

      -- Loop while counter is less than text lenth (for each character in...)
      while (@i <= len(@Value)+1)
      begin

            -- Get the current character
            set @c = SUBSTRING(@Value,@i,1)

            -- If start of new Word, UPPERCASE character
            if @IsWhiteSpace = 1 set @c = UPPER(@c)

            -- Check if character is white space/symbol (using ascii values)
            set @IsWhiteSpace = case when (ASCII(@c) between 48 and 58) then 0
                                          when (ASCII(@c) between 64 and 90) then 0
                                          when (ASCII(@c) between 96 and 123) then 0
                                          else 1 end

            if @IsWhiteSpace = 0
            begin
                  -- Append character to temp @Word variable if not whitespace
                  set @Word = @Word + @c
            end
            else
            begin
                  -- Character is white space/punctuation/symbol which marks the end of our current Word.
                  -- If Word length is less than or equal to the UPPERCASE Word length, convert to upper case.
                  -- e.g. you can specify a @UCASEWordLength of 3 to automatically UPPERCASE all 3 letter words.
                  set @Word = case when len(@Word) <= @UCASEWordLength then UPPER(@Word) else @Word end

                  -- Check Word against user exceptions list. If exception is found, use the case specified in the exception.
                  -- e.g. WiseSoft, RAM, CPU.
                  -- If Word isn't in user exceptions list, check for "known" exceptions.
                  set @Word = case when charindex(@sep + @Word + @sep,@exceptions collate Latin1_General_CI_AS) > 0
                                    then substring(@exceptions,charindex(@sep + @Word + @sep,@exceptions collate Latin1_General_CI_AS)+1,len(@Word))
                                    when @Word = 's' and substring(@Value,@i-2,1) = '''' then 's' -- e.g. Who's
                                    when @Word = 't' and substring(@Value,@i-2,1) = '''' then 't' -- e.g. Don't
                                    when @Word = 'm' and substring(@Value,@i-2,1) = '''' then 'm' -- e.g. I'm
                                    when @Word = 'll' and substring(@Value,@i-3,1) = '''' then 'll' -- e.g. He'll
                                    when @Word = 've' and substring(@Value,@i-3,1) = '''' then 've' -- e.g. Could've
                                    else @Word end

                  -- Append the Word to the @ProperCaseText along with the whitespace character
                  set @ProperCaseText = @ProperCaseText + @Word + @c
                  -- Reset the Temp @Word variable, ready for a new Word
                  set @Word = ''
            end
            -- Increment the counter
            set @i = @i + 1
      end
      return @ProperCaseText
end
2
SQL_Underworld

これらのアプローチで遭遇する課題は、情報を失ったことです。ビジネスユーザーに、ぼやけて焦点が合っていない写真を撮ったこと、およびテレビで見たものにかかわらず撮ったことを説明します。くっきりと焦点を合わせる方法はありません。これらのルールが機能しない状況が常に発生し、誰もがそうであることを知っている限り、そうなります。

これはHRデータなので、メインフレームはそれを [〜#〜] aaron [〜#〜] BERTRANDとして保存するため、一貫したタイトルケースフォーマットで名前を取得することについて話していると想定します。新しいシステムが彼らに怒鳴らないようにしたいのです。アーロンは簡単です(安くはありません)。あなたとMaxはすでにMc/Macの問題を特定しているので、Mc/Macを正しく大文字にしますが、Mackey/ Maclin /Mackenzieで攻撃的になりすぎる場合があります。マッケンジーは興味深いケースですが、その人気が 赤ちゃんの名前 として急成長している様子を見てください

Mackenzie

人々がひどい存在であるので、ある時点で、マッケンジー・マッケンジーという名前の貧しい子供がいます。

また、目盛りの周りの両方の文字をキャップするD'Antoniのような素敵なものに出くわします。アポストロフィの後の文字だけを大文字にするd'Autremontを除きます。しかし、天国はあなたを助けてくれます。もしあなたが彼らの姓がディロニであるためにディロニにメールを送るなら。

実際のコードを提供するために、以下は目的のために2005年のインスタンスで使用したCLRメソッドです。作成した例外のリストを除いて、通常はToTitleCaseを使用しました。例外のリストは、基本的に前述の例外をコード化することをやめたときです。

namespace Common.Util
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;

    /// <summary>
    /// A class that attempts to proper case a Word, taking into
    /// consideration some outliers.
    /// </summary>
    public class ProperCase
    {
        /// <summary>
        /// Convert a string into its propercased equivalent.  General case
        /// it will capitalize the first letter of each Word.  Handled special 
        /// cases include names with apostrophes (O'Shea), and Scottish/Irish
        /// surnames MacInnes, McDonalds.  Will fail for Macbeth, Macaroni, etc
        /// </summary>
        /// <param name="inputText">The data to be recased into initial caps</param>
        /// <returns>The input text resampled as proper cased</returns>
        public static string Case(string inputText)
        {
            CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture;
            TextInfo textInfo = cultureInfo.TextInfo;
            string output = null;
            int staticHack = 0;

            Regex expression = null;
            string matchPattern = string.Empty;

            // Should think about maybe matching the first non blank character
            matchPattern = @"
                (?<Apostrophe>'.\B)| # Match things like O'Shea so apostrophe plus one.  Think about white space between ' and next letter.  TODO:  Correct it's from becoming It'S, can't -> CaN'T
                \bMac(?<Mac>.) | # MacInnes, MacGyver, etc.  Will fail for Macbeth
                \bMc(?<Mc>.) # McDonalds
                ";
            expression = new Regex(matchPattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);

            // Handle our funky rules            
            // Using named matches is probably overkill as the
            // same rule applies to all but for future growth, I'm
            // defining it as such.
            // Quirky behaviour---for 2005, the compiler will 
            // make this into a static method which is verboten for 
            // safe assemblies.  
            MatchEvaluator upperCase = delegate(Match match)
            {
                // Based on advice from Chris Hedgate's blog
                // I need to reference a local variable to prevent
                // this from being turned into static
                staticHack = matchPattern.Length;

                if (!string.IsNullOrEmpty(match.Groups["Apostrophe"].Value))
                {
                    return match.Groups["Apostrophe"].Value.ToUpper();
                }

                if (!string.IsNullOrEmpty(match.Groups["Mac"].Value))
                {
                    return string.Format("Mac{0}", match.Groups["Mac"].Value.ToUpper());
                }

                if (!string.IsNullOrEmpty(match.Groups["Mc"].Value))
                {
                    return string.Format("Mc{0}", match.Groups["Mc"].Value.ToUpper());
                }

                return match.Value;
            };

            MatchEvaluator evaluator = new MatchEvaluator(upperCase);

            if (inputText != null)
            {
                // Generally, title casing converts the first character 
                // of a Word to uppercase and the rest of the characters 
                // to lowercase. However, a Word that is entirely uppercase, 
                // such as an acronym, is not converted.
                // http://msdn.Microsoft.com/en-us/library/system.globalization.textinfo.totitlecase(VS.80).aspx
                string temporary = string.Empty;
                temporary = textInfo.ToTitleCase(inputText.ToString().ToLower());
                output = expression.Replace(temporary, evaluator);
            }
            else
            {
                output = string.Empty;
            }

            return output;
        }
    }
}

これですべてが明確になったので、この素敵な詩の本をeカミングスで仕上げます

11
billinkc

私はあなたがすでに良い解決策を手に入れていることを知っていますが、次のSQL Serverの「vNext」バージョンの使用に依存しているものの、インラインテーブル値関数を使用するより簡単な解決策を追加したいと思いました。 STRING_AGG()およびSTRING_SPLIT()関数:

_IF OBJECT_ID('dbo.fn_TitleCase') IS NOT NULL
DROP FUNCTION dbo.fn_TitleCase;
GO
CREATE FUNCTION dbo.fn_TitleCase
(
    @Input nvarchar(1000)
)
RETURNS TABLE
AS
RETURN
SELECT Item = STRING_AGG(splits.Word, ' ')
FROM (
    SELECT Word = UPPER(LEFT(value, 1)) + LOWER(RIGHT(value, LEN(value) - 1))
    FROM STRING_SPLIT(@Input, ' ')
    ) splits(Word);
GO
_

関数のテスト:

_SELECT *
FROM dbo.fn_TitleCase('this is a test');
_

これはテストです

_SELECT *
FROM dbo.fn_TitleCase('THIS IS A TEST');
_

これはテストです

STRING_AGG() および STRING_SPLIT() に関するドキュメントについては、MSDNを参照してください

STRING_SPLIT()関数は、特定の順序でアイテムを返すことを保証しないことに注意してください。これは最も厄介なことです。出力の順序を示すために、STRING_SPLITの出力に列を追加するように要求するMicrosoftフィードバックアイテムがあります。それを賛成することを検討してください ここ

エッジに住みたい場合で、この方法論を使用したい場合は、例外を含めるように拡張できます。それを行うインラインテーブル値関数を作成しました。

_CREATE FUNCTION dbo.fn_TitleCase
(
    @Input nvarchar(1000)
    , @SepList nvarchar(1)
)
RETURNS TABLE
AS
RETURN
WITH Exceptions AS (
    SELECT v.ItemToFind
        , v.Replacement
    FROM (VALUES /* add further exceptions to the list below */
          ('mca', 'McA')
        , ('maca','MacA')
        ) v(ItemToFind, Replacement)
)
, Source AS (
    SELECT Word = UPPER(LEFT(value, 1 )) + LOWER(RIGHT(value, LEN(value) - 1))
        , Num = ROW_NUMBER() OVER (ORDER BY GETDATE())
    FROM STRING_SPLIT(@Input, @SepList) 
)
SELECT Item = STRING_AGG(splits.Word, @SepList)
FROM (
    SELECT TOP 214748367 Word
    FROM (
        SELECT Word = REPLACE(Source.Word, Exceptions.ItemToFind, Exceptions.Replacement)
            , Source.Num
        FROM Source
        CROSS APPLY Exceptions
        WHERE Source.Word LIKE Exceptions.ItemToFind + '%'
        UNION ALL
        SELECT Word = Source.Word
            , Source.Num
        FROM Source
        WHERE NOT EXISTS (
            SELECT 1
            FROM Exceptions
            WHERE Source.Word LIKE Exceptions.ItemToFind + '%'
            )
        ) w
    ORDER BY Num
    ) splits;
GO
_

これをテストすると、それがどのように機能するかがわかります。

_SELECT *
FROM dbo.fn_TitleCase('THIS IS A TEST MCADAMS MACKENZIE MACADAMS', ' ');
_

これはテストですマクアダムスマッケンジーマクアダムス

6
Max Vernon