Không thể cập nhật CẦU BẠC CỰC LẠ


19

Đưa ra bảng này:

CREATE TABLE test (
    id INT NOT NULL,
    description NVARCHAR(100) COLLATE Modern_Spanish_CI_AS NOT NULL
);
INSERT INTO test (id, description) VALUES (1, 'CO2');

Tôi nhận ra rằng tôi không thể khắc phục một vấn đề chính tả:

SELECT * FROM test WHERE id = 1;
UPDATE test SET description = 'CO₂' WHERE id = 1;
SELECT * FROM test WHERE id = 1;

bởi vì bản cập nhật khớp nhưng không có tác dụng:

id          description
----------- -----------
1           CO2

(1 affected rows)

(1 affected rows)

id          description
----------- -----------
1           CO2

(1 affected rows)

Đó là nếu SQL Server xác định rằng, kể từ khi rõ ràng chỉ là một nhỏ 2 , giá trị cuối cùng sẽ không thay đổi vì vậy nó không đáng để thay đổi nó.

Ai đó có thể làm sáng tỏ điều này và có thể đề xuất một cách giải quyết (ngoài việc cập nhật lên giá trị trung gian)?


1
Álvaro: nếu bạn muốn tìm hiểu thêm về hành vi này, để hiểu rõ hơn lý do tại sao điều này xảy ra, vui lòng xem hai liên kết mà tôi vừa thêm vào cuối câu trả lời của tôi.
Solomon Rutzky

Câu trả lời:


29

Tiểu mục 2 không phải là một phần của bộ ký tự varchar (trong bất kỳ đối chiếu nào, không chỉ Modern_Sp Biến). Vì vậy, làm cho nó một hằng số nvarchar:

UPDATE test SET description = N'CO₂' WHERE id = 1;

1
Tôi không chỉ cố định giá trị, tôi còn hiểu làm thế nào nó đạt được điều đó ngay từ đầu. Cảm ơn bạn!
Álvaro González

2
@ Asia có thể sử dụng cùng một trang Mã). Tuy nhiên, "Đăng ký 2" có sẵn trong Bộ sưu tập của Hàn Quốc. Điều đó sẽ không giúp đỡ ở đây, nhưng chỉ là FYI. Tôi có chi tiết và một ví dụ trong câu trả lời của tôi .
Solomon Rutzky

21

@gbn đã giải thích lý do cơ bản và cách khắc phục, nhưng lý do cụ thể cho hành vi mà bạn đang thấy là đây:

  1. Bạn đang sử dụng một VARCHARchữ (không có Ntiền tố) thay vì bằng NVARCHARchữ (chuỗi có Ntiền tố), do đó, ký tự Unicode sẽ được chuyển đổi thành VARCHAR.
  2. VARCHARlà một mã hóa 8 bit, trong hầu hết các trường hợp, một byte cho mỗi ký tự, nhưng cũng có thể là hai byte cho mỗi ký tự. Mặt khác, NVARCHARmã hóa 16 bit (UTF-16 Little Endian) là hai byte hoặc bốn byte cho mỗi ký tự.
  3. Do sự khác biệt về số lượng byte có sẵn để sử dụng để ánh xạ các ký tự, mã hóa 8 bit, về bản chất, bị hạn chế hơn nhiều về số lượng ký tự có thể được ánh xạ. VARCHARdữ liệu lên tới 256 ký tự cho Bộ ký tự một byte (phần lớn trong số chúng) và tối đa 65.536 ký tự cho Bộ ký tự Double-Byte (chỉ một vài trong số này). Mặt khác, NVARCHARdữ liệu có thể ánh xạ chỉ hơn 1,1 triệu ký tự Unicode (mặc dù chỉ dưới 250k hiện được ánh xạ).
  4. Do số lượng ánh xạ có thể được thực hiện với 8 bit / VARCHARdữ liệu, các nhóm ký tự khác nhau (dựa trên Ngôn ngữ / Văn hóa) được trải rộng trên nhiều "Trang mã" (nghĩa là bộ ký tự)
  5. Mỗi Collation chỉ định Trang mã nào, nếu có, được sử dụng cho VARCHARdữ liệu ( NVARCHARlà tất cả các ký tự)
  6. Khi chuyển đổi một chuỗi bằng chữ hoặc biến từ NVARCHAR (ví dụ Unicode / UTF-16 / tất cả các ký tự) thành VARCHAR(bộ ký tự dựa trên Trang mã được chỉ định trong hầu hết các Collations), Collation mặc định của Cơ sở dữ liệu được sử dụng
  7. Nếu Trang Mã của Đối chiếu được sử dụng cho chuyển đổi không chứa cùng một ký tự, nhưng chứa ánh xạ "phù hợp nhất", thì ánh xạ "phù hợp nhất" sẽ được sử dụng.
  8. Nếu Trang Mã của Đối chiếu đang được sử dụng cho chuyển đổi không chứa cùng một ký tự hoặc chứa ánh xạ "phù hợp nhất", thì ký tự "thay thế" mặc định sẽ được sử dụng (phổ biến nhất ?).

