【问题标题】:In SQL, how to convert a column of a comma separated key string to a comma separated value string在 SQL 中,如何将逗号分隔的键字符串的列转换为逗号分隔的值字符串
【发布时间】:2021-03-17 16:38:56
【问题描述】:

我有一个数据库表(表 A),在 SQL Server 2016 中如下所示:

表A:

TaskID - TaskName  -  AssignedTo
-------------------------------
1        Task 1       1,4
2        Task 2       3
3        Task 3       2,3
4        Task 4       2,4,5

我还有一个 TableB,它是 AssignedTo 的查找表,如下所示:

表B:

AssigneeID - Name
-------------------------------
1            John Smith
2            Janet Wright
3            Tom Morgan
4            Kevin Warren
5            Mike Taylor

我想编写一个查询来生成以下报告/表格:

TaskID - TaskName  -  NameAssignedTo
------------------------------------------------------------
1        Task 1       John Smith,Kevin Warren
2        Task 2       Tom Morgan
3        Task 3       Janet Wright,Tom Morgan
4        Task 4       Janet Wright,Kevin Warren,Mike Taylor

如果可以通过编写 SQL 查询来实现,那就太好了。任何人都可以帮忙吗?非常感谢!

【问题讨论】:

  • 修复你的数据模型!不要将数字存储在字符串中!不要在一个字符串中存储多个值!声明外键关系!
  • 您好@Gordon,感谢您的评论,我只是以上面的两个表格为例来解释我的问题。然而,这两个表没有正式声明的外键关系。你能帮我处理 SQL 查询吗?非常感谢!
  • 。 .他们当然没有。他们不能。这就是我发表评论的目的。字符串和整数不是兼容的类型。 应该有一个关系,但你不能定义它。了解联结/关联表以及在 SQL 中表示这种关系的正确方法。
  • 在提出问题时,您需要提供一个可重现的最小示例。请参考以下链接:stackoverflow.com/help/minimal-reproducible-example 请提供以下内容: (1) DDL 和样本数据填充,即 CREATE table(s) 加上 INSERT T-SQL 语句。 (2) 你需要做什么,即逻辑和你的代码尝试在 T-SQL 中实现它。 (3) 期望的输出,基于上面#1 中的样本数据。 (4) 你的 SQL Server 版本 (SELECT @@version;)
  • @Denis 我认为你问的问题很好。非常清楚。您不需要需要提供最小的可重现示例,但如果您这样做,人们将能够更好地提供帮助。忽略关于你不应该这样做或那样做的讲座......有时我们没有选择。

标签: sql sql-server reporting


【解决方案1】:

下次您需要提供##1-4。 并从这个答案中了解它的含义,即一个最小的可重现示例。 您将其复制到 SSMS 并在那里启动它。

下面是如何在 SQL Server 2016 中实现它:

  • STRING_SPLIT() 将其分解,每行一个 AssignedTo
  • SELECT ... FOR XML ... 将其还原为每个任务的一行。

SQL

-- DDL and sample data population, start
DECLARE @tblA TABLE (TaskID INT PRIMARY KEY, TaskName VARCHAR(100), AssignedTo VARCHAR(30));
INSERT INTO @tblA (TaskID, TaskName, AssignedTo) VALUES
(1, 'Task 1', '1,4'),
(2, 'Task 2', '3'),
(3, 'Task 3', '2,3'),
(4, 'Task 4', '2,4,5');

DECLARE @tblB TABLE (AssigneeID INT PRIMARY KEY, [Name] VARCHAR(30));
INSERT INTO @tblB (AssigneeID, [Name]) VALUES
(1, 'John Smith'),
(2, 'Janet Wright'),
(3, 'Tom Morgan'),
(4, 'Kevin Warren'),
(5, 'Mike Taylor')
-- DDL and sample data population, end

DECLARE @separator CHAR(1) = ',';

;WITH cte AS
(
    SELECT * FROM @tblA
        CROSS APPLY (SELECT value FROM STRING_SPLIT(AssignedTo, @separator)) AS x
        INNER JOIN @tblB AS b ON x.value = b.AssigneeID
)
SELECT p.TaskID, p.TaskName
    , STUFF((SELECT DISTINCT
                     CONCAT(@separator, c.Name)
                     FROM cte AS c
                     WHERE c.TaskID = p.TaskID
                     FOR XML PATH ('')),
             1, 1, '') AS NameAssignedTo
FROM cte AS p
GROUP BY p.TaskID, p.TaskName;

输出

+--------+----------+---------------------------------------+
| TaskID | TaskName |            NameAssignedTo             |
+--------+----------+---------------------------------------+
|      1 | Task 1   | John Smith,Kevin Warren               |
|      2 | Task 2   | Tom Morgan                            |
|      3 | Task 3   | Janet Wright,Tom Morgan               |
|      4 | Task 4   | Janet Wright,Kevin Warren,Mike Taylor |
+--------+----------+---------------------------------------+

【讨论】:

  • 查理,添加.value(..) 方法的原因是什么?不涉及 XML 数据类型。
  • 非常感谢@Yitzhak Khabinsky!它似乎有效,我将把它应用到我的真实表格上来查看。再次感谢!
  • @Denis,很高兴听到建议的解决方案对您有用。请不要忘记将其标记为答案。
  • 非常感谢,它也适用于我的实时数据。我已将其标记为答案!再次感谢您。
【解决方案2】:

您拥有的架构从根本上损坏了。你真的想要一个额外的表来表达TaskAssignments,如下所示:

TaskID Assignee
1 1
1 4
2 3
3 2
3 3
4 2
4 4
4 5

您可以找到其他解决方案来为您提供此查询的结果,但这会使它更容易编写,它的性能会好几个数量级,而且它也更适合回答您甚至还没有想过要问的问题(查询)。

这里的一般经验法则:人类更喜欢更少、更宽的桌子,但计算机更喜欢更多、更窄的桌子。因此,我们设计了数据库存储来安抚计算机,并编写报告来为人类展示组合数据。

【讨论】:

  • 感谢@Joel 的评论!我知道最好在中间有一个附加表来识别多对多关系。不过此时我的任务是写报告,而不是从根本上改变代码和数据库结构。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
  • 2017-12-24
  • 2019-11-18
  • 2015-03-09
  • 2019-02-11
相关资源
最近更新 更多