Заменить разделенные значения в столбце значениями из другой таблицы - PullRequest
0 голосов
/ 29 апреля 2019

У меня есть таблица CountryCodes с 2 столбцами (код, описание), пример ниже:

code description
AD   Andorra
AE   United Arab Emirates
AF   Afghanistan 

У меня есть столбец Markets в представлении, которое содержит строки, как показано ниже:

Markets (this is one column)
AD | AE | AF
US | UK
NZ | AU | AD

Мне нужно написать оператор выбора, который будет искать коды из столбца Market в таблице CountryCodes между разделителем (|). Например:

AD | AE | AF ----> Andorra | United Arab Emirates | Afghanistan
US | UK ----> United States | United Kingdom

Я знаю, что это можно добавить, обернув select в тонну операторов замены, но у меня в таблице 249 кодов, и это кажется ужасно неэффективным для написания и поддержки.

Я также изучил функцию string_split, но она не поддерживается в моей версии SQL-сервера: Microsoft SQL Azure (окончательная первоначальная версия) - 12.0.2000.8

У кого-нибудь есть предложения?

Ответы [ 2 ]

0 голосов
/ 29 апреля 2019

Тим очень хороший ответ.
Вы должны нормализовать вашу базу данных. Это правильный способ решить эту проблему.
Для получения дополнительной информации читайте Действительно ли плохо хранить список с разделителями в столбце базы данных? , где вы увидите множество причин, по которым ответ на этот вопрос Абсолютно да!

Однако бывают случаи, когда вы просто не можете изменить структуру базы данных по ряду причин. Иногда изменения слишком дороги, иногда вы работаете со сторонней базой данных.
Какова бы ни была причина, я ответил здесь на множество вопросов (и в других местах), где структура базы данных должна быть изменена, но это не вариант.

Поэтому я дам вам ответ, который показывает, как вы можете получить желаемый результат без изменения структуры базы данных.

Сначала создайте и заполните примеры таблиц ( Пожалуйста, сохраните этот шаг в ваших будущих вопросах):

DECLARE @Codes AS TABLE
(
    Code char(2),
    Description varchar(100)
);

INSERT INTO @Codes (Code, Description) VALUES
('AD', 'Andorra'),
('AE', 'United Arab Emirates'),
('AF', 'Afghanistan'),
('UK', 'United Kingdom');

DECLARE @T AS TABLE
(
    Markets varchar(100)
);

INSERT INTO @T (Markets) VALUES
('AD | AE | AF'),
('US | UK'),
('NZ | AU | AD');

Затем я использую общее табличное выражение для разбиения значений в столбце Markets на строки.
Charindex предназначен для сохранения первоначального порядка значений в результате. (Примечание: этот прием работает только в том случае, если значения уникальны в каждой строке). Примечание. String_split поддерживается базой данных Azure, но требует уровень совместимости не менее 130

WITH CTE AS
(
SELECT Markets, 
       TRIM(Value) As Code, 
       CHARINDEX(Value, Markets) As Sort
FROM @T
CROSS APPLY STRING_SPLIT(Markets, '|')
)

Затем, используя string_agg Я восстанавливаю строки, но на этот раз с их переводами.
string_agg поддерживается базой данных Azure, но требуется уровень совместимости не менее 140 .

Примечание: left join и isnull предназначены для обработки случаев, когда есть значение, которое не может быть найдено в таблице кодов. В вашем реальном случае вы можете отказаться от этих значений - в этом случае измените left join на inner join и удалите isnull.

SELECT Markets, 
       STRING_AGG(ISNULL(Description, 'N/A'), ' | ') WITHIN GROUP(ORDER BY Sort) As Translated
FROM CTE
LEFT JOIN @Codes C
    ON CTE.Code = C.Code
GROUP BY Markets

Результаты:

Markets         Translated
AD | AE | AF    Andorra | United Arab Emirates | Afghanistan
NZ | AU | AD    N/A | N/A | Andorra
US | UK         N/A | United Kingdom

Вы можете увидеть живое демо на db <> fiddle

Если ваш уровень совместимости меньше 140, вы можете использовать более старый трюк для агрегирования строк, используя for xml.

Если ваш уровень совместимости меньше 130, вы можете использовать пользовательскую функцию для разбиения строки .

0 голосов
/ 29 апреля 2019

Как правильно заметил комментарий @Jens, хранение кодов стран в виде строк с разделителями по конвейеру, различной длины, является плохим дизайном таблицы. Вместо этого было бы намного лучше хранить одно отношение на запись, что-то вроде этого:

ID | code
1  | AD
1  | AE
1  | AF
2  | US
2  | UK
3  | NZ
3  | AU
3  | AD

Затем, если вы хотите преобразовать это в списки рынков CSV для каждого ID, вы можете просто попробовать:

SELECT
    m.ID,
    STRING_AGG(cc.description, ',') WITHIN GROUP (ORDER BY m.ID) AS markets
FROM Markets m
INNER JOIN CountryCodes cc
    ON m.code = cc.code
GROUP BY
    m.ID;
...