次の方法で共有


補助文字対応文字列操作サンプル

SQL Server 用のこのサンプルは、補助文字に対応した文字列処理を示します。 このサンプルでは、組み込み関数と同じ文字列操作機能を提供する、Transact-SQL の 5 つの文字列関数の実装を示します。ただしこのサンプルの文字列操作関数では、Unicode 文字列および補助文字文字列の両方を処理することができます。 5 つの関数 lens()、lefts() rights() subs()、および replace_s() は、組み込み文字列関数の LEN() LEFT() RIGHT() SUBSTRING() および REPLACE() に対応します。

必要条件

このプロジェクトを作成して実行するには、次のソフトウェアがインストールされている必要があります。

  • SQL Server または SQL Server Express。 SQL Server Express は、SQL Server Express ドキュメントとサンプルの Web サイトから無償で入手できます。

  • SQL Server デベロッパー Web サイトから入手できる AdventureWorks データベース。

  • .NET Framework SDK 2.0 以降または Microsoft Visual Studio 2005 以降。 .NET Framework SDK は無償で入手できます。

  • また、次の条件を満たしている必要があります。

  • 使用している SQL Server インスタンスで CLR 統合が有効になっている必要があります。

  • CLR 統合を有効にするには、次の手順に従います。

    CLR 統合の有効化

    • 以下の Transact-SQL コマンドを実行します。

    sp_configure 'clr enabled', 1

    GO

    RECONFIGURE

    GO

    注意

    CLR 統合を有効にするには、サーバー レベルの ALTER SETTINGS 権限が必要です。この権限は、sysadmin 固定サーバー ロールおよび serveradmin 固定サーバー ロールのメンバーには暗黙に許可されています。

  • 使用している SQL Server インスタンスに AdventureWorks データベースがインストールされている必要があります。

  • 使用している SQL Server インスタンスの管理者でない場合、インストールを完了するには、CreateAssembly 権限が管理者から許可されている必要があります。

サンプルのビルド

次の手順に従ってサンプルを作成し、実行します。

  1. Visual Studio または .NET Framework のコマンド プロンプトを開きます。

  2. 必要な場合は、サンプル用のディレクトリを作成します。 この例では C:\MySample を使用します。

  3. この例では署名付きアセンブリが必要であるため、次のコマンドを入力して非対称キーを作成します。

    sn -k SampleKey.snk

  4. 選択した言語に応じて次のいずれかをコマンド プロンプトで実行し、サンプル コードをコンパイルします。

    • Vbc /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll,C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll,C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /keyfile:Key.snk /target:library SurrogateStringFunction.vb

    • Csc /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.XML.dll /keyfile:Key.snk /target:library SurrogateStringFunction.cs

  5. Transact-SQL インストール コードをファイルにコピーし、Install.sql としてサンプル ディレクトリに保存します。

  6. 次のコマンドを実行して、アセンブリとストアド プロシージャを配置します。

    • sqlcmd -E -I -i install.sql -v root = "C:\MySample\"
  7. Transact-SQL テスト コマンド スクリプトをファイルにコピーし、test.sql としてサンプル ディレクトリに保存します。

  8. 次のコマンドを使用してテスト スクリプトを実行します。

    • sqlcmd -E -I -i test.sql
  9. Transact-SQL クリーンアップ スクリプトをファイルにコピーし、cleanup.sql としてサンプル ディレクトリに保存します。

  10. 次のコマンドを使用してこのスクリプトを実行します。

    • sqlcmd -E -I -i cleanup.sql

サンプル コード

このサンプルのコード リストを次に示します。

C#

using System;
using System.Globalization;
using System.Text;

