5
votes

Convertir les données varbinary (max) encodées en utf-8 en chaîne nvarchar (max)

Existe-t-il un moyen simple de convertir une colonne varbinary (max) encodée en utf-8 en varchar (max) dans T-SQL. Quelque chose comme CONVERT (varchar (max), [MyDataColumn]) . Le mieux serait une solution qui ne nécessite pas de fonctions personnalisées. Actuellement, je convertis les données côté client, mais cela présente l'inconvénient que le filtrage et le tri corrects ne sont pas aussi efficaces que ceux effectués côté serveur.


9 commentaires

N'utilisez pas un champ binaire pour stocker du texte. Essayer de convertir UTF8 en page de code ASCII / à un octet (c'est ce qu'est varchar) échouera toujours à moins que la chaîne ne soit déjà dans la page de code US-ASCII


Je crois que vous avez une valeur varchar stockée en tant que varbinary? Sinon, le résultat du varchar sera Gibberish.


La solution la plus simple consiste à stocker du texte Unicode dans des types Unicode. nvarchar (...) pour du texte simple, nvarchar (max) pour les CLOB. Aucune fonction ou conversion nécessaire, aucune chance d'erreurs de conversion de page de codes. Vos bibliothèques de code client ou vos pilotes pourront simplement stocker le texte Unicode sans complications


Quelle version de SQL Server utilisez-vous? Étant donné que la prise en charge de l'UTF-8 n'a été ajoutée que dans la version 2019, il est fort probable que vous exécutiez une version de SQL Server qui ne sait rien sur UTF-8 .


@Damien_The_Unbeliever sera ajouté . 2019 n'est pas encore sorti et les aperçus actuels ont toujours un support UTF8 incomplet. Je me demande pourquoi l'OP veut UTF8. Une tentative de migration d'une autre base de données qui n'a pas nvarchar ? Ou juste une supposition qu'Unicode signifie UTF8?


@sschoenb pourquoi voulez-vous quand même stocker du texte Unicode en tant que binaire? Cela n'offre aucun avantage tout en introduisant beaucoup de problèmes. Quel problème essayez-vous de résoudre?


ce n'était pas mon choix de stocker le contenu UTF-8 en tant que varbinary. Un tiers stocke les données JSON en tant que utf-8 varbinary


@sschoenb c'est plutôt ... malheureux, car cela vous empêche également d'utiliser les fonctions JSON de SQL Server. Les bases de données Big 3 (DB2, Oracle, SQL Server) ont toujours pris en charge nchar / nvarchar . Ce qui suggère que le tiers a commencé à partir de MySQL / PostgreSQL à la place.


@sschoenb ce que vous pouvez faire est de créer un autre champ nvarchar qui est mis à jour, par exemple par un déclencheur, pour contenir le texte UTF16. Cela vous permettra d'appliquer des fonctions JSON, de l'indexer, de spécifier des classements, etc. Vous pouvez utiliser la compression transparente sur la table (disponible même dans SQL Server Express depuis 2016SP1) pour réduire l'espace et améliorer les performances en réduisant les E / S


3 Réponses :


6
votes

SQL-Server ne connaît pas UTF-8 (au moins toutes les versions que vous pouvez utiliser de manière productive). Il existe prise en charge limitée à partir de la version v2014 SP2 (et quelques détails sur les versions prises en charge ) lors de la lecture d'un fichier encodé en utf-8 à partir d'un disque via BCP (idem pour l'écriture de contenu sur le disque).

Important à savoir:

VARCHAR (x) n'est pas utf-8 . Il est codé sur 1 octet ASCII étendu, utilisant une page de codes (vivant dans le classement) comme table de caractères.

NVARCHAR (x) n'est pas utf-16 (mais très proche, c'est ucs-2 ) . Il s'agit d'une chaîne codée sur 2 octets couvrant presque tous les caractères connus (mais des exceptions existent).

utf-8 utilisera 1 octet pour les caractères latin simples , mais 2 octets ou même plus pour les jeux de caractères étrangers encodés.

Un VARBINAIRE (x) contiendra le utf-8 comme une chaîne d'octets sans signification.

Un simple CAST ou CONVERT ne fonctionnera pas: VARCHAR prendra chaque octet comme un caractère. Ce n'est certainement pas le résultat auquel vous vous attendez. NVARCHAR prendrait chaque bloc de 2 octets comme un caractère. Encore une fois, ce n'est pas ce dont vous avez besoin.

Vous pouvez essayer de l'écrire dans un fichier et de le relire avec BCP (v2014 SP2 ou supérieur). Mais la meilleure chance que je vois pour vous est une Fonction CLR .


3 commentaires

nvarchar est UTF16 depuis SQL Server 2012 tant qu'un Caractères supplémentaires, le classement _SC est utilisé


@PanagiotisKanavos, ouais ... J'ai déjà trébuché sur ça. Mais je suppose que personne n'utilise ces classements _SC , du moins pas par défaut. Changer le classement d'une base de données est une action vraiment difficile. Écrire ces classements dans chacune des chaînes dont vous avez besoin est un processus très laid et chronophage ... De plus, je pense que les documents ne sont pas absolument précis, en parlant de NVARCHAR comme d'un équivalent unicode / utf-16 . Eh bien, dans 99,9% oui, mais aucune chaîne UTF-16 ne fonctionnerait avec un cast en VARBINARY et retour en NVARCHAR ...


Probablement parce que nous n'avons rencontré aucun problème, même avec les emojis. Ils ne sont peut-être pas dans UCS-2, mais comme l'explique la documentation, ils ne seront pas mutilés.



0
votes

vous pouvez utiliser ce qui suit pour publier une chaîne dans le champ varbinary

public string ReadCString(byte[] cString)
        {
            var nullIndex = Array.IndexOf(cString, (byte)0);
            nullIndex = (nullIndex == -1) ? cString.Length : nullIndex;
            return System.Text.Encoding.Unicode.GetString(cString);
        }

puis utiliser ce qui suit pour récupérer les données sous forme de chaîne

Encoding.Unicode.GetBytes(Item.VALUE)


0 commentaires

0
votes

Astuce pour modifier la table

J'essayais de proposer une approche utilisant le classement Utf8 de SQL Server 2019 et j'ai trouvé jusqu'à présent une méthode possible, qui devrait être plus rapide que l'astuce XML (voir ci-dessous). p >

  1. Créez une table temporaire avec une colonne varbinary.
  2. Insérer des valeurs varbinary dans le tableau
  3. Modifier la table modifier la colonne en varchar avec le classement utf8

(cette parenthèse sert à séparer la liste numérotée du code formaté)

DECLARE @LongText NVARCHAR(MAX) = N'...<s>o</s>o&amp;me lo>>>ng u>n<<i< &&code text...';
DECLARE @StartXML DATETIME2(7), @EndXML DATETIME2(7), @StartTable DATETIME2(7), @EndTable DATETIME2(7);

drop table if exists
  #longTexts,
  #longBinaries,
  #XMLConverts;
create table #longTexts (LongText VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);
create table #longBinaries (LongBinary VARBINARY(MAX) NOT NULL);
CREATE TABLE #XMLConverts (LongText VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8 NOT NULL);

insert into #longTexts --make the long text longer
  (LongText)
select
  REPLICATE(@LongText, 100000)
from 
  Data0001.dbo.Numbers --use while if you don't have number table
WHERE
  Number BETWEEN 1 AND 100; --make more of them

insert into #longBinaries (LongBinary) select CAST(LongText AS varbinary(max)) from #longTexts;

SET @StartXML = SYSDATETIME();
------------------------------
--MEASURE XML--
INSERT INTO #XMLConverts 
  (
    LongText
  )
SELECT
  CAST(
    '<?xml version=''1.0'' encoding=''utf-8''?>' +
    REPLACE(
      REPLACE(
        LB.LongBinary,
        '&',
        '&amp;'
      ),
      '<',
      '&lt;'
    ) AS XML
  ).value('.', 'varchar(max)')
FROM
  #longBinaries AS LB;
SET @EndXML = SYSDATETIME();

SET @StartTable = SYSDATETIME();
--------------------------------------------
--MEASURE ALTER TABLE--
DROP TABLE IF EXISTS #AlterConverts;
CREATE TABLE #AlterConverts (UTF8 VARBINARY(MAX));

INSERT INTO #AlterConverts 
  (
    UTF8
  )
SELECT
  LB.LongBinary
FROM
  #longBinaries AS LB;

ALTER TABLE #AlterConverts ALTER COLUMN UTF8 VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8;
--ALTER TABLE #AlterConverts ALTER COLUMN UTF8 NVARCHAR(MAX);

SET @EndTable = SYSDATETIME();

SELECT
  DATEDIFF(MILLISECOND, @StartXML, @EndXML) AS XML_MS,
  DATEDIFF(MILLISECOND, @StartTable, @EndTable) AS ALTER_MS;

Astuce XML

La solution suivante devrait fonctionner pour tout encodage.

Il existe une manière délicate de faire exactement ce que l'OP demande. Edit: J'ai trouvé la même méthode discutée sur SO ( SQL - UTF-8 à varchar / nvarchar Encoding issue )

Le processus se déroule comme suit:

SELECT
  CAST(
    '<?xml version=''1.0'' encoding=''utf-8''?>' +
    REPLACE(
      REPLACE(
        @BinaryValue,
        '&',
        '&amp;'
      ),
      '<',
      '&lt;'
    ) AS XML
  ).value('.', 'varchar(max)')

Pourquoi cela fonctionne: varbinary et varchar sont la même chaîne de bits - seule l'interprétation diffère, donc le xml résultant est vraiment un train de bits encodé en utf8 et l'interpréteur xml est alors capable de reconstruire les caractères encodés en utf8 corrects.

Différence de vitesse

La vitesse la différence varie considérablement en fonction des données. Lorsqu'il y a beaucoup à remplacer lors de l'utilisation de astuce XML , l ' astuce ALTER peut être beaucoup, beaucoup plus rapide. Lorsqu'il y a peu de textes ou si les textes sont courts, la surcharge liée à la création et à la modification d'une table temporaire entraînera une surcharge importante. Donc: "Cela dépend ..."

drop table if exists
  #bin,
  #utf8;

create table #utf8 (UTF8 VARCHAR(MAX) COLLATE Czech_100_CI_AI_SC_UTF8);
create table #bin (BIN VARBINARY(MAX));

insert into #utf8 (UTF8) values ('Žluťoučký kůň říčně pěl ďábelské ódy za svitu měsíce.');
insert into #bin (BIN) select CAST(UTF8 AS varbinary(max)) from #utf8;

select * from #utf8; --here you can see the utf8 string is stored correctly and that
select BIN, CAST(BIN AS VARCHAR(MAX)) from #bin; --utf8 binary is converted into gibberish

alter table #bin alter column BIN varchar(max) collate Czech_100_CI_AI_SC_UTF8;
select * from #bin; --voialá, correctly converted varchar

alter table #bin alter column BIN nvarchar(max);
select * from #bin; --finally, correctly converted nvarchar 

0 commentaires