ワイルドカード「[]」を使用して、](角かっこ)とPATINDEXを一致させる


9

T-SQL でカスタムJSONパーサーを作成しています。

私のパーサーのために、PATINDEXトークンのリストからトークンの位置を計算する関数を使用しています。私の場合のトークンはすべて1文字で、次のものが含まれています。

{} []:、

通常、与えられたいくつかの文字の(最初の)位置を見つける必要があるときは、次のPATINDEXような関数を使用します。

PATINDEX('%[abc]%', SourceString)

この関数はその後、私の最初の位置を与えるaか、bまたはcに-最初に発見される早い方- SourceString

今、私の場合の問題は]キャラクターに関連しているようです。文字リストで指定するとすぐに、たとえば次のようになります。

PATINDEX('%[[]{}:,]%', SourceString)

関数が一致を見つけられないため、私の意図したパターンは明らかに壊れています。私が最初に脱出する方法が必要ですように見えます]ので、PATINDEX検索文字ではなく、特別なシンボルの一つとして扱い、それを。

私は同様の問題について尋ねるこの質問を見つけました:

ただし、その場合、]単に1文字であり、大括弧なしで指定できるため、大括弧で指定する必要はありません。エスケープ使用しない別の解決策は、だけのために働くLIKEといないためPATINDEX、それが使用しているため、ESCAPE後者によって前者としないことによってサポートされ、副次句を。

だから、私の質問は、ワイルドカード使用してを探す方法はありますか?]PATINDEX[ ]または、他のTransact-SQLツールを使用してその機能をエミュレートする方法はありますか?

追加情報

上記PATINDEX[…]パターンで使用する必要があるクエリの例を次に示します。ここのパターンは(多少ではありますが)機能し]ます。文字が含まれていないためです。私もそれを使用する必要があり]ます:

WITH
  data AS (SELECT CAST('{"f1":["v1","v2"],"f2":"v3"}' AS varchar(max)) AS ResponseJSON),
  parser AS
  (
    SELECT
      Level         = 1,
      OpenClose     = 1,
      P             = p.P,
      S             = SUBSTRING(d.ResponseJSON, 1, NULLIF(p.P, 0) - 1),
      C             = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0), 1),
      ResponseJSON  = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0) + 1, 999999)
    FROM
      data AS d
      CROSS APPLY (SELECT PATINDEX('%[[{]%', d.ResponseJSON)) AS p (P)
    UNION ALL
    SELECT
      Level         = ISNULL(d.OpenClose - 1, 0) + d.Level + ISNULL(oc.OpenClose, 0),
      OpenClose     = oc.OpenClose,
      P             = d.P + p.P,
      S             = SUBSTRING(d.ResponseJSON, 1, NULLIF(p.P, 0) - 1),
      C             = c.C,
      ResponseJSON  = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0) + 1, 999999)
    FROM
      parser AS d
      CROSS APPLY (SELECT PATINDEX('%[[{}:,]%' COLLATE Latin1_General_BIN2, d.ResponseJSON)) AS p (P)
      CROSS APPLY (SELECT SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0), 1)) AS c (C)
      CROSS APPLY (SELECT CASE WHEN c.C IN ('[', '{') THEN 1 WHEN c.C IN (']', '}') THEN 0 END) AS oc (OpenClose)
    WHERE 1=1
      AND p.P <> 0
  )
SELECT
  *
FROM
  parser
OPTION
  (MAXRECURSION 0)
;

私が得る出力は:

Level  OpenClose  P   S      C   ResponseJSON
-----  ---------  --  -----  --  ---------------------------
1      1          1          {   "f1":["v1","v2"],"f2":"v3"}
1      null       6   "f1"   :   ["v1","v2"],"f2":"v3"}
2      1          7          [   "v1","v2"],"f2":"v3"}
2      null       12  "v1"   ,   "v2"],"f2":"v3"}
2      null       18  "v2"]  ,   "f2":"v3"}
2      null       23  "f2"   :   "v3"}
2      0          28  "v3"   }   

がいずれかの行の]一部として含まれていることがわかりSます。Level列は、ネストブラケットとブレースを意味し、ネストのレベルを示しています。ご覧のとおり、レベルが2になると、1に戻ることはありません。トークンとしてPATINDEX認識]させることができれば、そうなるでしょう。

上記の例で予想される出力は次のとおりです。

