Sql 如何将多个字符串行中的字符串压缩为单个字段?

Sql 如何将多个字符串行中的字符串压缩为单个字段?,sql,sql-server,tsql,Sql,Sql Server,Tsql,对于一个类项目,我和其他一些人决定制作一个(非常难看的)StackOverflow的有限克隆。为此,我们正在处理一个查询: 主页:列出所有问题、它们的分数(根据投票数计算)、与它们的第一次修订相对应的用户以及答案数量,并根据问题上的最后一个操作(其中一个操作是答案、编辑答案或编辑问题)按日期降序排序 现在,我们已经弄清楚了整个事情,除了如何表示问题上的标记。我们目前正在使用标签的M-N映射来回答以下问题: CREATE TABLE QuestionRevisions ( id INT IDENT

对于一个类项目,我和其他一些人决定制作一个(非常难看的)StackOverflow的有限克隆。为此,我们正在处理一个查询:

主页:列出所有问题、它们的分数(根据投票数计算)、与它们的第一次修订相对应的用户以及答案数量,并根据问题上的最后一个操作(其中一个操作是答案、编辑答案或编辑问题)按日期降序排序

现在,我们已经弄清楚了整个事情,除了如何表示问题上的标记。我们目前正在使用标签的M-N映射来回答以下问题:

CREATE TABLE QuestionRevisions (
id INT IDENTITY NOT NULL,
question INT NOT NULL,
postDate DATETIME NOT NULL,
contents NTEXT NOT NULL,
creatingUser INT NOT NULL,
title NVARCHAR(200) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT questionrev_fk_users FOREIGN KEY (creatingUser) REFERENCES
Users (id) ON DELETE CASCADE,
CONSTRAINT questionref_fk_questions FOREIGN KEY (question) REFERENCES
Questions (id) ON DELETE CASCADE
);

CREATE TABLE Tags (
id INT IDENTITY NOT NULL,
name NVARCHAR(45) NOT NULL,
PRIMARY KEY (id)
);

CREATE TABLE QuestionTags (
tag INT NOT NULL,
question INT NOT NULL,
PRIMARY KEY (tag, question),
CONSTRAINT qtags_fk_tags FOREIGN KEY (tag) REFERENCES Tags(id) ON
DELETE CASCADE,
CONSTRAINT qtags_fk_q FOREIGN KEY (question) REFERENCES Questions(id) ON
DELETE CASCADE
);
现在,对于这个查询,如果我们只加入问号,那么我们将一遍又一遍地得到问题和标题。如果我们不这样做,那么我们有一个N查询场景,这同样糟糕。理想情况下,我们的结果行应该是:

+-------------+------------------+
|其他东西|标签|
+-------------+------------------+
|诸如此类|塔加,塔加,塔加|
+-------------+------------------+
基本上——对于连接中的每一行,对结果标记进行字符串连接


是否有内置函数或类似函数可以在T-SQL中实现这一点?

这里有一个使用递归CTE的可能解决方案:

对所采用的方法进行了说明

TSQL设置测试数据(我使用表变量):

以下是行动部分:

;WITH CTE ( id, taglist, tagid, [length] ) 
      AS (  SELECT question, CAST( '' AS VARCHAR(8000) ), 0, 0
            FROM @QuestionRevisions qr
            GROUP BY question
            UNION ALL
            SELECT qr.id
                ,  CAST(taglist + CASE WHEN [length] = 0 THEN '' ELSE ', ' END + t.name AS VARCHAR(8000) )
                ,  t.id
                ,  [length] + 1
            FROM CTE c 
            INNER JOIN @QuestionRevisions qr ON c.id = qr.question
            INNER JOIN @QuestionTags qt ON qr.question=qt.question
            INNER JOIN @Tags t ON t.id=qt.tag
            WHERE t.id > c.tagid )
SELECT id, taglist 
FROM ( SELECT id, taglist, RANK() OVER ( PARTITION BY id ORDER BY length DESC )
         FROM CTE ) D ( id, taglist, rank )
WHERE rank = 1;

这就是我最终选择的解决方案。我勾选了麦克的答案,因为它可以处理任意数量的标签,并且符合我在问题中提出的要求。不过,我最终还是选择了这个,原因很简单,因为我了解它在做什么,而我不知道麦克的工作原理:)


这是一个常见的问题,经常以多种不同的方式出现(将行连接为字符串、将行合并为字符串、将行压缩为字符串、将行合并为字符串等)。在SQL Server中,有两种普遍接受的方法来处理将任意数量的行组合成单个字符串的问题

第一个,通常也是最容易的,是滥用XML路径STUFF函数,如下所示:

