2016-07-29 42 views
0

N番目の文字の前に部分文字列を返すSQL Server関数を作成しました。文字列内のN番目の文字の検索を最適化する

たとえば、 SELECT dbo.fn_getFirstNthSentence('.', 'hello world.It.is.raining.today', 3) は「hello world.It.Is。」を返します。結果として。

私が書いた機能は汚いとゆっくりと見えるので、私はそれを最適化したいと思います。 清潔にするためのアドバイスをいただければ幸いです。

ありがとうございます。

CREATE FUNCTION fn_getFirstNthSentence 
(
    @TargetStr VARCHAR(MAX) , 
    @SearchedStr VARCHAR(8000) , 
    @Occurrence INT 
) 
RETURNS varchar(MAX) 
AS 
BEGIN 

    DECLARE @pos INT , 
     @counter INT , 
     @ret INT; 

    SET @pos = CHARINDEX(@TargetStr, @SearchedStr); 

    IF (@pos = 0) 
     RETURN @SearchedStr 

    SET @counter = 1; 

    IF @Occurrence = 1 
     SET @ret = @pos; 

    ELSE 
     BEGIN 

      WHILE (@counter < @Occurrence) 
       BEGIN 

        IF(LEN(@SearchedStr) < @pos + 1) 
         RETURN @SearchedStr 

        SELECT @ret = CHARINDEX(@TargetStr, @SearchedStr, 
              @pos + 1); 
        IF(@ret = 0) 
         RETURN @SearchedStr 
        SET @counter = @counter + 1; 
        SET @pos = @ret; 
       END; 
     END; 
    RETURN LEFT(@SearchedStr, @ret) 
END; 
+1