/// <summary>
/// Include several string functions for T-SQL to manipulate surrogate characters.
/// </summary>
public sealed class SurrogateStringFunction
{
/// <summary>
///
/// </summary>
private SurrogateStringFunction()
{}

/// <summary>
/// LenS is equal to T-SQL string function LEN() which returns the number
/// of characters, rather than the number of bytes, of the given string expression.
/// </summary>
/// <param name="value">The input string.</param>
/// <returns>The number of characters in the string.</returns>
public static long LenS(String value)
{
if (value == null) throw new ArgumentNullException("value");

int[] myIndex;
// Remove trailing spaces for situations when the Transact-SQL variable or table column
// uses a fixed length datatype such as nchar(50).
// If the trailing spaces are not excluded, this function will return 50 which is not
// correct or expected.
myIndex = StringInfo.ParseCombiningCharacters(value.TrimEnd());
return (null != myIndex) ? myIndex.Length : 0;
}

/// <summary>
/// SubS only support character expression of T-SQL funciton SUBSTRING()
/// which returns part of a string.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="start">The position of the first character that will be returned.</param>
/// <param name="length">The number of characters to return.</param>
/// <returns>The string found at the starting position for the specified
/// number characters.</returns>

public static String SubS(String value, int start, int length)
{
if (value == null) throw new ArgumentNullException("value");
if (length < 0)
                throw new ArgumentException("Invalid length parameter passed to the substring function.");

// In Transact-SQL, the substring method initializes to 1. So, start should be initialized to at least 1.
// Length also has to be at least 1 or the Transact-SQL result would be an empty string.
            if ((start + length) <= 1)
                return (String.Empty);

// The 2 if statements below guarentee that the result will match the substring function in 
// Transact-SQL which will initialize start to 1 by subtracting from the length.
            if (start <= 0 && length > 0)
                length--;

            if ((start <= 0))
            {
                length = length + start;
                start = 1;
            }

            int[] myIndex;
myIndex = StringInfo.ParseCombiningCharacters(value);
int NumOfIndexes = (null != myIndex) ? myIndex.Length : 0;

            start--;
            if ((0 <= start) && (start < NumOfIndexes))
{
int lastIndex = start + length;

// if we are past the last char, then we get the string
// up to the last char 
if (lastIndex > (NumOfIndexes - 1))
{
return value.Substring(myIndex[start]);
}
else
{
return value.Substring(myIndex[start], myIndex[lastIndex] - myIndex[start]);
}
}
else
{
return String.Empty;
}
}

// 
//    
/// <summary>
/// LeftS is equal to T-SQL string function LEFT() which returns the left
/// part of a character string with the specified number of characters.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="start">The position of the first character that will be returned.</param>
/// <param name="length">The number of characters to return.</param>
/// <returns>The string found at the starting position for n-length.</returns>

public static String LeftS(String value, int length )
{
if (length < 0)
throw new ArgumentException("length must be a positive integer");

return SubS(value, 1, length);
}

// RightS is equal to T-SQL string function RIGHT() which returns the right
//    part of a character string with the specified number of characters.

public static String RightS(String value, int length)
{
if (length < 0)
throw new NotSupportedException("length must be a positive integer");
if (value == null) throw new ArgumentNullException("value");

int[] myIndex;

myIndex = StringInfo.ParseCombiningCharacters(value);
int numOfIndexes = (null != myIndex) ? myIndex.Length : 0;

if (numOfIndexes <= length)
return value;

if (length == 0) return String.Empty;

int virtualStartIndex = numOfIndexes - length;
int physicalStartIndex = myIndex[virtualStartIndex];
return value.Substring(physicalStartIndex);

}

//
// ReplaceS is equal to T-SQL string function REPLACE() which replaces all
// occurrences of the second given string expression in the first string expression
// with a third expression.
//
public static String ReplaceS(String value, String replaceValue, String newValue)
{
StringBuilder result = new StringBuilder(value.Length);
int[] myIndex;
int i = 0;
String upperValue = value.ToUpper(CultureInfo.CurrentUICulture);
String upperReplaceValue = replaceValue.ToUpper(CultureInfo.CurrentUICulture);

myIndex = StringInfo.ParseCombiningCharacters(upperValue);
while (i < value.Length)
{
int possibleMatch = upperValue.IndexOf(upperReplaceValue, i);
if (possibleMatch < 0)
{
result.Append(value.Substring(i));
break;
}
else
{
//Ensure we're not matching a partial surrogate
int surrogateIndex = Array.IndexOf<int>(myIndex, possibleMatch);
if (surrogateIndex < 0)
{
//We've matched in the middle of a surrogate, skip this match
//as it is not valid.
int nextStart = possibleMatch + 1;
result.Append(value.Substring(i, nextStart-i));
i = nextStart;
}
else
{
//This is a valid match.  Make the substitution.
result.Append(value.Substring(i, possibleMatch - i));
result.Append(newValue);
i = possibleMatch + replaceValue.Length;
}
}
}
return result.ToString();
}
}

Visual Basic

