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

主页:列出所有问题,其得分(从投票中计算)以及与他们的第一个修订版相对应的用户,以及答案的数量,这些答案按照对问题的最后操作(以操作为准,以降序排列)答案,答案的编辑或问题的编辑)。

现在,我们已经弄清楚了所有事情,除了如何表示问题标签。 我们目前正在使用标记的MN映射到类似这样的问题:

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
);

现在,对于此查询,如果我们只是加入QuestionTags,那么我们将一遍又一遍地获得问题和标题。 如果不这样做,那么我们有N个查询场景,同样糟糕。 理想情况下,我们将在结果行中包含以下内容:

+-------------+------------------+
| Other Stuff | Tags             |
+-------------+------------------+
| Blah Blah   | TagA, TagB, TagC |
+-------------+------------------+

基本上-对于JOIN中的每一行,在结果标签上进行字符串联接。

是否有内置函数或类似函数可以在T-SQL中完成此任务?

===============>>#1 票数:2 已采纳

这是使用递归CTE的一种可能的解决方案:

此处说明使用的方法

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

DECLARE @QuestionRevisions TABLE  ( 
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)

DECLARE @Tags TABLE ( 
id INT IDENTITY NOT NULL, 
name NVARCHAR(45) NOT NULL
)

DECLARE @QuestionTags TABLE ( 
tag INT NOT NULL, 
question INT NOT NULL
)
INSERT INTO @QuestionRevisions 
(question,postDate,contents,creatingUser,title)
VALUES
(1,GETDATE(),'Contents 1',1,'TITLE 1')

INSERT INTO @QuestionRevisions 
(question,postDate,contents,creatingUser,title)
VALUES
(2,GETDATE(),'Contents 2',2,'TITLE 2')

INSERT INTO @Tags (name) VALUES ('Tag 1')
INSERT INTO @Tags (name) VALUES ('Tag 2')
INSERT INTO @Tags (name) VALUES ('Tag 3')
INSERT INTO @Tags (name) VALUES ('Tag 4')
INSERT INTO @Tags (name) VALUES ('Tag 5')
INSERT INTO @Tags (name) VALUES ('Tag 6')

INSERT INTO @QuestionTags (tag,question) VALUES (1,1)
INSERT INTO @QuestionTags (tag,question) VALUES (3,1)
INSERT INTO @QuestionTags (tag,question) VALUES (5,1)
INSERT INTO @QuestionTags (tag,question) VALUES (4,2)
INSERT INTO @QuestionTags (tag,question) VALUES (2,2)

这是动作部分:

;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;

===============>>#2 票数:1

这就是我最终选择的解决方案。 我勾选了Mack的答案,因为它可以处理任意数量的标签,并且可以匹配我在问题中所要求的内容。 我最终还是这么做了,但是,仅仅是因为我了解了它在做什么,而我却不知道Mack的工作原理:)

WITH tagScans (qRevId, tagName, tagRank)
AS (
    SELECT DISTINCT
        QuestionTags.question AS qRevId,
        Tags.name AS tagName,
        ROW_NUMBER() OVER (PARTITION BY QuestionTags.question ORDER BY Tags.name) AS tagRank
    FROM QuestionTags
    INNER JOIN Tags ON Tags.id = QuestionTags.tag
)
SELECT
    Questions.id AS id,
    Questions.currentScore AS currentScore,
    answerCounts.number AS answerCount,
    latestRevUser.id AS latestRevUserId,
    latestRevUser.caseId AS lastRevUserCaseId,
    latestRevUser.currentScore AS lastRevUserScore,
    CreatingUsers.userId AS creationUserId,
    CreatingUsers.caseId AS creationUserCaseId,
    CreatingUsers.userScore AS creationUserScore,
    t1.tagName AS tagOne,
    t2.tagName AS tagTwo,
    t3.tagName AS tagThree,
    t4.tagName AS tagFour,
    t5.tagName AS tagFive
FROM Questions
INNER JOIN QuestionRevisions ON QuestionRevisions.question = Questions.id
INNER JOIN
(
    SELECT
        Questions.id AS questionId,
        MAX(QuestionRevisions.id) AS maxRevisionId
    FROM Questions
    INNER JOIN QuestionRevisions ON QuestionRevisions.question = Questions.id
    GROUP BY Questions.id
) AS LatestQuestionRevisions ON QuestionRevisions.id = LatestQuestionRevisions.maxRevisionId
INNER JOIN Users AS latestRevUser ON latestRevUser.id = QuestionRevisions.creatingUser
INNER JOIN
(
    SELECT
        QuestionRevisions.question AS questionId,
        Users.id AS userId,
        Users.caseId AS caseId,
        Users.currentScore AS userScore
    FROM Users
    INNER JOIN QuestionRevisions ON QuestionRevisions.creatingUser = Users.id
    INNER JOIN
    (
        SELECT
            MIN(QuestionRevisions.id) AS minQuestionRevisionId
        FROM Questions
        INNER JOIN QuestionRevisions ON QuestionRevisions.question = Questions.id
        GROUP BY Questions.id
    ) AS QuestionGroups ON QuestionGroups.minQuestionRevisionId = QuestionRevisions.id
) AS CreatingUsers ON CreatingUsers.questionId = Questions.id
INNER JOIN
(
    SELECT
        COUNT(*) AS number,
        Questions.id AS questionId
    FROM Questions
    INNER JOIN Answers ON Answers.question = Questions.id
    GROUP BY Questions.id
) AS answerCounts ON answerCounts.questionId = Questions.id
LEFT JOIN tagScans AS t1 ON t1.qRevId = QuestionRevisions.id AND t1.tagRank = 1
LEFT JOIN tagScans AS t2 ON t2.qRevId = QuestionRevisions.id AND t2.tagRank = 2
LEFT JOIN tagScans AS t3 ON t3.qRevId = QuestionRevisions.id AND t3.tagRank = 3
LEFT JOIN tagScans AS t4 ON t4.qRevId = QuestionRevisions.id AND t4.tagRank = 4
LEFT JOIN tagScans AS t5 ON t5.qRevId = QuestionRevisions.id AND t5.tagRank = 5
ORDER BY QuestionRevisions.postDate DESC

===============>>#3 票数:1

这是一个常见的问题,经常以多种不同的方式来表达(将行连接为字符串,将行合并为字符串,将行压缩为字符串,将行合并为字符串等)。 在SQL Server中,有两种公认的方法可以将任意数量的行合并为单个字符串。

第一种方法(通常是最简单的方法)是将XML PathSTUFF函数结合使用,如下所示:

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 

这是一个有效的示例 (从Mack借用一些稍作修改的设置)。 为了您的目的,您可以将该查询的结果存储在公用表表达式或子查询中(我将保留其作为练习)。

第二种方法是使用递归公用表表达式 这是一个有注释的示例,该示例将如何工作:

--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答案中的链接也对该方法进行了详细介绍)。

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

  ask by Billy ONeal translate from so

未解决问题?本站智能推荐: