Kết hợp dấu ngoặc đơn trái và phải được sử dụng như dấu nháy đơn


7

Tôi có bốn cột chứa tên và muốn tìm kiếm chúng bằng cách sử dụng LIKEtrong môi trường Microsoft SQL Server.

Các biến chứng đi kèm mà tên có thể bao gồm trái và bên phải dấu chú giải đơn / dấu nháy góc cạnh (ví dụ , char(145)char(146)tương ứng), mà phải phù hợp với một dấu nháy đơn thẳng (ví dụ ', char(39))

Làm như sau rất chậm:

SELECT person_id
FROM person
WHERE REPLACE(
          REPLACE(
              person_name, 
              CHAR(145), 
              CHAR(39)
          ), 
          CHAR(146), 
          CHAR(39)
      ) LIKE '{USER_INPUT}'

Như đã giải thích trong câu lệnh thay thế SQL quá chậm trên Stack Overflow, điều này là do việc sử dụng REPLACElàm cho câu lệnh không thể mở rộng được.

Có cách nào để SQL Server có thể xử lý các tình huống như thế này theo cách tốt hơn không?

Một giải pháp đã được đề xuất là phải có ứng dụng tạo ra một giá trị 'tìm kiếm' mà concatenates tất cả các lĩnh vực ( person_name, person_surname, person_nickname, vv) và người cải đạo những nhân vật có vấn đề tại thời điểm chỉnh sửa. Điều này có thể được lập chỉ mục và tìm kiếm một cách hiệu quả. Lưu trữ dữ liệu này trong một bảng / cột SQL riêng biệt sẽ yêu cầu viết lại ứng dụng ít hơn so với thực hiện một giải pháp NoQuery đầy đủ như Lucene.

Ví dụ trên là một sự đơn giản hóa: truy vấn không được xây dựng theo đúng nghĩa đen như tôi đã giải thích ở trên và chúng tôi thực hiện các biện pháp bảo vệ SQL (và các biện pháp khác).

Câu hỏi là làm thế nào để thay thế các dấu nháy đơn bằng các dấu thẳng trong dữ liệu bảng. Làm rõ:

  • Nguồn cung cấp của người dùng O‘Malley- điều này phải phù hợp với cả O‘MalleyhoặcO'Malley
  • Nguồn cung cấp của người dùng O'Malley- điều này phải phù hợp với cả O‘MalleyhoặcO'Malley

Chúng ta cần thay thế dữ liệu SQL, không phải đầu vào của người dùng. Chúng ta có thể chuyển đổi đầu vào của người dùng trên đường đi qua ứng dụng để nếu họ nhập các dấu nháy đơn, chúng ta thay đổi chúng thành các dấu nháy đơn giản trước khi chuyển sang SQL. Đó là dữ liệu trong SQL chúng ta cần chuẩn hóa.

Thật không may, dữ liệu phải ở trong cơ sở dữ liệu dưới dạng khung chính xác, nhưng khi chúng ta thực hiện tìm kiếm, chúng ta cần đối chiếu tất cả chúng với các dấu nháy đơn.

Câu trả lời:


7

Cách tốt nhất để xử lý vấn đề của bạn (và tránh tiêm SQL) là chuyển vào đầu vào người dùng của bạn dưới dạng một biến. Vì bạn đang sử dụng, LIKEbạn có thể làm một cái gì đó như thế này:

CREATE TABLE #person (person_name nvarchar(50))
INSERT INTO #person VALUES (N'Bob'),(N'Bo''b'),(N'Bo‘b'),(N'Bo’b'),(N'Bo#b'),(N'Bo^b')

DECLARE @user_input nvarchar(50) = 'Bo’b'

SET @user_input = REPLACE(
                        REPLACE(
                                REPLACE(@user_input, N'‘', N''''), 
                                N'’', N''''), 
                                N'''', N'[‘’'']')

-- @user_input now == Bo[‘’']b

SELECT person_name
FROM #person
WHERE person_name LIKE @user_input

Về cơ bản, điều này thay thế tất cả các loại khác nhau bằng một loại duy nhất (') và sau đó đặt [] xung quanh cả ba để chúng được sử dụng trong LIKE.


Xin chào Kenneth, cảm ơn vì câu trả lời, nhưng đầu vào của người dùng chúng tôi có thể tiêu chuẩn hóa trên đường đi qua ứng dụng, đó là dữ liệu SQL mà chúng tôi muốn so sánh. Vì vậy, chúng tôi có thể 'làm thẳng' tất cả các dấu nháy đơn của người dùng trước khi chuyển chúng sang lệnh SQL, nhưng chúng tôi muốn khớp điều này với bất kỳ loại dấu nháy đơn nào trong các giá trị của Person_Name.
JLo

1
Tôi không chỉ nói thẳng đầu vào của người dùng. Tôi đang thiết lập một so sánh như thế. Nếu bạn nhìn vào biến @user_inputngay trước thực tế SELECTthì bạn sẽ thấy các ký tự giữa các []. Điều đó có nghĩa là vị trí cụ thể đó sẽ phù hợp với bất kỳ một trong số các nhân vật giữa họ.
Kenneth Fisher

1
@JLo Một lần nữa, những gì bạn đang yêu cầu chính xác là giải pháp của Kenneth ở đây: nó chuyển đổi bất kỳ 3 dấu nháy đơn nào thành ký tự đại diện nhiều ký tự - [‘’']- cho LIKEtoán tử. Do đó, không có vấn đề gì khi người dùng chuyển vào, ngay cả khi họ sử dụng nhiều loại trong một cụm từ tìm kiếm, vì mỗi loại sẽ khớp với bất kỳ loại nào trong 3 dữ liệu.
Solomon Rutzky

Cảm ơn! Tôi đã hoàn toàn bỏ lỡ điều đó, nó hoạt động hoàn hảo
JLo

2

Tôi có thể đã thề rằng tôi đã thấy những nhân vật này bị đánh đồng ở đâu đó nhưng bây giờ tôi không thể tìm thấy nó. Tôi đã kiểm tra tất cả các đối chiếu trong cả SQL Server 2012 và 2014 và không ai trong số chúng tương đương CHAR(39)với một trong hai đối tượng kia. Vì vậy, hãy quên ý tưởng ban đầu.

Tuy nhiên, một lựa chọn, nếu loại dấu nháy đơn chính xác không có tầm quan trọng cụ thể, là chỉ cập nhật dữ liệu:

UPDATE person 
SET person_name = REPLACE(...)

... để chuyển đổi CHAR(145)CHAR(146)thành CHAR(39). Sau đó, bạn không phải làm bất cứ điều gì lập trình. Bạn chỉ cần kiểm tra dữ liệu mỗi một lần trong một thời gian, hoặc tạo ra một kích hoạt để dịch các thành CHAR(39)thuận INSERThoặc UPDATE.


1
@JLo Tôi không thấy lý do tại sao bạn phải lưu trữ một bản sao của dữ liệu. Giải pháp đề xuất của Kenneth xử lý cả 3 trong dữ liệu hiện có. Xin vui lòng xem bình luận trả lời của tôi cho bạn về câu trả lời của anh ấy để biết thêm.
Solomon Rutzky

0

Hãy cẩn thận: bạn có chắc là bạn thực sự có Unicode ở đó chứ không phải một số bảng mã 8 bit? Mặc dù 145 được hiển thị dưới dạng khá nhiều phông chữ nhưng không chính thức trong tiêu chuẩn unicode (145 & 146 được liệt kê lần lượt là RIÊNG TƯ SỬ DỤNG MỘT VÀ RIÊNG TƯ SỬ DỤNG HAI: http://www.fileformat.info/info/unicode/ char / 0091 / index.htm ). Nếu bạn có văn bản 8 bit thì ký tự đó có thể trở thành bất kỳ thứ gì khác trong một bảng mã khác (145 là æ trong một số) để bạn có thể gặp vấn đề dịch thuật ký tự trong ứng dụng của mình. Báo giá đơn xoăn là 2018 và 2019 tương ứng trong unicode.

Các vấn đề mã hóa có thể xảy ra sang một bên: gói một hàm xung quanh cột bạn đang tìm kiếm sẽ khiến bộ lọc không thể mở rộng được như bạn đã suy luận. Trong trường hợp này, ba là rất ít bạn có thể làm về nó mà không thay đổi cấu trúc.

Tối thiểu, nếu bạn có một chỉ mục trên, person_namebạn hy vọng có thể biến quét bảng thành quét chỉ mục (mặc dù tùy thuộc vào các bộ lọc khác, bao gồm cả các bộ lọc được ngụ ý bởi các phép nối và v.v., trong truy vấn, nó có thể không hoàn toàn dễ dàng như vậy) .

Để thử tăng tốc mọi thứ đúng cách và biến những lần quét tiềm năng đó thành tìm kiếm chỉ mục, nếu bạn có quyền kiểm soát bố cục bảng cũng như mã truy vấn, bạn có thể tạo một cột được tính toán bền bỉ có chứa chuỗi "chính tắc" (trong trường hợp này là biểu diễn chính tắc sẽ được tạo ra bởi một cái gì đó như person_name_canon = REPLACE(REPLACE(person_name, CHAR(145), CHAR(39)), CHAR(146), CHAR(39)))), đảm bảo rằng cột có liên quan đến các chỉ mục có liên quan và khi tìm kiếm theo cột đó bạn có thể làm WHERE person_name_canon LIKE REPLACE(REPLACE(@user_input, CHAR(145), CHAR(39)), CHAR(146), CHAR(39))).

Nếu bạn không thể thêm cột được tính toán nhưng có một chỉ mục hữu ích về person_namecách tiếp cận hơi bị hack có thể là làm một việc như:

WHERE REPLACE(REPLACE(person_name, CHAR(145), CHAR(39)), CHAR(146), CHAR(39))) LIKE @user_input
AND   person_name LIKE CASE
                       WHEN @user_input LIKE '%''%'
                       THEN SUBSTIRNG(@user_input,1,CHARINDEX('''',@user_input)-1)
                       ELSE @user_input
                       END
                   + '%'

Xin lưu ý rằng tôi hoàn toàn không kiểm tra điều này vì vậy nó có thể không có hiệu quả mong muốn nhưng về lý thuyết, mệnh đề thứ hai sẽ dẫn đến một bộ lọc có thể thay đổi được, vì vậy nếu @user_input là Michael O'Briennó có thể sử dụng chỉ mục để tìm văn bản bắt đầu bằng văn bản Michael Okhác bộ lọc sẽ loại bỏ những thứ không khớp hoàn toàn sau khi tất cả các thay thế trên dữ liệu được lưu trữ được thực hiện.

Bạn cũng có thể cần tính đến các ký tự trích dẫn khác nhau trong đầu vào nếu điều đó không được chuẩn hóa trong logic nghiệp vụ của bạn trước khi tìm kiếm, như vậy:

WHERE REPLACE(REPLACE(person_name, CHAR(145), CHAR(39)), CHAR(146), CHAR(39))) LIKE @user_input
AND   person_name LIKE CASE
                       WHEN @user_input LIKE '%''%'            THEN SUBSTIRNG(@user_input,1,CHARINDEX('''',@user_input)-1)
                       WHEN @user_input LIKE '%'+CHAR(145)+'%' THEN SUBSTIRNG(@user_input,1,CHARINDEX(CHAR(145),@user_input)-1)
                       WHEN @user_input LIKE '%'+CHAR(146)+'%' THEN SUBSTIRNG(@user_input,1,CHARINDEX(CHAR(145),@user_input)-1)
                       ELSE @user_input
                       END
                   + '%'

1
Chào David. 1) Chúng ta có thể giả sử OP đang sử dụng dữ liệu 8 bit vì OP đang sử dụng CHAR(145)chứ không phải NCHAR(145)vì câu hỏi được đóng khung như hiện tại, không có khả năng, chậm. 2) Ngay cả khi dữ liệu thực tế là NVARCHAR, sử dụng CHAR(145)các tác phẩm vì nó sẽ hoàn toàn chuyển đổi thành NCHAR(8216). 3) Cột được tính toán bền bỉ là một sự lãng phí không gian hoàn toàn không cần thiết cả trên đĩa và trong bộ nhớ vì bạn có thể tìm kiếm một chỉ mục LIKEbằng cách sử dụng các ký tự đại diện, miễn là mẫu không bắt đầu bằng một. 4) Không có lý do gì để thực hiện hack vì câu trả lời của Kenneth xử lý tất cả các trường hợp.
Solomon Rutzky

@srutzky: 1) chính xác, do đó, codepage đang sử dụng trở thành một vấn đề, các trích dẫn xoăn có thể kết thúc ở một nơi khác và 145/146 (mặc dù chúng rất có thể đã được dịch sang 39, nhưng nó đáng để xem xét các ứng dụng quốc tế), 3) chỉ mục sẽ không hoạt động cho dù các ký tự đại diện ở đâu (không) nếu các chức năng được áp dụng cho cột vì chúng là ví dụ ( WHERE FUNCTION({column}) = {input}) không phải là mặt khác của so sánh
David Spillett

Thêm @srutzky: ... Nhưng có: 4) dịch đầu vào của người dùng theo cách Kenith làm (câu trả lời của anh ta không có khi tôi bắt đầu nhập của tôi) chắc chắn sạch sẽ hơn trong trường hợp đầu vào duy nhất, tôi đã quá phức tạp! Việc hack có thể có một số liên quan nếu cố gắng tham gia thay vì lọc theo một giá trị. Tôi sẽ có suy nghĩ khi tôi không làm việc và điều chỉnh lại hoặc xóa câu trả lời của tôi.
David Spillett
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.