web-dev-qa-db-ja.com

SQL Server全文検索-rtfタグによって.rtfファイルのインデックスが誤って作成される

SQL Server DBのvarbinary(max)列にフルテキストインデックスを設定しました。 type列が指定されており、「。doc」、「。pdf」などのファイルの拡張子が含まれています。

ただし、.rtfファイルにインデックスが付けられている場合、SQLはすべてのメタ情報(RTFタグ "listoverridecount0"など)をファイルに含めていることに気付きました。

これはインデックスをかなり膨らませており、検索がこれらのタグで一致することも意味します(つまり、「listoverridecount0」で検索して、すべての.rtfを返すことができます)。

.rtfのiFilterがRTFタグを削除しないのはなぜですか?

これを実行すると:

SELECT * FROM sys.fulltext_document_types WHERE document_type = '.rtf';

私はこれを手に入れます:

document_type  .rtf                                     
class_id       C7310720-AC80-11D1-8DF3-00C04FB6EF4F 
path       c:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\Binn\msfte.dll
version        12.0.6828.0
manufacturer   Microsoft Corporation

Microsoft Connectバグ を提出しました。回避策を見つけることができないようです。おそらくこれは、a)RTF iFilterがタグを削除しないことによるエラー、またはb)全文索引の問題のいずれかです。

私のSQL Serverのバージョンは次のとおりです。

Microsoft SQL Server 2012(SP1)-11.0.3393.0(X64)
 2013年10月25日19:04:40 
著作権(c)Microsoft Corporation 
 Developer Edition(64ビット) Windows NT 6.2(ビルド9200:)(ハイパーバイザー)
6
oatsoda

これは再現できますが、いくつかの選択肢があると思います。

  • フルテキストインデックスの膨張についてはあまり心配しないでください。 「rtf1」、「pard」、「wmetafile0」を検索する人はそれほど多くなく、パフォーマンスにそれほど影響を与えるとは思いません。誰かが「赤」または「青」を検索していると、奇妙な衝突が発生する可能性がありますが、これはリスクがかなり低いと思います。
  • 文字列をクリーンアップします。例: here からコピーしたRegExを使用します。それをうまく利用しているようです。 stream_id(ドキュメントの一意のID)と別のテーブルに保存し、代わりにそのテーブルにフルテキストインデックスを付けます。
-- Convert rtf to plain text
SELECT 
    CAST( file_stream AS VARCHAR(MAX) ) original
    , MDS.mdq.RegExReplace( 
        CAST( file_stream AS VARCHAR(MAX) ), 
        '\{\*?\\[^{}]+}|[{}]|\\\n?[A-Za-z]+\n?(?:-?\d+)?[ ]?', '', 1 )
FROM dbo.Documents
  • CLR関数を使用した同様のアプローチ ここ から適応:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text;
using System.Linq;  // for TakeWhile

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlChars rtfToText( SqlChars inputRtf )
    {
        // RTF function lifted from here and adapted for SQL CLR:
        // https://stackoverflow.com/questions/23277178/richtextbox-throws-outofmemory-on-Azure

        bool slash = false; //indicates if backslash followed by the space
        bool figure_opened = false; //indicates if opening figure brace followed by the space
        bool figure_closed = false; //indicates if closing brace followed by the space
        bool first_space = false; //the else spaces are in plain text and must be included to the result

        if (inputRtf.Length < 4) return new SqlChars ( string.Empty );

        int i = 0;
        i = inputRtf.ToString().IndexOf("\\pard");
        if (i < 1) return new SqlChars(string.Empty);

        var builder = new StringBuilder();
        for (int j = i; j < inputRtf.Length - 1; j++)
        {
            char ch = inputRtf[j];
            char nextCh = inputRtf[j + 1];

            if (ch == '\\' && nextCh == 'p') // appends \n if \pard, except for first
            {
                if (j > i && j < inputRtf.Length - 4)
                {
                    string fiveChars = inputRtf.ToString().Substring(j, 5);
                    if (fiveChars.Equals("\\pard"))
                    {
                        builder.Append("\n");
                    }
                }
            }

            if (ch == '\\' && nextCh == 'u') // to deal correctly with special characters
            {
                string fourChars = inputRtf.ToString().Substring(j + 2, 4);
                string digits = new string(fourChars.TakeWhile(char.IsDigit).ToArray());
                char specialChar = (char)int.Parse(digits);
                builder.Append(specialChar);
                j += digits.Length + 5;
                continue;
            }

            if (ch == '\\' && nextCh == '{') // if the text contains symbol '{'
            {
                slash = false;
                figure_opened = false;
                figure_closed = false;
                first_space = false;
                builder.Append('{');
                j++;
                continue;
            }
            else if (ch == '\\' && nextCh == '}') // if the text contains symbol '}'
            {
                slash = false;
                figure_opened = false;
                figure_closed = false;
                first_space = false;
                builder.Append('}');
                j++;
                continue;
            }
            else if (ch == '\\' && nextCh == '\\') // if the text contains symbol '\'
            {
                slash = false;
                figure_opened = false;
                figure_closed = false;
                first_space = false;
                builder.Append('\\');
                j++;
                continue;
            }
            else if (ch == '\\') // we are looking at the backslash
            {
                first_space = true;
                slash = true;
            }
            else if (ch == '{')
            {
                first_space = true;
                figure_opened = true;
            }
            else if (ch == '}')
            {
                first_space = true;
                figure_closed = true;
            }
            else if (ch == ' ')
            {
                slash = false;
                figure_opened = false;
                figure_closed = false;
            }

            if (!slash && !figure_opened && !figure_closed)
            {
                if (!first_space)
                {
                    builder.Append(ch);
                }
                else
                {
                    first_space = false;
                }
            }
        }

        // Return
        return new SqlChars(builder.ToString());
    }
};

これは世界で最も効率的なCLR関数ではないと確信していますが、テストした約100のドキュメントで問題なく機能しましたが、いくつかの失敗がありました。

接続アイテムについて何か聞こえた場合はお知らせください。

2
wBob

使用しているMSSQLのバージョンはわかりませんが、このリンクは役に立ちますか? SQL Filters SQL Server内で設定されているさまざまなフィルターを調べます。rtfフィルターが正しく読み込まれていない可能性がありますか?

0
EJW