2014-09-17 2 views
0

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

В большинстве случаев предлагается нечто подобное.

DECLARE @Actors TABLE ([Id] INT , [Name] VARCHAR(20) , [MovieId] INT); 
DECLARE @Movie TABLE ([Id] INT, [Name] VARCHAR(20), [FranchiseId] INT); 


INSERT INTO @Actors 
    (Id, Name, MovieId) 
VALUES (1, 'Sean Connery', 1), 
    (2, 'Gert Fröbe', 1), 
    (3, 'Honor Blackman', 1), 
    (4, 'Daniel Craig', 2), 
    (5, 'Judi Dench', 2), 
    (2, 'Harrison Ford', 3) 

INSERT INTO @Movie 
    (Id, Name, FranchiseId) 
VALUES (1, 'Goldfinger', 1), 
    (2, 'Skyfall', 1), 
    (3, 'Return of the Jedi', 2) 


SELECT m.Name , 
    STUFF((SELECT ',' + a_c.Name 
      FROM @Actors a_c 
      WHERE a_c.MovieId = m.Id 
      FOR 
      XML PATH('') 
     ), 1, 1, '') 
FROM @Actors a 
    JOIN @Movie m ON a.MovieId = m.Id 
GROUP BY m.Id , 
    m.Name 

Проблема заключается в том (как мне объяснить?), Один на самом деле не получить доступ к сгруппированным элементам (как Count(), Max(), Min(), ...), один ли перестраивать присоединение шаблон «внешнего запроса» и силу в операторе WHERE, что соответствующие значения те же, что и в операторе GROUP BY (во внешнем запросе).

Если вы не понимаете, что я хочу сказать, я продлила пример выше, один дополнительный стол, и вы увидите, что я также должен будет расширить «внутренний запрос»

DECLARE @Actors TABLE ([Id] INT , [Name] VARCHAR(20) , [MovieId] INT); 
DECLARE @Movie TABLE ([Id] INT, [Name] VARCHAR(20), [FranchiseId] INT); 
DECLARE @Franchise TABLE ([Id] INT , [Name] VARCHAR(20)); 


INSERT INTO @Actors 
    (Id, Name, MovieId) 
VALUES (1, 'Sean Connery', 1), 
    (2, 'Gert Fröbe', 1), 
    (3, 'Honor Blackman', 1), 
    (4, 'Daniel Craig', 2), 
    (5, 'Judi Dench', 2), 
    (2, 'Harrison Ford', 3) 

INSERT INTO @Movie 
    (Id, Name, FranchiseId) 
VALUES (1, 'Goldfinger', 1), 
    (2, 'Skyfall', 1), 
    (3, 'Return of the Jedi', 2) 

INSERT INTO @Franchise 
    (Id, Name) 
VALUES (1, 'James Bond'), 
    (2, 'Star Wars') 


SELECT f.Name , 
    STUFF((SELECT ',' + a_c.Name 
      FROM @Actors a_c 
        JOIN @Movie m_c ON a_c.MovieId = m_c.Id 
      WHERE m_c.FranchiseId = f.Id 
      FOR 
      XML PATH('') 
     ), 1, 1, '') 
FROM @Actors a 
    JOIN @Movie m ON a.MovieId = m.Id 
    JOIN @Franchise f ON m.FranchiseId = m.Id 
GROUP BY f.Id , 
    f.Name 

И теперь, идя несколько дальше, представьте огромный запрос, очень сложный, несколько значений группировки во многих таблицах. Эффективность - это проблема. Я не хочу перестраивать весь шаблон объединения во «внутреннем запросе».

Так есть ли другой способ? Способ, который не убивает производительность, и вам не нужно дублировать шаблон соединения?

+0

Извините, но я не понимаю ваш вопрос. Что именно здесь не работает? (Например, когда вы говорите: _ «никто не делает этого и этого]» _, ссылаетесь ли вы на запросы, которые вы показываете, или вы ссылаетесь на то, что вы хотели бы * сделать (но не надейся) t знаете как)?) – stakx

+0

Мой пример работает, да.Но это очень простая инструкция группировки, и у меня возникает проблема группировки, которая является способом huger и более сложной, 20 объединений, 15 групп группировки .... Моя проблема заключается в том, что в сложных запросах данное решение действительно не удовлетворяет. Он имеет плохую производительность и является хрупким и подверженным ошибкам, так как вы должны снова подключиться. Кроме того, сложность также сложна. – Luinil

ответ

0

Вопреки тому, что я сказал in this comment, вам не нужен GROUP BY пункта , ни пункт WHERE, на всех!