Imports Microsoft.VisualBasic
Imports System
Imports System.Collections
Imports System.Data
Imports System.Diagnostics
Imports System.Globalization
Imports System.Text
''' <summary>
''' Include several string functions for T-SQL to manipulate surrogate characters.
''' </summary>

Public NotInheritable Class SurrogateStringFunction
    ''' <summary>
    ''' Empty default constructor
    ''' </summary>
    Private Sub New()
    End Sub

    ''' <summary>
    ''' LenS is equal to T-SQL string function LEN() which returns the number
    ''' of characters, rather than the number of bytes, of the given string expression.
    ''' </summary>
    ''' <param name="value">The input string.</param>
    ''' <returns>The number of characters in the string.</returns>
    <Microsoft.SqlServer.Server.SqlFunction()> _
    Public Shared Function LenS(ByVal value As String) As Long
        If value Is Nothing Then
            Throw New ArgumentNullException("value")
        End If

        Dim myIndex() As Integer
        ' Remove trailing spaces for situations when the Transact-SQL variable or table column
        ' uses a fixed length datatype such as nchar(50).
        ' If the trailing spaces are not excluded, this function will return 50 which is not
        ' correct or expected.
        myIndex = StringInfo.ParseCombiningCharacters(value.TrimEnd())

        If (myIndex IsNot Nothing) Then
            Return myIndex.Length
        Else
            Return 0
        End If
    End Function

    ''' <summary>
    ''' SubS only support character expression of T-SQL funciton SUBSTRING()
    ''' which returns part of a string.
    ''' </summary>
    ''' <param name="value">The input string.</param>
    ''' <param name="start">The position of the first character that will be returned.</param>
    ''' <param name="length">The number of characters to return.</param>
    ''' <returns>The string found at the starting position for the specified
    ''' number characters.</returns>
    <Microsoft.SqlServer.Server.SqlFunction()> _
    Public Shared Function SubS(ByVal value As String, ByVal start As Integer, ByVal length As Integer) As String
        If value Is Nothing Then
            Throw New ArgumentNullException("value")
        End If

        If length < 0 Then
            Throw New ArgumentException("Invalid length parameter passed to the substring function.")
        End If

        ' In Transact-SQL, the substring method initializes to 1. So, start should be initialized to at least 1.
        ' Length also has to be at least 1 or the Transact-SQL result would be an empty string.
        If start + length <= 1 Then
            Return String.Empty
        End If

        ' The 2 if statements below guarentee that the result will match the substring function in 
        ' Transact-SQL which will initialize start to 1 by subtracting from the length.
        If start <= 0 AndAlso length > 0 Then
            length -= 1
        End If

        If start <= 0 Then
            length = length + start
            start = 1
        End If

        Dim myIndex() As Integer
        myIndex = StringInfo.ParseCombiningCharacters(value)

        Dim NumOfIndexes As Integer
        If (myIndex IsNot Nothing) Then
            NumOfIndexes = myIndex.Length
        Else
            NumOfIndexes = 0
        End If

        start -= 1
        If 0 <= start AndAlso start < NumOfIndexes Then
            Dim lastIndex As Integer = start + length

            ' if we are past the last char, then we get the string
            ' up to the last char 
            If lastIndex > NumOfIndexes - 1 Then
                Return value.Substring(myIndex(start))
            Else
                Return value.Substring(myIndex(start), myIndex(lastIndex) - myIndex(start))
            End If
        Else
            Return String.Empty
        End If
    End Function

    ''' <summary>
    ''' LeftS is equal to T-SQL string function LEFT() which returns the left
    ''' part of a character string with the specified number of characters.
    ''' </summary>
    ''' <param name="value">The input string.</param>
    ''' <param name="length">The number of characters to return.</param>
    ''' <returns>The string found at the starting position for n-length.</returns>
    <Microsoft.SqlServer.Server.SqlFunction()> _
    Public Shared Function LeftS(ByVal value As String, ByVal length As Integer) As String
        If length < 0 Then
            Throw New ArgumentException("Length must be a positive integer")
        End If

        Return SubS(value, 1, length)
    End Function

    ' RightS is equal to T-SQL string function RIGHT() which returns the right
    '    part of a character string with the specified number of characters.
    <Microsoft.SqlServer.Server.SqlFunction()> _
    Public Shared Function RightS(ByVal value As String, ByVal length As Integer) As String
        If value Is Nothing Then
            Throw New ArgumentNullException("value")
        End If

        If length < 0 Then
            Throw New NotSupportedException("Length must be a positive integer")
        End If

        Dim myIndex() As Integer

        myIndex = StringInfo.ParseCombiningCharacters(value)
        Dim NumOfIndexes As Integer
        If (myIndex IsNot Nothing) Then
            NumOfIndexes = myIndex.Length
        Else
            NumOfIndexes = 0
        End If


        If NumOfIndexes <= length Then
            Return value
        End If

        If length = 0 Then
            Return String.Empty
        End If

        Dim virtualStartIndex As Integer = NumOfIndexes - length
        Dim physicalStartIndex As Integer = myIndex(virtualStartIndex)

        Return value.Substring(physicalStartIndex)
    End Function

    ''' <summary>
    ''' ReplaceS is equal to T-SQL string function REPLACE() which replaces all
    ''' occurrences of the second given string expression in the first string expression
    ''' with a third expression.
    ''' </summary>
    ''' <param name="value"></param>
    ''' <param name="replaceValue"></param>
    ''' <param name="newValue"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Microsoft.SqlServer.Server.SqlFunction()> _
    Public Shared Function ReplaceS(ByVal value As String, ByVal replaceValue As String, ByVal newValue As String) As String
        Dim result As New StringBuilder(value.Length)
        Dim myIndex() As Integer
        Dim i As Integer = 0
        Dim upperValue As String = value.ToUpper(CultureInfo.CurrentUICulture)
        Dim upperReplaceValue As String = replaceValue.ToUpper(CultureInfo.CurrentUICulture)

        myIndex = StringInfo.ParseCombiningCharacters(upperValue)

        While i < value.Length
            Dim possibleMatch As Integer = upperValue.IndexOf(upperReplaceValue, i)
            If possibleMatch < 0 Then
                result.Append(value.Substring(i))
                Exit While
            Else
                'Ensure we're not matching a partial surrogate
                Dim surrogateIndex As Integer = Array.IndexOf(Of Integer)(myIndex, possibleMatch)
                If surrogateIndex < 0 Then
                    'We've matched in the middle of a surrogate, skip this match
                    'as it is not valid.
                    Dim nextStart As Integer = possibleMatch + 1
                    result.Append(value.Substring(i, nextStart - i))
                    i = nextStart
                Else
                    'This is a valid match.  Make the substitution.
                    result.Append(value.Substring(i, possibleMatch - i))
                    result.Append(newValue)
                    i = possibleMatch + replaceValue.Length
                End If
            End If
        End While

        Return result.ToString()
    End Function
