@gbn ได้อธิบายเหตุผลพื้นฐานและการแก้ไขแล้ว แต่เหตุผลเฉพาะสำหรับพฤติกรรมที่คุณเห็นคือ:
- คุณกำลังใช้
VARCHAR
ตัวอักษร (ไม่มีN
คำนำหน้า) แทนที่จะเป็นNVARCHAR
ตัวอักษร (สตริงที่มีN
คำนำหน้า) จึงอักขระ Unicode VARCHAR
จะได้รับการแปลงเป็น
VARCHAR
เป็นการเข้ารหัสแบบ 8 บิตที่โดยส่วนใหญ่แล้วหนึ่งไบต์ต่ออักขระ แต่สามารถมีได้สองไบต์ต่ออักขระ ในอีกทางหนึ่งNVARCHAR
คือการเข้ารหัสแบบ 16 บิต (UTF-16 Little Endian) ที่มีสองไบต์หรือสี่ไบต์ต่ออักขระ
- เนื่องจากความแตกต่างของจำนวนไบต์ที่มีให้ใช้สำหรับการแมปอักขระการเข้ารหัส 8 บิตนั้นโดยทั่วไปแล้วจะมีข้อ จำกัด มากขึ้นในจำนวนอักขระที่สามารถแมปได้
VARCHAR
ข้อมูลมีความยาวสูงสุด 256 อักขระสำหรับชุดอักขระไบต์เดียว (ส่วนใหญ่) และสูงสุด 65,536 อักขระสำหรับชุดอักขระไบต์คู่ (เพียงไม่กี่ตัวเท่านั้น) ในทางกลับกันNVARCHAR
ข้อมูลสามารถแมป Unicode ได้มากกว่า 1.1 ล้านตัวอักษร (แม้ว่าจะแมปน้อยกว่า 250k ในปัจจุบัน)
- เนื่องจากการแมปมีจำนวน จำกัด ที่สามารถทำได้กับ 8 บิต /
VARCHAR
ข้อมูลการจัดกลุ่มอักขระต่าง ๆ (ตามภาษา / วัฒนธรรม) จึงกระจายออกไปทั่ว "หน้ารหัส" (เช่นชุดอักขระ)
- แต่ละการตรวจทานระบุว่าจะใช้หน้ารหัสใดสำหรับ
VARCHAR
ข้อมูล ( NVARCHAR
เป็นอักขระทั้งหมด)
- เมื่อแปลงสตริงตามตัวอักษรหรือตัวแปรจาก
NVARCHAR
(เช่น Unicode / UTF-16 / อักขระทั้งหมด) เป็นVARCHAR
(ชุดอักขระตามโค้ดเพจซึ่งระบุไว้ใน Collation ส่วนใหญ่) จะใช้ Collation เริ่มต้นของฐานข้อมูล
- หากหน้ารหัสของการจัดเรียงที่ใช้สำหรับการแปลงไม่มีอักขระเดียวกัน แต่มีการแมป "ที่เหมาะสมที่สุด" การแมป "ที่เหมาะสมที่สุด" จะถูกใช้
- หากหน้ารหัสของการจัดเรียงที่ใช้สำหรับการแปลงไม่มีอักขระเดียวกันหรือมีการแมป "ที่เหมาะสมที่สุด" ระบบจะใช้อักขระ "การแทนที่" เริ่มต้น (โดยทั่วไป
?
)
ดังนั้นสิ่งที่คุณจะเห็นเป็นNVARCHAR
ที่จะVARCHAR
แปลงเนื่องจากการขาดหายไปN
คำนำหน้าบนตัวอักษรสตริง และหน้ารหัสของการเริ่มต้นการเปรียบเทียบสำหรับฐานข้อมูลไม่ได้มีตัวอักษรเดียวกันแน่นอน แต่ "แบบที่ดีที่สุด" การทำแผนที่พบซึ่งเป็นเหตุผลที่คุณจะได้รับแทน2
?
คุณสามารถเห็นผลกระทบนี้ได้โดยทำแบบทดสอบง่ายๆดังต่อไปนี้:
SELECT '₂', N'₂';
ผลตอบแทน:
2 ₂
เพื่อความชัดเจนหากรหัสหน้าของการเรียงหน้าเริ่มต้นสำหรับฐานข้อมูลมีอักขระตัวเดียวกันแน่นอนจากนั้นจะแปลเป็นอักขระตัวเดียวกันในหน้ารหัสนั้น และในกรณีของคุณเนื่องจากคุณเก็บไว้ในNVARCHAR
คอลัมน์มันจะแปลอีกครั้งกลับไปที่อักขระ Unicode ดั้งเดิม ตัวอย่างสุดท้ายด้านล่างแสดงพฤติกรรมนี้
สำคัญ:โปรดทราบว่าการแปลงจะเกิดขึ้นเมื่อมีการตีความตัวอักษรของสตริงซึ่งเป็นก่อนที่จะถูกเก็บไว้ในคอลัมน์ ซึ่งหมายความว่าแม้ว่าคอลัมน์สามารถเก็บอักขระนั้นได้ แต่ก็จะถูกแปลงเป็นอย่างอื่นตามการจัดเรียงเริ่มต้นของฐานข้อมูลซึ่งทั้งหมดนี้เกิดจากการไม่N
ใส่คำนำหน้าบนตัวอักษรของสตริงนั้น และนี่คือสิ่งที่คุณกำลังประสบ (หรือเคย)
ตัวอย่างเช่นหากการเรียงหน้าเริ่มต้นของฐานข้อมูลของคุณจะเป็นหนึ่งในการเปรียบเทียบภาษาเกาหลี (หนึ่งในสี่ชุดอักขระแบบสองไบต์) คุณจะไม่ได้เห็นปัญหานี้เนื่องจากอักขระ "ตัวห้อย 2" มีอยู่ในตัวละครนั้น ชุด (รหัสหน้า 949) ลองทดสอบต่อไปนี้เพื่อดู (ใช้การเรียงหน้าของคอลัมน์แทนการเรียงหน้าเริ่มต้นของฐานข้อมูลตามที่แสดงได้ง่ายกว่า):
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;
ผลตอบแทน:
8bit_Latin1_General-1252 8bit_Korean-949 UTF16LE_Latin1_General-1252
2 ₂ ₂
ดังที่คุณเห็น Latin1_General Collations ซึ่งใช้รหัสหน้า 1252 (หน้ารหัสเดียวกันกับที่Modern_Spanish
ใช้การจัดเรียง) สำหรับVARCHAR
ข้อมูลไม่มีการจับคู่ที่ตรงกัน แต่มีการแมป "เหมาะสมที่สุด" (ซึ่งเป็นสิ่งที่คุณเห็น ) แต่การเปรียบเทียบภาษาเกาหลีซึ่งใช้รหัสหน้า 949 สำหรับVARCHAR
ข้อมูลจะมีการจับคู่ที่ตรงกันสำหรับอักขระ "Subscript 2"
เพื่อแสดงให้เห็นเพิ่มเติมเราสามารถสร้างฐานข้อมูลใหม่ด้วยการเรียงหน้าเริ่มต้นของหนึ่งในการเปรียบเทียบภาษาเกาหลีและจากนั้นเรียกใช้ SQL ที่แน่นอนที่อยู่ในคำถาม:
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;
ผลตอบแทน:
id description
1 CO2
id description
1 CO₂
UPDATE
สำหรับทุกคนที่มีความสนใจในการหาข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่ว่าเกิดขึ้นที่นี่ (เช่นรายละเอียดทั้งหมดเต็มไปด้วยเลือด), การโปรดดูการสอบสวนสองส่วนผมเพิ่งโพสต์: