Impossibile aggiornare "CO2" in "CO₂" nella riga della tabella


19

Data questa tabella:

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

Ho capito che non posso risolvere un problema tipografico:

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

perché l'aggiornamento corrisponde ma non ha alcun effetto:

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

(1 affected rows)

(1 affected rows)

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

(1 affected rows)

È come se SQL Server determinasse che, poiché è ovviamente solo un minuscolo 2 , il valore finale non cambierà, quindi non vale la pena cambiarlo.

Qualcuno potrebbe far luce su questo e forse suggerire una soluzione alternativa (oltre all'aggiornamento a un valore intermedio)?


1
Álvaro: se vuoi saperne di più su questo comportamento, per capire meglio perché questo stava accadendo, consulta i due link che ho appena aggiunto in fondo alla mia risposta.
Solomon Rutzky,

Risposte:


29

Il pedice 2 non fa parte del set di caratteri varchar (in qualsiasi confronto, non solo Modern_Spanish). Quindi rendilo una costante nvarchar:

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

1
Non solo ho fissato il valore, ma ho anche capito come è arrivato lì, in primo luogo. Grazie!
Álvaro González,

2
@ ÁlvaroGonzález e gbn: Giusto per essere chiari, "Sottoscrizione 2" non è disponibile nella pagina di codice specificata dalla raccolta predefinita del database in questione, che è la raccolta utilizzata per valori letterali e variabili di stringa, non la raccolta della colonna (sebbene entrambi potrebbe utilizzare la stessa pagina di codice). Tuttavia, "Sottoscrizione 2" è disponibile nel Codice Pagina 949 tramite le Raccolte coreane. Questo non aiuta qui, ma solo FYI. Ho dettagli e un esempio nella mia risposta .
Solomon Rutzky,

21

@gbn ha già spiegato il motivo e la correzione di base, ma il motivo specifico del comportamento che stai riscontrando è questo:

  1. Stai usando un valore VARCHARletterale (senza Nprefisso) anziché NVARCHARletterale (stringa con Nprefisso), quindi il carattere Unicode verrà convertito in VARCHAR.
  2. VARCHARè una codifica a 8 bit che è, nella maggior parte dei casi, un byte per carattere, ma può anche essere due byte per carattere. D'altra parte, NVARCHARè una codifica a 16 bit (UTF-16 Little Endian) che è di due byte o quattro byte per carattere.
  3. A causa della differenza nel numero di byte disponibili da utilizzare per la mappatura dei caratteri, le codifiche a 8 bit sono, per loro stessa natura, molto più limitate nel numero di caratteri che è possibile mappare. VARCHARi dati possono contenere fino a 256 caratteri per set di caratteri a byte singolo (la maggior parte di essi) e fino a 65.536 caratteri per set di caratteri a byte doppio (solo alcuni di questi). D'altra parte, i NVARCHARdati possono mappare poco più di 1,1 milioni di caratteri Unicode (sebbene poco meno di 250k attualmente mappati).
  4. A causa del numero limitato di mappature che possono essere eseguite con 8 bit / VARCHARdati, diversi raggruppamenti di caratteri (basati su Lingua / Cultura) sono distribuiti su più "Pagine di codice" (ovvero set di caratteri)
  5. Ogni fascicolo specifica quale codice pagina, se presente, utilizzare per i VARCHARdati ( NVARCHARsono tutti caratteri)
  6. Quando si converte una stringa letterale o variabile da NVARCHAR(ovvero Unicode / UTF-16 / tutti i caratteri) a VARCHAR(set di caratteri basato sulla pagina di codice specificata nella maggior parte delle regole di confronto), viene utilizzata la funzione di confronto predefinita del database
  7. Se la pagina di codice della collation utilizzata per la conversione non contiene lo stesso carattere, ma contiene un mapping "best fit", verrà utilizzato il mapping "best fit".
  8. Se la pagina di codice della collation utilizzata per la conversione non contiene lo stesso carattere o contiene una mappatura "best fit", verrà utilizzato il carattere "sostituzione" predefinito (più comunemente ?).

Quindi, ciò che state vedendo è una NVARCHARper VARCHARla conversione a causa della mancanza del Nprefisso su quello letterale stringa. Inoltre, la pagina di codice delle regole di confronto predefinite per il database non contiene esattamente lo stesso carattere, ma è stata trovata una mappatura "best fit", motivo per cui si ottiene un 2anziché un ?.

Puoi vedere questo effetto eseguendo il seguente semplice test:

SELECT '₂', N'₂';

Ritorna:

2    ₂

Per essere chiari, SE la pagina di codice della raccolta predefinita per il database contenesse lo stesso identico carattere, sarebbe stata tradotta nello stesso carattere in quella pagina di codice. E, quindi, nel tuo caso, dal momento che stai memorizzando in una NVARCHARcolonna, si sarebbe tradotto di nuovo, tornando al carattere Unicode originale. L'esempio finale di seguito mostra questo comportamento.

IMPORTANTE: tenere presente che la conversione avviene quando viene interpretato il valore letterale della stringa, ovvero prima che venga memorizzato nella colonna. Ciò significa che anche se la colonna può contenere quel carattere, sarà già stata convertita in qualcos'altro, in base alle regole di confronto predefinite del database, il tutto a causa dell'abbandono del Nprefisso su quella stringa letterale. E questo è esattamente ciò che stai (o stavi vivendo).

Ad esempio, se le regole di confronto predefinite del database sarebbero state una delle regole di confronto coreane (uno dei quattro set di caratteri a doppio byte), non si vedrebbe questo problema poiché il carattere "Sottoscrizione 2" è disponibile in quel personaggio set (Codice Pagina 949). Prova a vedere il seguente test (utilizza le regole di confronto della colonna anziché le regole di confronto predefinite del database in quanto è più facile da mostrare):

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;

Ritorna:

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

Come puoi vedere, le Latin1_General Collation, che usano Code Page 1252 (stessa code page utilizzata dalle Modern_SpanishCollation) per i VARCHARdati, non hanno una corrispondenza esatta, ma hanno una mappatura "best fit" (che è ciò che stai vedendo ). MA, le regole di confronto coreane, che usano il codice pagina 949 per i VARCHARdati, hanno una corrispondenza esatta per il carattere "Sottoscrizione 2".


Per illustrare ulteriormente, possiamo creare un nuovo database con una raccolta predefinita di una delle regole di confronto coreane e quindi eseguire l'SQL esatto che si trova nella domanda:

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;

Ritorna:

id  description
1   CO2


id  description
1   CO₂

AGGIORNARE

Per chiunque sia interessato a scoprire di più su cosa sta succedendo esattamente qui (cioè tutti i dettagli cruenti), si prega di consultare l'indagine in due parti che ho appena pubblicato:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.