web-dev-qa-db-fra.com

Division de valeurs délimitées dans une colonne SQL en plusieurs lignes

Je voudrais vraiment avoir quelques conseils ici, pour donner quelques informations générales, je travaille à l’insertion de journaux de suivi des messages d’Exchange 2007 dans SQL. Comme nous avons des millions et des millions de lignes par jour, j'utilise une instruction Bulk Insert pour insérer les données dans une table SQL.

En fait, j’ai inséré une insertion en bloc dans une table temporaire puis, à partir de là, je fusionnai les données dans la table active. C’est pour des problèmes d’analyse de test car certains champs ont des guillemets et autres autour des valeurs.

Cela fonctionne bien, à l'exception du fait que la colonne adresse-destinataire est un champ délimité par un; caractère, et il peut être incroyablement long parfois, car il peut y avoir de nombreux destinataires de courrier électronique.

Je voudrais prendre cette colonne et diviser les valeurs en plusieurs lignes qui seraient ensuite insérées dans une autre table. Le problème est que tout ce que j'essaie prend trop de temps ou ne fonctionne pas comme je le souhaite.

Prenons cet exemple de données:

message-id                                              recipient-address
[email protected]   [email protected]
[email protected]     [email protected]
[email protected]              [email protected];[email protected];[email protected]

Je voudrais que cela soit formaté comme suit dans mon tableau Destinataires:

message-id                                              recipient-address
[email protected]   [email protected]
[email protected]     [email protected]
[email protected]              [email protected]
[email protected]              [email protected]
[email protected]              [email protected]

Quelqu'un a-t-il une idée de la façon dont je peux y arriver?

Je connais assez bien PowerShell, alors j’ai essayé, mais une boucle foreach, même sur des disques 28K, a pris du temps à traiter, j’ai besoin de quelque chose qui fonctionnera aussi rapidement et efficacement que possible.

Merci!

13
HungryHippos

Commencez par créer une fonction divisée:

CREATE FUNCTION dbo.SplitStrings
(
    @List       NVARCHAR(MAX),
    @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
AS
    RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number),
        Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@List, Number, 
        CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)))
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id])
        FROM sys.all_objects AS s1 CROSS APPLY sys.all_objects) AS n(Number)
    WHERE Number <= CONVERT(INT, LEN(@List))
        AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
    ) AS y);
GO

Vous pouvez maintenant extrapoler simplement en:

SELECT s.[message-id], f.Item
  FROM dbo.SourceData AS s
  CROSS APPLY dbo.SplitStrings(s.[recipient-address], ';') as f;

Aussi, je suggère de ne pas mettre de tirets dans les noms de colonnes. Cela signifie que vous devez toujours les mettre dans [square brackets].

45
Aaron Bertrand

SQL Server 2016 inclut une nouvelle fonction de table, string_split (), similaire à la solution précédente. 

La seule exigence est de définir le niveau de compatibilité sur 130 (SQL Server 2016).

3
Oscar Perez

Vous pouvez utiliser CROSS APPLY (disponible dans SQL Server 2005 et versions ultérieures) et STRING_SPLIT fonction (disponible dans SQL Server 2016 et versions ultérieures):

DECLARE @delimiter nvarchar(255) = ';';

-- create tables
CREATE TABLE MessageRecipients (MessageId int, Recipients nvarchar(max));
CREATE TABLE MessageRecipient (MessageId int, Recipient nvarchar(max));

-- insert data
INSERT INTO MessageRecipients VALUES (1, '[email protected]; [email protected]; [email protected]');
INSERT INTO MessageRecipients VALUES (2, '[email protected]; [email protected]');

-- insert into MessageRecipient
INSERT INTO MessageRecipient
SELECT MessageId, ltrim(rtrim(value))
FROM MessageRecipients 
CROSS APPLY STRING_SPLIT(Recipients, @delimiter)

-- output results
SELECT * FROM MessageRecipients;
SELECT * FROM MessageRecipient;

-- delete tables
DROP TABLE MessageRecipients;
DROP TABLE MessageRecipient;

Résultats:

MessageId   Recipients
----------- ----------------------------------------------------
1           [email protected]; [email protected]; [email protected]
2           [email protected]; [email protected]

et

MessageId   Recipient
----------- ----------------
1           [email protected]
1           [email protected]
1           [email protected]
2           [email protected]
2           [email protected]
1
Alex Vazhev