Level  OpenClose  P   S     C   ResponseJSON
-----  ---------  --  ----  --  ---------------------------
1      1          1         {   "f1":["v1","v2"],"f2":"v3"}
1      NULL       6   "f1"  :   ["v1","v2"],"f2":"v3"}
2      1          7         [   "v1","v2"],"f2":"v3"}
2      NULL       12  "v1"  ,   "v2"],"f2":"v3"}
2      0          17  "v2"  ]   ,"f2":"v3"}
1      NULL       18        ,   "f2":"v3"}
1      NULL       23  "f2"  :   "v3"}
1      0          28  "v3"  }

このクエリはdb <> fiddleで試すことができます。


SQL Server 2014を使用しており、JSON解析をネイティブでサポートするバージョンにすぐにアップグレードすることはほとんどありません。仕事をするアプリケーションを書くことはできますが、解析の結果をさらに処理する必要があります。これは、解析だけではなく、アプリケーションでより多くの作業が必要であることを意味します。 T-SQLスクリプト(結果に直接適用できる場合のみ)。

この問題の解決策としてSQLCLRを使用できる可能性はほとんどありません。ただし、誰かがSQLCLRソリューションを投稿することを決定してもかまいません。


どのように見えるjsonはどう["foo]bar”]ですか?
Salman A

@SalmanA:このようなシナリオは無視しても問題ありません。
Andriy M

回答:


6

私自身の解決策は、より回避策であり、を含む文字範囲を指定し、]その範囲を[ ]ワイルドカード内の他の文字と共に使用することで構成されていました。ASCIIテーブルに基づく範囲を使用しました。その表によると、]キャラクターは次の近所にいます:

Hex Dec Char
--- --- ----
…
5A 90 Z
5B 91 [
5C 92 \
5D 93]
5E 94 ^
5F 95 _
…

私の範囲は、それゆえ、の形をとった[-^、それは4つの文字が含まれてすなわち:[\]^。また、パターンがASCIIの範囲と正確に一致するようにバイナリ照合を使用することも指定しました。結果のPATINDEX式は次のようになります。

PATINDEX('%[[-^{}:,]%' COLLATE Latin1_General_BIN2, MyJSONString)

このアプローチの明らかな問題は、パターンの最初の範囲に2つの不要な文字が含まれ\ていること^です。解析が必要だった特定のJSON文字列に余分な文字が含まれることがないため、このソリューションは私にとってはうまくいきました。当然、これは一般的には真実ではあり得ないので、私はまだ他の方法に興味があります。


4

多くの文字列分割をしなければならなかったとき、私はおそらくこれを恐ろしく後ろから受けています。

既知の文字セットがある場合は、それらの表を作成します。

CREATE TABLE dbo.characters ( character CHAR(1) NOT NULL PRIMARY KEY CLUSTERED );

INSERT dbo.characters ( character )
SELECT *
FROM (
        SELECT '[' UNION ALL
        SELECT ']' UNION ALL
        SELECT '{' UNION ALL
        SELECT '}' UNION ALL
        SELECT ',' 
) AS x (v)

次に、その魔法をCROSS APPLY一緒に使用しますCHARINDEX

SELECT TOP 1000 p.Id, p.Body, ca.*
FROM dbo.Posts AS p
CROSS APPLY (
    SELECT TOP 1 CHARINDEX(c.character, p.Body) AS first_things_first
    FROM dbo.characters AS c
    ORDER BY CHARINDEX(c.character, p.Body) ASC
) AS ca
WHERE ca.first_things_first > 0

私があなたがする必要があることについて明白な何かを見逃しているなら、lemmeは知っています。


4

私は過去に、問題のある文字を検索する前に置き換えて、後で元に戻すというアプローチを見てきました。

この場合、次のようなことができます。

DECLARE @test NVARCHAR(MAX);
DECLARE @replacementcharacter CHAR(1) = CHAR(174);

SET @test = 'Test[]@String'

SELECT PATINDEX('%[[' + @replacementcharacter + '@]%', REPLACE(@test,']',@Replacementcharacter))

このコードは正しく5を返します。表示される可能性が低いため、I文字を使用しています。使用しないASCII文字がない場合、このソリューションは機能しません。

奇妙なことに、質問への直接の答えは「いいえ」です-PATINDEXで「]」を検索することもできませんが、置き換える場合は必要ありません。

同じ例ですが、変数の使用法はありません。

DECLARE @test NVARCHAR(MAX);

SET @test = 'Test[]@String'

SELECT PATINDEX('%[[' + CHAR(174) + '@]%', REPLACE(@test,']',CHAR(174)))

コードで上記のソリューションを使用すると、必要な結果が得られます。

