用另一张表中的值替换列中的分隔值

时间:2019-04-29 04:35:01

标签: sql sql-server tsql split azure-sql-database

我有一个带有2列(代码,说明)的CountryCounts表,下面是一个示例:

code description
AD   Andorra
AE   United Arab Emirates
AF   Afghanistan 

我在一个包含以下字符串的视图中有一个名为Markets的列:

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

我需要编写一条select语句,该语句将从定界符(|)之间的CountryCodes表中的Market列中查找代码。例如:

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

我知道可以通过将select包裹在大量的replace语句中来附加它,但是此表中有249个代码,编写和维护起来似乎效率极低。

我也研究了string_split函数,但是我的SQL Server版本不支持该函数:Microsoft SQL Azure(RTM)-12.0.2000.8

有人有什么建议吗?

2 个答案:

答案 0 :(得分:0)

正如@Jens的注释正确指出的那样,将国家/地区代码存储为长度不同的竖线分隔字符串是不好的表设计。相反,最好为每个记录存储一个关系,如下所示:

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

然后,如果要将每个ID的市场转换为CSV列表,则可以尝试:

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;

答案 1 :(得分:0)

蒂姆的答案很好。
您应该规范化数据库。那是解决这个问题的正确方法。
有关更多信息,请阅读Is storing a delimited list in a database column really that bad?,您将在这里看到很多答案,为什么这个问题的答案是绝对是!

但是,由于许多原因,很多时候您根本无法更改数据库结构。有时,更改太昂贵了,有时您正在使用第三方数据库。
不管是什么原因,我在这里(以及其他地方)都回答过很多问题,这些地方应该更改数据库结构,但这不是一个选择。

因此,我将为您提供一个答案,说明如何在不更改数据库结构的情况下获得所需的输出。

首先,创建并填充示例表(在您将来的问题中为我们保存此步骤):

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可以保留结果中值的原始顺序。 (注意:仅当每行中的值都是唯一的时,此技巧才有效)。 注意:Azure数据库支持String_split,但要求兼容级别至少为 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 joinisnull用于处理在代码表中找不到值的情况。在实际情况下,您可能希望丢弃这些值-如果是这种情况,请将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,则可以使用用户定义的函数来split the string