Как я могу сжать строки из нескольких строк в одно поле? - PullRequest
2 голосов
/ 12 декабря 2011

Для проекта класса, несколько других, и я решил сделать (очень уродливый) ограниченный клон 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?

Ответы [ 3 ]

2 голосов
/ 12 декабря 2011

Вот одно из возможных решений с использованием рекурсивного 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;
1 голос
/ 17 декабря 2011

Это распространенный вопрос, который часто задают несколькими различными способами (объединять строки в строку, объединять строки в строку, объединять строки в строку, объединять строки в строку и т. Д.).Существует два общепринятых способа обработки произвольного количества строк в одной строке в 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 

Вот рабочий пример (заимствуя немного измененную настройку из 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 также подробно описывает метод).

Конечно, есть и другой способ сделать это., который не обрабатывает произвольное количество строк, то есть объединяет вашу таблицу с псевдонимом несколько раз, что вы и сделали в своем ответе.

1 голос
/ 17 декабря 2011

Это было решением, на котором я остановился.Я пометил ответ Мака, потому что он работает с произвольным числом тегов, и потому что он соответствует тому, что я просил в своем вопросе.Я закончил, хотя и пошел с этим, однако, просто потому, что я понимаю, что это делает, в то время как я не знаю, как работает Мак:)

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...