WITH
  data AS (SELECT CAST('{"f1":["v1","v2"],"f2":"v3"}' AS varchar(max)) AS ResponseJSON),
  parser AS
  (
    SELECT
      Level         = 1,
      OpenClose     = 1,
      P             = p.P,
      S             = SUBSTRING(d.ResponseJSON, 1, NULLIF(p.P, 0) - 1),
      C             = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0), 1),
      ResponseJSON  = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0) + 1, 999999)
    FROM
      data AS d
      CROSS APPLY (SELECT PATINDEX('%[[{'+ CHAR(174) + ']%', REPLACE(d.ResponseJSON,']',CHAR(174)))) AS p (P)
    UNION ALL
    SELECT
      Level         = ISNULL(d.OpenClose - 1, 0) + d.Level + ISNULL(oc.OpenClose, 0),
      OpenClose     = oc.OpenClose,
      P             = d.P + p.P,
      S             = SUBSTRING(d.ResponseJSON, 1, NULLIF(p.P, 0) - 1),
      C             = c.C,
      ResponseJSON  = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0) + 1, 999999)
    FROM
      parser AS d
      CROSS APPLY (SELECT PATINDEX('%[[{}:,'+ CHAR(174) + ']%' COLLATE Latin1_General_BIN2, REPLACE(d.ResponseJSON,']',CHAR(174)))) AS p (P)
      CROSS APPLY (SELECT SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0), 1)) AS c (C)
      CROSS APPLY (SELECT CASE WHEN c.C IN ('[', '{') THEN 1 WHEN c.C IN (']', '}') THEN 0 END) AS oc (OpenClose)
    WHERE 1=1
      AND p.P <> 0
  )
SELECT
  *
FROM
  parser
OPTION
  (MAXRECURSION 0)
;

4

はで]のみ特別なので、の外に移動して2回[...]使用できます。との両方を評価します。一方の結果がゼロの場合は、もう一方の結果を取ります。それ以外の場合は、2つの値のうち小さい方を使用します。PATINDEX][...]PATINDEX('%[[{}:,]%', SourceString)PATINDEX('%]%', SourceString)

あなたの例では:

WITH
  data AS (SELECT CAST('{"f1":["v1","v2"],"f2":"v3"}' AS varchar(max)) AS ResponseJSON),
  parser AS
  (
    SELECT
      Level         = 1,
      OpenClose     = 1,
      P             = p.P,
      S             = SUBSTRING(d.ResponseJSON, 1, NULLIF(p.P, 0) - 1),
      C             = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0), 1),
      ResponseJSON  = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0) + 1, 999999)
    FROM
      data AS d
      CROSS APPLY (SELECT PATINDEX('%[[{]%', d.ResponseJSON)) AS p (P)
    UNION ALL
    SELECT
      Level         = ISNULL(d.OpenClose - 1, 0) + d.Level + ISNULL(oc.OpenClose, 0),
      OpenClose     = oc.OpenClose,
      P             = d.P + ISNULL(p.P, 0),
      S             = SUBSTRING(d.ResponseJSON, 1, p.P - 1),
      C             = c.C,
      ResponseJSON  = SUBSTRING(d.ResponseJSON, p.P + 1, 999999)
    FROM
      parser AS d
      CROSS APPLY (VALUES (NULLIF(PATINDEX('%[[{}:,]%', d.ResponseJSON), 0), NULLIF(PATINDEX('%]%', d.ResponseJSON), 0))) AS p_ (a, b)
      CROSS APPLY (VALUES (CASE WHEN p_.a < p_.b OR p_.b IS NULL THEN p_.a ELSE p_.b END)) AS p (P)
      CROSS APPLY (SELECT SUBSTRING(d.ResponseJSON, p.P, 1)) AS c (C)
      CROSS APPLY (SELECT CASE WHEN c.C IN ('[', '{') THEN 1 WHEN c.C IN (']', '}') THEN 0 END) AS oc (OpenClose)
    WHERE 1=1
      AND p.P <> 0
  )
SELECT
  *
FROM
  parser
OPTION
  (MAXRECURSION 0)
;

https://dbfiddle.uk/?rdbms=sqlserver_2014&fiddle=66fba2218d8d7d310d5a682be143f6eb


-4

左 '['の場合:

PATINDEX('%[[]%',expression)

正しい ']'の場合:

PATINDEX('%]%',expression)

1
これは、開始角括弧または終了角括弧を検索する方法を指定します。OPは、右大括弧を含むいくつかの文字(問題の文字を大括弧で囲むことに注意)の1つを探しています。
RDFozz 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.