使用“ []”通配符将一个](右方括号)与PATINDEX匹配


9

我正在T-SQL †中编写自定义JSON解析器。

出于解析器的目的,我使用的PATINDEX功能是从标记列表中计算标记的位置。在我的情况下,标记都是单个字符,它们包括以下这些:

{} []:,

通常,当我需要找到几个给定字符中任何一个的(第一个)位置时,我会使用如下PATINDEX函数:

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

然后,该函数将为我提供aor b或or 的第一个位置c-两者中最先找到的那个SourceString

现在,就我而言,问题似乎与]角色有关。一旦在字符列表中指定了它,例如:

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

我的预期模式显然已损坏,因为该函数从未找到匹配项。看来我需要一种方法来转义第一个,]以便PATINDEX将其视为查找字符之一,而不是特殊符号。

我发现这个问题询问类似的问题:

但是,在那种情况下,]根本不需要在方括号中指定简单字符,因为它只是一个字符,可以在不带方括号的情况下进行指定。确实使用转义的替代解决方案仅针对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 '18年

Answers:


6

我自己的解决方案,更像是一种变通方法,在于指定一个字符范围,该范围包括]和使用该范围以及[ ]通配符中的其他字符。我使用了基于ASCII表的范围。根据该表,]角色位于以下附近:

十六进制字符
---
…
5A 90 Z
5B 91 [
5C 92 \
5D 93]
5E 94 ^
5F 95 _
…

我的范围,因此,采取的形式[-^,也就是说,它包含四个大字:[\]^。我还指定该模式使用二进制排序规则,以完全匹配ASCII范围。结果PATINDEX表达式最终看起来像这样:

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

这种方法的明显问题是模式开头的范围包括两个不需要的字符,\^。该解决方案为我工作的原因很简单,因为多余的字符永远不会出现在我需要解析的特定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 APPLYCHARINDEX

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

如果我对您需要做的事情缺少明显的了解,请留意。


4

过去,我曾见过在搜索之前先替换有问题的字符并将其放回原处的方法。

在这种情况下,我们可以执行以下操作:

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

SET @test = 'Test[]@String'

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

这段代码正确返回5。我正在使用¬字符,因为它不太可能出现-如果您不使用没有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

由于]仅特殊[...]用途,因此您可以使用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正在寻找以下几个字符之一(注意到的字符用方括号括起来),包括一个闭合的方括号。
RDFozz '18 -10-1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.