【问题标题】:Replace delimited values in a column with values from another table将列中的分隔值替换为另一个表中的值
【发布时间】:2019-04-29 14:56:47
【问题描述】:

我有一个包含 2 列(代码、描述)的 CountryCodes 表,示例如下:

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 包装在大量替换语句中来附加,但我在此表中有 249 个代码,编写和维护似乎非常低效。

我还研究了 string_split 函数,但我的 SQL 服务器版本不支持它:Microsoft SQL Azure (RTM) - 12.0.2000.8

有人有什么建议吗?

【问题讨论】:

  • 使用分隔符存储值是非常糟糕的数据库设计。
  • 将它们存储为数组(而不是将每个元素存储在另一个表中它们自己的行中)只有在以后不需要加入元素时才好(想想描述查找)。其中的例子是标签数据。但是,SQL Server 不支持数组,并且没有专门的语法/功能来过滤分隔字符串,过滤器性能可能很糟糕。所以,是的,将每个国家市场关联存储在自己的行中shon.github.io/2015/12/21/postgres_array_performance.html
  • @MichaelBuen 没错,SQL Server 不支持数组类型,但它确实包含对可用于存储数组的 XML 和 JSON 的内置支持。话虽如此,如果不对 xml/json 数据编写详细的检查约束,就不可能确保数据实际上是正确的。规范化的结构(每个数组元素的行)更容易防止无效数据,并且更容易使用。 (话虽如此,我对PostgreSQL一无所知,我只是在谈论SQL Server)。

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


【解决方案1】:

蒂姆的回答很好。
您应该规范化您的数据库。这是解决这个问题的正确方法。
欲了解更多信息,请阅读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 用于保留结果中值的原始顺序。 (注意:此技巧仅在每行中的值唯一时才有效)。 注意: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 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

【讨论】:

  • @Shnugo 我已经添加了一些说明。
  • 我只是指出这一点,因为问题提到了 12.0.2000.8... 并且 OP 明确指出,不支持 STRING_SPLIT()...
  • @Shnugo 是的,但它是天蓝色版本,根据official documentation,所有天蓝色产品都支持最高兼容级别 150。
  • "请在您以后的问题中保存我们这一步" 一些流行的小提琴具有文本到 DDL 功能,例如,sqlfiddle.comdb-fiddle.com 如果dbfiddle.uk 也支持它会很好,或者如果OP自己做
  • @MichaelBuen 我发现大多数时候,当我在我的答案中写下这个(我经常这样做)并且答案被接受时,OP 已经知道提供它是多么容易样本数据,实际上将在他们的下一个问题中这样做。我喜欢将其视为我对 Stackoverflow 上 sql 相关问题的整体质量的小贡献。
【解决方案2】:

正如@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;

【讨论】:

    猜你喜欢
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-24
    • 1970-01-01
    • 2021-06-16
    • 2021-05-13
    相关资源
    最近更新 更多