ไม่สามารถอัปเดต“ CO2” เป็น“ CO₂” ในแถวตาราง


19

รับตารางนี้:

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

(1 affected rows)

(1 affected rows)

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

(1 affected rows)

มันเหมือนกับว่า SQL Server กำหนดว่าเนื่องจากเป็นเพียง2 ตัวเล็ก ๆค่าสุดท้ายจะไม่เปลี่ยนแปลงดังนั้นจึงไม่คุ้มค่าที่จะเปลี่ยน

มีใครบางคนให้ความกระจ่างเกี่ยวกับเรื่องนี้และอาจแนะนำวิธีแก้ปัญหา (นอกเหนือจากการอัปเดตเป็นค่าตัวกลาง)


1
Álvaro: หากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับพฤติกรรมนี้เพื่อให้เข้าใจได้ดียิ่งขึ้นว่าเหตุใดจึงเป็นเช่นนี้โปรดดูลิงก์ทั้งสองที่ฉันเพิ่งเพิ่มเข้าไปที่ด้านล่างของคำตอบของฉัน
โซโลมอน Rutzky

คำตอบ:


29

ตัวห้อย 2 ไม่ได้เป็นส่วนหนึ่งของชุดอักขระ varchar (ในการจัดเรียงใด ๆ ไม่ใช่แค่ Modern_Spanish) ดังนั้นให้เป็นค่าคงที่ nvarchar:

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

1
ไม่เพียง แต่ฉันจะแก้ไขค่าฉันยังเข้าใจว่ามันมาถึงที่นั่นได้อย่างไรในตอนแรก ขอขอบคุณ!
ÁlvaroGonzález

2
@ ÁlvaroGonzálezและ gbn: เพื่อให้ชัดเจน "Subscript 2" ไม่สามารถใช้ได้ในหน้ารหัสที่ระบุโดยการเรียงหน้าเริ่มต้นของการเปรียบเทียบฐานข้อมูลที่เป็นปัญหาซึ่งเป็น Collation ที่ใช้สำหรับตัวอักษรและตัวแปรสตริงไม่ใช่การเรียงคอลัมน์ อาจใช้หน้ารหัสเดียวกัน) อย่างไรก็ตาม "Subscript 2" มีให้ใน Code Page 949 ผ่านทาง Collation เกาหลี นั่นไม่ได้ช่วยที่นี่ แต่เพียงแค่ FYI ฉันมีรายละเอียดและตัวอย่างในของฉันคำตอบ
โซโลมอน Rutzky

21

@gbn ได้อธิบายเหตุผลพื้นฐานและการแก้ไขแล้ว แต่เหตุผลเฉพาะสำหรับพฤติกรรมที่คุณเห็นคือ:

  1. คุณกำลังใช้VARCHARตัวอักษร (ไม่มีNคำนำหน้า) แทนที่จะเป็นNVARCHARตัวอักษร (สตริงที่มีNคำนำหน้า) จึงอักขระ Unicode VARCHARจะได้รับการแปลงเป็น
  2. VARCHARเป็นการเข้ารหัสแบบ 8 บิตที่โดยส่วนใหญ่แล้วหนึ่งไบต์ต่ออักขระ แต่สามารถมีได้สองไบต์ต่ออักขระ ในอีกทางหนึ่งNVARCHARคือการเข้ารหัสแบบ 16 บิต (UTF-16 Little Endian) ที่มีสองไบต์หรือสี่ไบต์ต่ออักขระ
  3. เนื่องจากความแตกต่างของจำนวนไบต์ที่มีให้ใช้สำหรับการแมปอักขระการเข้ารหัส 8 บิตนั้นโดยทั่วไปแล้วจะมีข้อ จำกัด มากขึ้นในจำนวนอักขระที่สามารถแมปได้ VARCHARข้อมูลมีความยาวสูงสุด 256 อักขระสำหรับชุดอักขระไบต์เดียว (ส่วนใหญ่) และสูงสุด 65,536 อักขระสำหรับชุดอักขระไบต์คู่ (เพียงไม่กี่ตัวเท่านั้น) ในทางกลับกันNVARCHARข้อมูลสามารถแมป Unicode ได้มากกว่า 1.1 ล้านตัวอักษร (แม้ว่าจะแมปน้อยกว่า 250k ในปัจจุบัน)
  4. เนื่องจากการแมปมีจำนวน จำกัด ที่สามารถทำได้กับ 8 บิต / VARCHARข้อมูลการจัดกลุ่มอักขระต่าง ๆ (ตามภาษา / วัฒนธรรม) จึงกระจายออกไปทั่ว "หน้ารหัส" (เช่นชุดอักขระ)
  5. แต่ละการตรวจทานระบุว่าจะใช้หน้ารหัสใดสำหรับVARCHARข้อมูล ( NVARCHARเป็นอักขระทั้งหมด)
  6. เมื่อแปลงสตริงตามตัวอักษรหรือตัวแปรจากNVARCHAR(เช่น Unicode / UTF-16 / อักขระทั้งหมด) เป็นVARCHAR(ชุดอักขระตามโค้ดเพจซึ่งระบุไว้ใน Collation ส่วนใหญ่) จะใช้ Collation เริ่มต้นของฐานข้อมูล
  7. หากหน้ารหัสของการจัดเรียงที่ใช้สำหรับการแปลงไม่มีอักขระเดียวกัน แต่มีการแมป "ที่เหมาะสมที่สุด" การแมป "ที่เหมาะสมที่สุด" จะถูกใช้
  8. หากหน้ารหัสของการจัดเรียงที่ใช้สำหรับการแปลงไม่มีอักขระเดียวกันหรือมีการแมป "ที่เหมาะสมที่สุด" ระบบจะใช้อักขระ "การแทนที่" เริ่มต้น (โดยทั่วไป?)

ดังนั้นสิ่งที่คุณจะเห็นเป็น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

สำหรับทุกคนที่มีความสนใจในการหาข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่ว่าเกิดขึ้นที่นี่ (เช่นรายละเอียดทั้งหมดเต็มไปด้วยเลือด), การโปรดดูการสอบสวนสองส่วนผมเพิ่งโพสต์:

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.