End Class

次の Transact-SQL インストール スクリプト (Install.sql) は、アセンブリを配置し、データベースに補助文字対応関数を作成します。

Use [AdventureWorks]
Go

IF OBJECT_ID('[dbo].[len_s]') IS NOT NULL
  DROP FUNCTION [dbo].[len_s];

IF OBJECT_ID('[dbo].[sub_s]') IS NOT NULL
  DROP FUNCTION [dbo].[sub_s];

IF OBJECT_ID('[dbo].[left_s]') IS NOT NULL
  DROP FUNCTION [dbo].[left_s];

IF OBJECT_ID('[dbo].[right_s]') IS NOT NULL
  DROP FUNCTION [dbo].[right_s];

IF OBJECT_ID('[dbo].[replace_s]') IS NOT NULL
  DROP FUNCTION [dbo].[replace_s];
GO

IF EXISTS (SELECT [name] FROM sys.assemblies
             WHERE [name] = 'SurrogateStringFunction')
  DROP ASSEMBLY SurrogateStringFunction;
GO

USE master
GO

IF EXISTS (SELECT * FROM sys.server_principals WHERE [name] = 'ExternalSample_Login')
DROP LOGIN ExternalSample_Login;
GO

IF EXISTS (SELECT * FROM sys.asymmetric_keys WHERE [name] = 'ExternalSample_Key')
DROP ASYMMETRIC KEY ExternalSample_Key;
GO

--Before we register the assembly to SQL Server, we must arrange for the appropriate permissions.
--Assemblies with unsafe or external_access permissions can only be registered and operate correctly
--if either the database trustworthy bit is set or if the assembly is signed with a key,
--that key is registered with SQL Server, a server principal is created from that key,
--and that principal is granted the external access or unsafe assembly permission.  We choose
--the latter approach as it is more granular, and therefore safer.  You should never
--register an assembly with SQL Server (especially with external_access or unsafe permissions) without
--thoroughly reviewing the source code of the assembly to make sure that its actions 
--do not pose an operational or security risk for your site.

DECLARE @SamplesPath nvarchar(1024);

-- You may need to modify the value of the this variable if you have installed the sample someplace other than the default location.
set @SamplesPath = N'C:\MySample\'

EXEC('CREATE ASYMMETRIC KEY ExternalSample_Key FROM EXECUTABLE FILE = ''' + @SamplesPath 
    + 'SurrogateStringFunction.dll'';');
CREATE LOGIN ExternalSample_Login FROM ASYMMETRIC KEY ExternalSample_Key
GRANT EXTERNAL ACCESS ASSEMBLY TO ExternalSample_Login;
GO

USE AdventureWorks;
GO

--
-- Create assembly to register class methods for create functions
-- 
DECLARE @SamplesPath nvarchar(1024);
-- You may need to modify the value of the this variable if you have installed the sample someplace other than the default location.

set @SamplesPath = N'C:\MySample\'

CREATE ASSEMBLY [SurrogateStringFunction]
FROM @SamplesPath + 'SurrogateStringFunction.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO

CREATE FUNCTION [dbo].[len_s] (@str nvarchar(4000))
RETURNS bigint
AS EXTERNAL NAME [SurrogateStringFunction].[SurrogateStringFunction].[LenS];
GO

CREATE FUNCTION [dbo].[sub_s](@str nvarchar(4000), @pos int, @cont int)
RETURNS nvarchar(4000)
AS EXTERNAL NAME [SurrogateStringFunction].[SurrogateStringFunction].[SubS];
GO

CREATE FUNCTION [dbo].[left_s](@str nvarchar(4000), @cont int)
RETURNS nvarchar(4000)
AS EXTERNAL NAME [SurrogateStringFunction].[SurrogateStringFunction].[LeftS];
GO

CREATE FUNCTION [dbo].[right_s](@str nvarchar(4000), @cont int)
RETURNS nvarchar(4000)
AS EXTERNAL NAME [SurrogateStringFunction].[SurrogateStringFunction].[RightS];
GO

CREATE FUNCTION [dbo].[replace_s](@str nvarchar(4000), @str1 nvarchar(4000), @str2 nvarchar(4000))
RETURNS nvarchar(4000)
AS EXTERNAL NAME [SurrogateStringFunction].[SurrogateStringFunction].[ReplaceS];
GO

次の test.sql は、関数を実行してサンプルをテストします。

Use [AdventureWorks]
Go

-- left_s  VS  Left
print ('***** left_s VS Left *****');
select [dbo].[left_s](N'౷౸123',2);
print(N'Should Return ౷౸');
go
select Left(N'౷౸123',2);
print(N'Will Return ౷');
go

-- right_s VS Right
print ('***** right_s VS Right *****')
select [dbo].[right_s](N'౷౸123',5);
print(N'Should Return ౷౸123');
go
select Right(N'౷౸123',5);
print(N'Will Return ౸123');
go

-- len_s  VS Len
print('***** len_s VS Len *****');
select [dbo].[len_s](N'ƾǀǃ12');
print(N'Should Return 5');
go
select Len(N'ƾǀǃ12');
print(N'Will Return 8');
go

-- sub_s VS Substring
print('***** sub_s VS Subscription *****');
select [dbo].[sub_s] (N'♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳',3,5);
print(N'Should Return ♤♥♦♧♨');
go
select substring(N'♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳',3,5);
print(N'Will Return ♣♤');
go

-- replace_s VS Replace
print('***** replace_s VS Replace *****');
select [dbo].[replace_s](N'ᥕᥖᥗᥙᥚᥛᥕᥖᥗᥙᥚᥛ',N'ᥗᥙᥚ',N'ᦼ');
print(N'Should Return ᥖᦼᥛᥕᥖᦼᥛ');
go
select replace(N'ᥕᥖᥗᥙᥚᥛᥕᥖᥗᥙᥚᥛ',N'ᥗᥙᥚ',N'ᦼ');
print(N'Will Return ᦼ');
go

次の Transact-SQL は、アセンブリと関数をデータベースから削除します。

-- Drop assemblies and functions if they exist.
USE [AdventureWorks]
GO

IF OBJECT_ID('[dbo].[len_s]') IS NOT NULL
  DROP FUNCTION [dbo].[len_s];

IF OBJECT_ID('[dbo].[sub_s]') IS NOT NULL
  DROP FUNCTION [dbo].[sub_s];

IF OBJECT_ID('[dbo].[left_s]') IS NOT NULL
  DROP FUNCTION [dbo].[left_s];

IF OBJECT_ID('[dbo].[right_s]') IS NOT NULL
  DROP FUNCTION [dbo].[right_s];

IF OBJECT_ID('[dbo].[replace_s]') IS NOT NULL
  DROP FUNCTION [dbo].[replace_s];
GO

IF EXISTS (SELECT [name] FROM sys.assemblies
             WHERE [name] = 'SurrogateStringFunction')
  DROP ASSEMBLY SurrogateStringFunction;
GO

USE master
GO

IF EXISTS (SELECT * FROM sys.server_principals WHERE [name] = 'ExternalSample_Login')
DROP LOGIN ExternalSample_Login;
GO

IF EXISTS (SELECT * FROM sys.asymmetric_keys WHERE [name] = 'ExternalSample_Key')
DROP ASYMMETRIC KEY ExternalSample_Key;
GO

USE [AdventureWorks]
GO

関連項目

概念

CLR (共通言語ランタイム) 統合の使用シナリオと例