Khớp a] (đóng dấu ngoặc vuông) với PATINDEX bằng ký tự đại diện [[]


9

Tôi viết một phân tích cú pháp JSON tùy chỉnh trong T-SQL .

Với mục đích của trình phân tích cú pháp của tôi, tôi đang sử dụng PATINDEXchức năng tính toán vị trí của mã thông báo từ danh sách mã thông báo. Các mã thông báo trong trường hợp của tôi đều là các ký tự đơn và chúng bao gồm:

{} [] :,

Thông thường, khi tôi cần tìm vị trí (đầu tiên) của bất kỳ ký tự nào, tôi sử dụng PATINDEXhàm như thế này:

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

Hàm sau đó sẽ cho tôi vị trí đầu tiên của ahoặc bhoặc c- bất kỳ trường hợp nào được tìm thấy trước - trong SourceString.

Bây giờ vấn đề trong trường hợp của tôi dường như được kết nối với ]nhân vật. Ngay sau khi tôi chỉ định nó trong danh sách nhân vật, ví dụ như thế này:

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

mô hình dự định của tôi dường như bị phá vỡ, bởi vì chức năng không bao giờ tìm thấy một kết quả khớp. Có vẻ như tôi cần một cách để thoát khỏi cái đầu tiên ]để PATINDEXcoi nó như một trong những nhân vật tra cứu hơn là một biểu tượng đặc biệt.

Tôi đã tìm thấy câu hỏi này hỏi về một vấn đề tương tự:

Tuy nhiên, trong trường hợp đó, ]đơn giản là không cần phải được chỉ định trong ngoặc, bởi vì nó chỉ là một ký tự và nó có thể được chỉ định mà không có dấu ngoặc quanh chúng. Giải pháp thay thế, sử dụng thoát, chỉ hoạt động cho LIKEvà không PATINDEXsử dụng, bởi vì nó sử dụng một phần ESCAPEphụ, được hỗ trợ bởi cái trước chứ không phải cái sau.

Vì vậy, câu hỏi của tôi là, có cách nào để tìm kiếm ]bằng PATINDEXcách sử dụng [ ]ký tự đại diện không? Hoặc có cách nào để mô phỏng chức năng đó bằng các công cụ Transact-SQL khác không?

thông tin thêm

Dưới đây là một ví dụ về truy vấn mà tôi cần sử dụng PATINDEXvới […]mẫu như trên. Các mô hình ở đây hoạt động (mặc dù phần nào ) bởi vì nó không bao gồm các ]nhân vật. Tôi cũng cần nó để làm việc với ]:

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)
;

Đầu ra tôi nhận được là:

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"   }   

Bạn có thể thấy rằng nó ]được bao gồm như là một phần của Smột trong các hàng. Các Levelcột chỉ ra mức độ làm tổ, có nghĩa là khung và niềng răng làm tổ. Như bạn có thể thấy, một khi cấp độ trở thành 2, nó sẽ không bao giờ trở lại 1. Nó sẽ có nếu tôi có thể PATINDEXnhận ra ]là mã thông báo.

Đầu ra dự kiến ​​cho ví dụ trên là:

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"  }

Bạn có thể chơi với truy vấn này tại db <> fiddle .


Chúng tôi đang sử dụng SQL Server 2014 và không có khả năng sớm nâng cấp lên một phiên bản hỗ trợ JSON phân tích nguyên bản. Tôi có thể viết một ứng dụng để thực hiện công việc nhưng kết quả của việc phân tích cú pháp cần được xử lý thêm, điều này hàm ý nhiều công việc trong ứng dụng hơn là phân tích cú pháp - loại công việc sẽ dễ dàng hơn và có thể hiệu quả hơn, được thực hiện với một tập lệnh T-SQL, nếu chỉ tôi có thể áp dụng nó trực tiếp vào kết quả.

Rất khó có khả năng tôi có thể sử dụng SQLCLR làm giải pháp cho vấn đề này. Tuy nhiên, tôi không phiền nếu ai đó quyết định đăng một giải pháp SQLCLR, vì điều đó có thể hữu ích cho những người khác.


Json trông như thế ["foo]bar”]nào?
Salman A

@SalmanA: Những kịch bản như vậy có thể được bỏ qua một cách an toàn.
Andriy M

Câu trả lời:


6

Giải pháp của riêng tôi, một cách giải quyết khác, bao gồm việc chỉ định một phạm vi ký tự bao gồm ]và sử dụng phạm vi đó cùng với các ký tự khác trong [ ]ký tự đại diện. Tôi đã sử dụng một phạm vi dựa trên bảng ASCII. Theo bảng đó, ]nhân vật nằm trong khu phố sau:

Hex Dec Char
--- --- ----
Giáo dục
5A 90 Z
5B 91 [
5C 92 \
5D 93]
5E 94 ^
5F 95 _
Giáo dục

Phạm vi của tôi, do đó, mất dạng [-^, tức là nó bao gồm bốn nhân vật: [, \, ], ^. Tôi cũng đã xác định rằng mẫu sử dụng đối chiếu nhị phân, để khớp chính xác với phạm vi ASCII. PATINDEXBiểu thức kết quả cuối cùng trông như thế này:

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

Vấn đề rõ ràng với cách tiếp cận này là phạm vi ở đầu mẫu bao gồm hai ký tự không mong muốn \^. Giải pháp này hiệu quả với tôi đơn giản vì các ký tự phụ không bao giờ có thể xảy ra trong các chuỗi JSON cụ thể mà tôi cần phân tích. Đương nhiên, điều này không thể đúng nói chung, vì vậy tôi vẫn quan tâm đến các phương pháp khác, hy vọng phổ quát hơn của tôi.


4

Tôi có một điều có lẽ là khủng khiếp về điều này từ khi tôi phải thực hiện nhiều thao tác tách chuỗi.

Nếu bạn có một bộ ký tự đã biết, hãy tạo một bảng gồm chúng.

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)

Sau đó sử dụng phép thuật đó CROSS APPLYcùng với 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

Nếu tôi thiếu một cái gì đó rõ ràng về những gì bạn cần làm, tôi sẽ biết.


4

Tôi đã thấy các cách tiếp cận trong quá khứ để thay thế nhân vật vi phạm trước khi tìm kiếm và đưa nó trở lại sau đó.

Trong trường hợp này, chúng ta có thể làm một cái gì đó như:

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

SET @test = 'Test[]@String'

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

Mã này trả về đúng 5. Tôi đang sử dụng ký tự as vì không thể xuất hiện - nếu không có ký tự ASCII nào bạn sẽ không sử dụng, giải pháp này sẽ không hoạt động.

Mặc dù thật kỳ lạ, câu trả lời trực tiếp cho câu hỏi của bạn sẽ là không - Tôi cũng không thể có được PATINDEX để tìm kiếm ']', nhưng nếu bạn thay thế thì bạn không cần phải làm vậy.

Ví dụ tương tự nhưng không có cách sử dụng biến:

DECLARE @test NVARCHAR(MAX);

SET @test = 'Test[]@String'

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

Sử dụng giải pháp trên trong mã của bạn mang lại kết quả cần thiết:

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

]chỉ đặc biệt trong [...], bạn có thể sử dụng PATINDEXhai lần, di chuyển ra ]ngoài [...]. Đánh giá cả PATINDEX('%[[{}:,]%', SourceString)PATINDEX('%]%', SourceString). Nếu một kết quả bằng 0, hãy lấy kết quả khác. Nếu không, lấy giá trị nhỏ hơn của hai giá trị.

Trong ví dụ của bạn:

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=66fba2218d8d7d 310d5a682be143f6eb


-4

Đối với bên trái '[':

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

Vì một quyền ']':

PATINDEX('%]%',expression)

1
Cái này chỉ định cách tìm kiếm một dấu ngoặc vuông mở hoặc một dấu đóng; OP đang tìm kiếm một trong nhiều ký tự (được lưu ý bằng cách đặt các ký tự được đề cập trong dấu ngoặc vuông), bao gồm dấu ngoặc vuông đóng.
RDFozz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.