Vì vậy, những gì bạn đang nhìn thấy là một NVARCHARđể VARCHARchuyển đổi do thiếu Ntiền tố đen trên chuỗi. Và, Trang Mã của Đối chiếu mặc định cho Cơ sở dữ liệu không chứa chính xác cùng một ký tự, nhưng đã tìm thấy ánh xạ "phù hợp nhất", đó là lý do tại sao bạn nhận được 2thay vì? .

Bạn có thể thấy hiệu ứng này bằng cách thực hiện bài kiểm tra đơn giản sau:

SELECT '₂', N'₂';

Trả về:

2    ₂

Để rõ ràng, NẾU Trang Mã của Đối chiếu mặc định cho Cơ sở dữ liệu có chứa cùng một ký tự, thì nó sẽ được dịch sang cùng một ký tự trong Trang Mã đó. Và, sau đó, trong trường hợp của bạn, vì bạn đang lưu trữ vào một NVARCHARcột, nên nó sẽ được dịch lại, trở lại ký tự Unicode gốc. Ví dụ cuối cùng dưới đây cho thấy hành vi này.

QUAN TRỌNG: Xin lưu ý rằng việc chuyển đổi xảy ra khi chuỗi ký tự đang được diễn giải, đó là trước khi nó được lưu trữ vào cột. Điều này có nghĩa là ngay cả khi cột có thể giữ ký tự đó, thì nó cũng đã được chuyển đổi thành thứ khác, dựa trên Collation mặc định của Cơ sở dữ liệu, tất cả là do bỏ điN tiền tố trên chuỗi ký tự đó. Và đây chính xác là những gì bạn đang (hoặc đã) trải qua.

Ví dụ: nếu Collation mặc định của Cơ sở dữ liệu của bạn là một trong các Collation Hàn Quốc (một trong bốn Bộ ký tự Double-Byte), thì bạn sẽ không gặp vấn đề này vì ký tự "Đăng ký 2" có sẵn trong ký tự đó đặt (Mã trang 949). Hãy thử kiểm tra sau để xem (nó sử dụng Collation của cột thay vì Collation mặc định của Cơ sở dữ liệu vì dễ hiển thị hơn):

CREATE TABLE #TestChar
(
    [8bit_Latin1_General-1252] VARCHAR(2) COLLATE Latin1_General_100_CI_AS_SC,
    [8bit_Korean-949] VARCHAR(2) COLLATE Korean_100_CI_AS_SC,
    [UTF16LE_Latin1_General-1252] NVARCHAR(2) COLLATE Latin1_General_100_CI_AS_SC
);

INSERT INTO #TestChar VALUES (N'₂', N'₂', N'₂');

SELECT * FROM #TestChar;

Trả về:

8bit_Latin1_General-1252    8bit_Korean-949    UTF16LE_Latin1_General-1252
2                           ₂                  ₂

Như bạn có thể thấy, Bộ sưu tập Latin1_General, sử dụng Mã trang 1252 (cùng Trang mã mà Bộ Modern_Spanishsưu tập sử dụng) cho VARCHARdữ liệu, không có kết quả khớp chính xác, nhưng chúng có ánh xạ "phù hợp nhất" (đó là những gì bạn đang thấy ). NHƯNG, Bộ sưu tập Hàn Quốc, sử dụng VARCHARdữ liệu Trang 949 cho dữ liệu, có một kết quả khớp chính xác cho ký tự "Đăng ký 2".


Để minh họa thêm, chúng ta có thể tạo Cơ sở dữ liệu mới với Collation mặc định của một trong các Collations của Hàn Quốc, sau đó chạy SQL chính xác có trong câu hỏi:

CREATE DATABASE [TestKorean-949] COLLATE Korean_100_CI_AS_KS_WS_SC;
ALTER DATABASE [TestKorean-949] SET RECOVERY SIMPLE;
GO

USE [TestKorean-949];

CREATE TABLE test (
    id INT NOT NULL,
    description NVARCHAR(100) COLLATE Modern_Spanish_CI_AS NOT NULL
);
INSERT INTO test (id, description) VALUES (1, 'CO2');


SELECT * FROM test WHERE id = 1;
UPDATE test SET description = 'CO₂' WHERE id = 1;
SELECT * FROM test WHERE id = 1;

Trả về:

id  description
1   CO2


id  description
1   CO₂

CẬP NHẬT

Đối với bất kỳ ai quan tâm đến việc tìm hiểu thêm về chính xác những gì đang diễn ra ở đây (tức là tất cả các thông tin chi tiết), vui lòng xem cuộc điều tra hai phần tôi vừa đăng:

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.