J'accède et crée des rapports d'un fournisseur via une base de données SQL Server répliquée. Ils ont fait des choses absolument folles pour lesquelles j'ai essayé de résoudre, mais celui-ci prend le gâteau.
Ils ont une table qui a de nombreuses colonnes standard. Mais ce tableau a également une colonne appelée "Données". La colonne est un type de données "texte" hérité, et elle contient une liste géante (des centaines) de paires clé/valeur. Chaque paire est séparée par un CRLF, et la clé et la valeur sont séparées par un signe égal. Exemple:
select myTable.[data] from myTable where tblKey = 123
Résultat:
Key 1=Value 1
Key 2=Value 2
Key 3=Value 3
...
Key 500=Value 500
J'essaie de déterminer le moyen le plus efficace de décomposer cette colonne en un tableau de données utilisable. L'objectif final serait de pouvoir interroger la table d'une manière qui renvoie la clé de la table avec les clés/valeurs spécifiées en tant que colonnes/champs en tant que tels:
tblKey | [Key 1] | [Key 3] | [Key 243]
-------|---------|---------|-----------
123 Value 1 Value 3 Value 243
124 Value 1 Value 3 Value 243
125 Value 1 Value 3 Value 243
Existe-t-il un moyen de mouler cette colonne en vue? Je ne peux pas imaginer qu'une fonction serait particulièrement efficace, mais je suis sûr que je pourrais analyser les choses de cette façon en utilisant un string_split ou quelque chose de ce genre. Quelqu'un a-t-il déjà rencontré ce type d'atrocité et trouvé un bon moyen de le transformer en données utilisables?
Modifiez pour ajouter dbfiddle exemple de données.
Les données sont répliquées à partir de la source d'un fournisseur, donc je ne peux pas créer de nouvelles tables. Je peux créer des vues, des procédures et des fonctions. C'est ce que je cherche des conseils pour une façon décente d'accomplir.
[~ # ~] mise à jour [~ # ~]
Si, comme vous l'avez publié dans votre propre réponse, vous pouvez utiliser une FDU pour obtenir des valeurs de clé spécifiques, laissez-moi suggérer celle-ci: (Vous n'avez pas besoin de diviser toutes les clés/valeurs et vous n'avez pas besoin de relire le tableau , vous pouvez l'obtenir en utilisant les fonctions de texte.)
CREATE FUNCTION fnGetKey(@Data text, @Key varchar(20))
RETURNS varchar(100)
AS
BEGIN
RETURN
(
SELECT
SUBSTRING (
@Data,
/* Position of first '=' after key + 1 */
CHARINDEX('=', @Data, PATINDEX('%' + @key + '%', @Data)) + 1,
/* Lenght, Position of first chr(13) after key less previuos value - 1 */
(CHARINDEX(CHAR(13), @Data, PATINDEX('%' + @key + '%', @Data))
-
CHARINDEX('=', @Data, PATINDEX('%' + @key + '%', @Data))) - 1
)
)
END
SELECT
FruitID, Name, Description,
dbo.fnGetKey([Data], 'key 2') as [key 2],
dbo.fnGetKey([Data], 'key 4') as [key 4]
FROM
[Fruit];
FruitID | Nom | Description | touche 2 | touche 4 ------: | : -- : ---------- | : ------ | : ------ 1 | Banane | Délicieux | valeur 2 | valeur 4 2 | Poire | Rotton | valeur 2 | valeur 4 3 | Kiwi | D'accord | valeur 2 | valeur 4
db <> violon --- (ici
Réponse originale
La seule solution que je peux comprendre est de diviser les clés/valeurs, puis de les faire pivoter pour obtenir le résultat souhaité.
Malheureusement, il y a quelques inconvénients:
text
. Par conséquent, vous devez le convertir en varchar
avant de pouvoir le manipuler.nchar(1)
ou nvarchar(1)
, ergo vous devez remplacer CHAR(3)+CHAR(10)
par un seul caractère.Value
en un certain type de données numériques.Voici ce que j'ai en utilisant vos exemples de données:
WITH KP AS
(
SELECT FruitID, Name, Description, value as KPair
FROM Fruit
CROSS APPLY STRING_SPLIT(REPLACE(CAST(Data AS varchar(max)), CHAR(13)+CHAR(10), ','), ',') /* STRING_SPLIT only allows nchar(1), varchar(1) */
)
, KP1 AS
(
SELECT
FruitID,
SUBSTRING(KPair, 5, CHARINDEX('=', KPair) - 5) AS [Key],
SUBSTRING(KPair, CHARINDEX('=', KPair) + 7, LEN(KPair) - CHARINDEX('=', KPair) - 6) AS [Value]
FROM
KP
)
SELECT [FruitID], [1],[2],[3],[4],[5]
FROM KP1
PIVOT (MAX([Value]) FOR [Key] IN ([1],[2],[3],[4],[5])) AS PVT;
Premier CTE divisé tous les Key X=Value Y
. Le second coupe cette valeur pour obtenir chaque [Clé] et [Valeur]. Et le PIVOT final compose le résultat final en colonnes.
FruitID | 1 | 2 | 3 | 4 | 5 ------: | : : - | : - | : - | : - 1 | 1 | 2 | 3 | 4 | 5 2 | 1 | 2 | 3 | 4 | 5 3 | 1 | 2 | 3 | 4 | 5
db <> violon --- (ici
REMARQUE: je ne sais pas si je dois conserver [Clé 1] & [Valeur 1] ou il doit être converti en une colonne nommée [Clé] & [Valeur].
Une approche différente
Lorsque je travaille avec des bases de données tierces, j'ajoute généralement une nouvelle base de données, sur le même serveur/instance si possible, puis je l'utilise à mes propres fins, juste pour éviter les conflits avec les propriétaires de bases de données.
Dans ce cas, vous pouvez ajouter une nouvelle table et lancer périodiquement un processus pour la mettre à jour avec les nouvelles valeurs.
Vous pouvez utiliser un tableau avec toutes les colonnes:
CREATE TABLE [FruitKeys]
(
[FruitID] int NOT NULL PRIMARY KEY,
[V1] int NULL,
[V2] int NULL,
[V3] int NULL,
[V4] int NULL,
[V5] int NULL
);
ou un tableau avec des paires clé/valeur et utilisez un pivot pour obtenir le résultat final:
CREATE TABLE [FruitKeys]
(
[FruitID] int NOT NULL,
[Key] int NOT NULL,
[Value] int NOT NULL,
CONSTRAINT [PK_FruitKeys] PRIMARY KEY ([FruitID], [Key])
);
Il me semble que les données source ne sont pas si éloignées du format JSON
.
Vous pouvez le convertir assez directement puis utiliser OPENJSON
pour produire une sortie relationnelle:
SELECT
F.FruitID,
F.[Name],
OJ.[key 1],
OJ.[key 2],
OJ.[key 3],
OJ.[key 4],
OJ.[key 5],
F.[Description]
FROM dbo.Fruit AS F
CROSS APPLY OPENJSON
(
-- Convert source data to JSON format
'{' +
CHAR(34) +
REPLACE
(
REPLACE
(
CONVERT(varchar(max), F.Data),
'=', CHAR(34) + ':' + CHAR(34)
),
CHAR(13) + CHAR(10),
CHAR(34) + ',' + CHAR(34)
) +
CHAR(34) +
'}'
)
WITH
(
[key 1] varchar(100),
[key 2] varchar(100),
[key 3] varchar(100),
[key 4] varchar(100),
[key 5] varchar(100)
) AS OJ;
Production:
FruitID | Nom | touche 1 | touche 2 | touche 3 | touche 4 | touche 5 | Description ------: | : -- : ------ | : ------ | : ------ | : ------ | : ------ | : ---------- 1 | Banane | valeur 1 | valeur 2 | valeur 3 | valeur 4 | valeur 5 | Délicieux 2 | Poire | valeur 1 | valeur 2 | valeur 3 | valeur 4 | valeur 5 | Rotton 3 | Kiwi | valeur 1 | valeur 2 | valeur 3 | valeur 4 | valeur 5 | D'accord
--- (démo db <> violon
McNets a fourni une approche raisonnable, mais le fractionnement pour chaque paire, bien qu'évidemment nécessaire, est un processus assez long. Avec plus de 500 paires clé/valeur pour chaque enregistrement de la table, je ne suis pas sûr que cela fonctionnera pour mes besoins. C'est probablement une approche décente s'il y a moins de paires et un petit nombre de lignes dans la table affectée.
Étant donné que je travaille avec des centaines de paires clé/valeur et également des milliers d'enregistrements dans la table elle-même, je pense à implémenter une fonction définie par l'utilisateur (ci-dessous) à utiliser selon les besoins dans les rapports et les requêtes où une clé/valeur spécifique une paire est nécessaire (et connue).
CREATE FUNCTION udfsv_GetFruitDataValue(
@FruitID int,
@DataId varchar(100)
)
RETURNS varchar(100)
AS BEGIN
DECLARE @DataVal varchar(100)
set @DataVal = (
select
replace(replace(split1, @DataId + '=', ''), char(13), '') as DataValue
from Fruit
left outer join (
select
FruitID,
value as split1
from Fruit
cross apply string_split(cast([data] as varchar(max)), char(10))
) line1 on line1.FruitID = Fruit.FruitID
where Fruit.FruitID = @FruitID
and split1 like @DataId + '=%'
)
RETURN @DataVal
END
Avec cela, je serais en mesure d'effectuer des requêtes pour inclure des clés/valeurs spécifiées, mais pas toutes les clés/valeurs.
SELECT
FruitID,
Name,
Description,
udfsv_GetFruitDataValue(FruitID, 'Key 1') as [Key 1],
udfsv_GetFruitDataValue(FruitID, 'Key 4') as [Key 4]
FROM
Fruit
WHERE FruitID = 123