Вам просто нужно внешнее SELECT для «перебора» по всем франшизам (или тому, что вы хотите группировать). Затем во внутреннем SELECT вам понадобится JOIN s, чтобы добраться до столбца ключа франшизы. Вместо пункта WHERE фильтрации по ключу внешней франшизы, просто использовать внешний ключ франшизы непосредственно в INNER JOIN:

SELECT f.Name AS FranchiseName, 
     COALESCE(STUFF((SELECT DISTINCT ', ' + a.Name 
         FROM @Actor a 
         JOIN @Movie m ON a.MovieId = m.Id 
         WHERE m.FranchiseId = f.Id 
         ORDER BY ', ' + a.Name -- this is optional 
         FOR XML PATH('')), 1, 1, ''), '') AS ActorNames 
FROM @Franchise f 

Источник информации:"High Performance T-SQL Using Window Functions" by Itzik Ben-Gak. Поскольку у SQL Server, к сожалению, нет функции агрегата/окна для конкатенации значений, автор книги рекомендует что-то вроде выше, как следующее лучшее решение.

P.S .: Я удалил my previous solution that substituted an additional JOIN for a WHERE clause; Теперь я вполне уверен, что a WHERE clause is likely to perform better. Тем не менее, я оставил некоторые доказательства моего предыдущего решения (т. Е. Текст с пропуском) из-за этой ссылки на комментарий, который я сделал ранее.

+0

Это выглядит очень интересно, и я сразу же попробую – Luinil

+0

Производительность лучше, чем во всех предыдущих попытках. его по-прежнему не самый приятный код, но, по крайней мере, производительность лучше, и это гораздо важнее, чем другие вещи. Большое спасибо! – Luinil

+0

Добро пожаловать! Я рад слышать, что вы добиваетесь прогресса. Не стесняйтесь повышать/принимать любые полезные ответы. ;);) – stakx

0

Возможно, вы упростите свой запрос с помощью обычного табличного выражения (CTE). Таким образом, вам нужно указать один раз JOIN. Кроме того, вам действительно нужен только один столбец в GROUP BY:

WITH idsAndNames AS -- the CTE that is used in two places 
(
    SELECT f.Id AS FranchiseId, 
      f.Name AS FranchiseName, 
      m.Id AS MovieId, 
      m.Name AS MovieName, 
      a.Id AS ActorId, 
      a.Name As ActorName 
    FROM @Actors a 
    JOIN @Movie m ON a.MovieId = m.Id 
    JOIN @Franchise f ON m.FranchiseId = f.Id 
) 
SELECT n.FranchiseName, 
     STUFF((SELECT ',' + x.ActorName -- you might need a DISTINCT here, btw. 
       FROM idsAndNames x 
       WHERE x.FranchiseId = n.FranchiseId 
      FOR XML PATH('')), 1, 1, '') 
FROM idsAndNames n 
GROUP BY /* n.FranchiseId, */ n.FranchiseName -- name might suffice if it's unique 
+0

Как вы упомянули, в случае, если одно и то же имя дублируется по какой-либо причине, мне понадобится Id в заявлении группы. Спасибо за ввод с общим табличным выражением. В огромном запросе (уже имеющем несколько CTE) это не помогает читаемости. Тем не менее, я попробовал, но производительность по-прежнему снижается. К сожалению, я просто не могу дать вам очень сложный запрос, который у меня есть. Просто imageine около 100 строк, 10 объединяются, 15 клавиш группировки. Очень неприятно все вместе. Поэтому я надеюсь на что-то еще, может быть, на какой-то другой подход и не пытаюсь улучшить данный. – Luinil

+0

@Luinil: Я думаю, что это несправедливо, чтобы потенциальные ответчики могли в темноте. Если мы не видим фактический запрос (или, по крайней мере, его общую форму) и не знаем исходную проблему (мы видим только одну из ваших попыток ее решения), как вы можете ожидать, что кто-нибудь найдет решение? Вероятно, это произойдет только по чистой случайности ... или если вы не уточните свою проблему более точно, чем вы до сих пор. – stakx

+0

@Luinil: Также обратите внимание, что я не предлагал CTE для удобочитаемости; Я предложил это, потому что это избавляет вас от необходимости выполнять 'JOIN' как во внутреннем, так и в внешнем 'SELECT'. Вы просто создаете один CTE, который содержит всю информацию, необходимую для выбора и группировки. Но во внутреннем 'SELECT' и' GROUP' во внешнем 'SELECT' вы всегда будете * требовать предложение WHERE', если хотите объединить атрибуты для некоторой группы вещей. – stakx

Смежные вопросы