select rsQuestions.QuestionID,
       stuff((select ', '+ rsTags.TagName
              from @Tags rsTags  
              inner join @QuestionTags rsMap on rsMap.TagID = rsTags.TagID
              where rsMap.QuestionID = rsQuestions.QuestionID
              for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '')
from @QuestionRevisions rsQuestions 
(从麦克那里借用了一些稍微修改过的设置)。出于您的目的,您可以将该查询的结果存储在公共表表达式或子查询中(我将把它作为练习)

第二种方法是使用递归公共表表达式。下面是一个注释示例,说明了该方法的工作原理:

--NumberedTags establishes a ranked list of tags for each question.
--The key here is using row_number() or rank() partitioned by the particular question
;with NumberedTags (QuestionID, TagString, TagNum) as
(
    select  QuestionID,
            cast(TagName as nvarchar(max)) as TagString,
            row_number() over (partition by QuestionID order by rsTags.TagID) as TagNum
    from @QuestionTags rsMap
    inner join @Tags rsTags on rsTags.TagID = rsMap.TagID
),
--TagsAsString is the recursive query
TagsAsString (QuestionID, TagString, TagNum) as
(
    --The first query in the common table expression establishes the anchor for the 
    --recursive query, in this case selecting the first tag for each question
    select  QuestionID,
            TagString,
            TagNum
    from NumberedTags 
    where TagNum = 1
        
    union all
    
    --The second query in the union performs the recursion by joining the 
    --anchor to the next tag, and so on...
    select  NumberedTags.QuestionID,
            TagsAsString.TagString + ', ' + NumberedTags.TagString,
            NumberedTags.TagNum
    from    NumberedTags
    inner join TagsAsString on TagsAsString.QuestionID = NumberedTags.QuestionID
                           and NumberedTags.TagNum = TagsAsString.TagNum + 1
)
--The result of the recursive query is a list of tag strings building up to the final
--string, of which we only want the last, so here we select the longest one which
--gives us the final result
select QuestionID, max(TagString) 
from TagsAsString                         
group by QuestionID
。同样,您可以使用公共表表达式或子查询中的结果来连接其他表,以获得最终结果。希望注释能帮助您更多地了解递归公共表表达式的工作原理(尽管Macks answer中的链接也详细介绍了该方法)


当然,还有另一种方法,它不会处理任意数量的行,那就是针对您的表多次使用别名进行连接,这就是您在回答中所做的。

是否只需要使用tsql来解决这个问题?@SebastianPiu:这将非常好,是的。我不想在SQL server上安装模块或任何东西,如果这是您的意思的话。(如果做不到,我会选择更慢的方法……我真的不必担心一个系统的大量用户,因为这个系统永远看不到生产部署)……不,我的意思不是说,我的意思是,这可能最好在dal层解决(或者任何你作为dal的地方)。我认为您需要两个查询,一个用于获取所有问题的信息,另一个用于获取问题ID和标记对。也许这个答案对您有帮助?如果我正确理解了您的需要,应该是这样。这种“将行合并为字符串”类型的问题被问了很多次,有时我想知道我们是否需要sql faq标记或其他什么。这似乎很有效。我决定使用一些微妙的不同,但我没有在我的问题和解决方案中指定,当标签数量大于某个数字时,我的解决方案不起作用。因此,我对这个答案投了赞成票并进行了核对;不过我会在调试完那该死的东西后发布我的。谢谢
select rsQuestions.QuestionID,
       stuff((select ', '+ rsTags.TagName
              from @Tags rsTags  
              inner join @QuestionTags rsMap on rsMap.TagID = rsTags.TagID
              where rsMap.QuestionID = rsQuestions.QuestionID
              for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '')
from @QuestionRevisions rsQuestions 
--NumberedTags establishes a ranked list of tags for each question.
--The key here is using row_number() or rank() partitioned by the particular question
;with NumberedTags (QuestionID, TagString, TagNum) as
(
    select  QuestionID,
            cast(TagName as nvarchar(max)) as TagString,
            row_number() over (partition by QuestionID order by rsTags.TagID) as TagNum
    from @QuestionTags rsMap
    inner join @Tags rsTags on rsTags.TagID = rsMap.TagID
),
--TagsAsString is the recursive query
TagsAsString (QuestionID, TagString, TagNum) as
(
    --The first query in the common table expression establishes the anchor for the 
    --recursive query, in this case selecting the first tag for each question
    select  QuestionID,
            TagString,
            TagNum
    from NumberedTags 
    where TagNum = 1
        
    union all
    
    --The second query in the union performs the recursion by joining the 
    --anchor to the next tag, and so on...
    select  NumberedTags.QuestionID,
            TagsAsString.TagString + ', ' + NumberedTags.TagString,
            NumberedTags.TagNum
    from    NumberedTags
    inner join TagsAsString on TagsAsString.QuestionID = NumberedTags.QuestionID
                           and NumberedTags.TagNum = TagsAsString.TagNum + 1
)
--The result of the recursive query is a list of tag strings building up to the final
--string, of which we only want the last, so here we select the longest one which
--gives us the final result
select QuestionID, max(TagString) 
from TagsAsString                         
group by QuestionID