あなたのコードがエラーなしで実行された場合、その後、あなたに賞賛していますが、[コードレビュー](http://codereview.stackexchange.com/)に投稿することができます。 –

+1

SQL Serverは本当にデータを扱うのには優れていますが、文字列の操作には不向きです。あなたはt-sqlを使ってこれをもっと速くすることができますが、CLRよりもSQLの方が速いとは思いません。 –

+0

誰かが正規表現を持っている可能性があります – Cato

答えて

1

は、区切り文字列スプリッタを使用してさらに別の選択肢です。既に投稿されたXMLメソッドは良い方法ですが、このアプローチではテーブル変数は必要ありません。

これは、パフォーマンスを非常に高速に保つインラインテーブル値関数として作成されます。

create function fn_getFirstNthSentence 
(
    @SearchedStr varchar(100) 
    , @Occurrence int 
    , @Delimiter char(1) 
) returns table as return 

    with ParsedValues as 
    (
     select Item 
      , ItemNumber 
     from dbo.DelimitedSplit8K(@SearchedStr, @Delimiter) 
     where ItemNumber <= @Occurrence 
    ) 

    select top 1 ResultString = STUFF(
    (
     select @Delimiter + Item 
     from ParsedValues 
     order by ItemNumber 
     for xml path('')), 1,1, '') + @Delimiter 
    from ParsedValues 

これは、Jeff Modenによって作成されたスプリッタも使用しています。それは、他のスプリッタのどれもが値をもたらした位置を示す列を持っていないという特徴があります。彼の記事はここで次の議論を見つけることができます。 http://www.sqlservercentral.com/articles/Tally+Table/72993/

実行する場合は、これを非常に簡単に実行できます。

declare @String varchar(100) = 'hello world.It.is.raining.today.' 
    , @Num int = 3 
    , @Delimiter char(1) = '.' 
; 

select * 
from fn_getFirstNthSentence(@String, @Num, @Delimiter) 

あなたがJeff Modenのスプリッタが好きではない場合は、ここにいくつかのオプションがあります。 http://sqlperformance.com/2012/07/t-sql-queries/split-strings私はModenをすべて使用しませんが、解析された値を素晴らしい順に保つ必要があるときには使用しません。ここで

--EDIT--

あなたはこれではなく、インラインテーブル値関数のスカラー関数になることを修正することができる方法です。私の好みはitvfをより速く柔軟に保つことです。

create function fn_getFirstNthSentenceScalar 
(
    @SearchedStr varchar(100) = 'hello world.It.is.raining.today.this is after 5' 
    , @Occurrence int = 5 
    , @Delimiter char(1) = '.' 
) returns varchar(max) as begin 

    declare @RetVal varchar(max); 

    with ParsedValues as 
    (
     select Item 
      , ItemNumber 
     from dbo.DelimitedSplit8K(@SearchedStr, @Delimiter) 
     where ItemNumber <= @Occurrence 
    ) 

    select top 1 @RetVal = STUFF(
    (
     select @Delimiter + Item 
     from ParsedValues 
     order by ItemNumber 
     for xml path('')), 1,1, '') + @Delimiter 
    from ParsedValues; 

    return @RetVal 
end 
+0

ありがとうございます。テーブルとしてではなく、varcharとして戻る方法はありますか? – jkl

+0

私がインラインテーブル値関数を使用したのは、スカラー関数よりも速いためです。これを変更してスカラー値を返すことはできますが、パフォーマンスと柔軟性を犠牲にすることになります。 –

+0

ありがとうございます。私はスカラー関数を使うより速い方法があればいいと思っていました。しかし、本当にありがとう!大きな助け – jkl

1

これらの関数は地雷フィールドであることがわかり、私はいくつかの単純化を試みた地雷を踏んでのリスクの--I - 性能が多分微視的改善

alter FUNCTION fn_getFirstNthSentence 
(
@TargetStr VARCHAR(MAX) , 
@SearchedStr VARCHAR(8000) , 
@Occurrence INT 
) 
RETURNS varchar(MAX) 
AS 
BEGIN 

DECLARE @pos INT , 
    @counter INT ; 

IF @Occurrence < 1 
    RETURN NULL; 

SELECT @counter = 0, @POS = 1; 

WHILE (@counter < @Occurrence AND @POS > 0) 
BEGIN 

    SELECT @POS = CHARINDEX(@TargetStr, @SearchedStr, 
           @pos + 1); 
    IF @POS > 0 
     SET @counter = @counter + 1; 

END; 
RETURN CASE WHEN @POS > 0 THEN 
      LEFT(@SearchedStr, @POS) 
     ELSE 
      @SearchedStr 
     END; 

END; 
+0

ありがとうございます。それはよりシンプルで、きれいで速いようです! – jkl

1

別オプションはXML経由です

私はあなたのベンチマークを見ることはできませんが、はるかに少ないコードです。追加のオプションは、パラメータを追加して変更することで3番目から5番目のオカレンスを見つけることです。ここで、Seq < = @ FindPosSeq範囲1と範囲2の間です。

Declare @FindPos int = 3 
Declare @String varchar(max) = 'hello world.It.is.raining.today' 
Declare @Delim varchar(10) = '.' 


Declare @XML xml,@RetVal varchar(max) = '' 
Set @XML = Cast('<x>' + Replace(@String,@Delim,'</x><x>')+'</x>' as XML) 

Declare @Table table (Seq int identity(1,1),String varchar(max)) 
Insert Into @Table Select ltrim(rtrim(String.value('.', 'varchar(max)')))[email protected] as value FROM @XML.nodes('x') as T(String) 

Select @[email protected] + String from @Table Where Seq<[email protected] Order By Seq 

Select @RetVal 

戻り

hello world.It.is. 

EDIT:それは助けている場合、下記の 正規化された表を戻し、私の一般的な構文解析機能...だから

CREATE FUNCTION [dbo].[udf-Str-Parse] (@String varchar(max),@Delimeter varchar(10)) 
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',') 
--  Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ') 
--  Select * from [dbo].[udf-Str-Parse]('id26,id46|id658,id967','|') 
--  Select * from [dbo].[udf-Str-Parse]('hello world. It. is. . raining.today','.') 

Returns @ReturnTable Table (Key_PS int IDENTITY(1,1), Key_Value varchar(max)) 
As 
Begin 
    Declare @XML xml;Set @XML = Cast('<x>' + Replace(@String,@Delimeter,'</x><x>')+'</x>' as XML) 
    Insert Into @ReturnTable Select Key_Value = ltrim(rtrim(String.value('.', 'varchar(max)'))) FROM @XML.nodes('x') as T(String) 
    Return 
End 

です例:

Select * from [dbo].[udf-Str-Parse]('hello world.It.is.raining.today','.') 

戻りここで

Key_PS Key_Value 
1  hello world 
2  It 
3  is 
4  raining 
5  today 
+0

ありがとうございます。これは私が考えることができなかったものです。 – jkl

+0

文字列解析の最適化がパフォーマンスを修正することはありません。データベースは* all *行を読み込み、個々の値を解析する必要があります。この質問はおそらく、非正規化のような別の問題を隠すでしょう。これを修正する唯一の方法は、オリジナルの問題を修正することです。 –

+0

@PanagiotisKanavos 100%合意。誰かが、文字列を解析するためにどれだけ多くの質問があるのか​​に関する分析を行うべきです。 –

関連する問題