2016-06-01 3 views
3

Я пытаюсь создать SQL-запрос, который может группироваться по уникальным отношениям «многие ко многим». У меня есть таблица Event, которая может иметь 1 или более художников. Я хотел бы сгруппировать события , но, только если у них нет одинакового состава (точно такие же исполнители). Если они имеют точно такой же состав, я бы хотел получить только самое новое событие (= Дата события).T-SQL Группировка по уникальным отношениям «многие-ко-многим»

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

UPDATE

Я бы необходима отдельные строки для каждого художника, каждое события, в наборе результатов, как описано в таблице ожидаемого результата. Они (или идентификатор художника) не должны быть объединены в одном поле.

UPDATE 2

я получил следующий запрос, чтобы сделать то, что я бы обязательно, но СУЩЕСТВУЕТ условие имеет низкую производительность. Может ли это быть переписано более успешно?

Примечание: Операторы SQL, которые используют условие EXISTS, очень неэффективны, так как подзапрос RE-RUN для КАЖДОЙ строки в таблице внешнего запроса. Существуют более эффективные способы написания большинства запросов, которые не используют условие EXISTS.

SELECT E.* 
FROM [Events] AS E 
WHERE EXISTS(
    SELECT NULL AS [EMPTY] 
    FROM [Headliners] AS H 
    WHERE H.[EventId] = E.[EventId] 
) 
AND NOT EXISTS(
    SELECT NULL AS [EMPTY] 
    FROM [Events] AS E2 
    WHERE E2.[Date] < E.[Date] 
    AND NOT EXISTS(
      SELECT NULL AS [EMPTY] 
      FROM [Headliners] AS H1 
      WHERE NOT EXISTS(
       SELECT NULL AS [EMPTY] 
       FROM [Headliners] AS [t4] 
       WHERE [t4].[EventId] = E.[EventId] 
       AND CASE WHEN [t4].[ArtistId] <> H1.[ArtistId] THEN 1 ELSE 0 END = 0) 
     AND H1.[EventId] = E2.[EventId]) 
    AND NOT EXISTS(
     SELECT NULL AS [EMPTY] 
     FROM [Headliners] AS H2 
     WHERE NOT EXISTS(
       SELECT NULL AS [EMPTY] 
       FROM [Headliners] AS [t6] 
       WHERE [t6].[EventId] = E2.[EventId] 
       AND CASE WHEN [t6].[ArtistId] <> H2.[ArtistId] THEN 1 ELSE 0 END = 0) 
     AND H2.[EventId] = E.[EventId] 
    ) 
) 

Ожидаемый результат

x-----------x---------x------------x----------x 
| EventId | Name | Date  | ArtistId | 
x-----------x---------x------------x----------x 
| 1  | E1 | 2016-01-01 | 1  | 
| 1  | E1 | 2016-01-01 | 2  | 
| 2  | E2 | 2016-01-02 | 3  | 
| 4  | E4 | 2016-01-04 | 5  | 
| 4  | E4 | 2016-01-04 | 6  | 
| 5  | E5 | 2016-01-05 | 4  | 
| 6  | E6 | 2016-01-06 | 5  | 
x-----------x---------x------------x----------x 

Схема

enter image description here

Таблица событий

x-----------x---------x------------x 
| EventId | Name | Date  | 
x-----------x---------x------------x 
| 1  | E1 | 2016-01-01 | 
| 2  | E2 | 2016-01-02 | 
| 3  | E3 | 2016-01-03 | 
| 4  | E4 | 2016-01-04 | 
| 5  | E5 | 2016-01-05 | 
| 6  | E6 | 2016-01-06 | 
| 7  | E7 | 2016-01-07 | 
| 8  | E8 | 2016-01-08 | 
x-----------x---------x------------x 

Headliner стол

x-----------x-------------x 
| EventId | ArtistId | 
x-----------x-------------x 
| 1  | 1   | 
| 1  | 2   | 
| 2  | 3   | 
| 3  | 2   | 
| 3  | 1   | 
| 4  | 5   | 
| 4  | 6   | 
| 5  | 4   | 
| 6  | 5   | 
| 8  | 3   | 
x-----------x-------------x 

Исполнитель стол

x------------x---------x--------------x 
| ArtistId | Name | Bio  | 
x------------x---------x--------------x 
| 1  | A1 | BIO1   | 
| 2  | A2 | BIO2   | 
| 3  | A3 | BIO3   | 
| 4  | A4 | BIO4   | 
| 5  | A5 | BIO5   | 
| 6  | A6 | BIO6   | 
x------------x---------x--------------x 
+0

... a 404 страница ошибки? –

+1

Я думаю, что ваши данные о выборке и результатах несовместимы с EventId 7 и 8 –

+1

@PhilipKelley Я не вижу несогласованности. Событие 7 не имеет хедлайнеров, а 8 имеет 1 headliner (artist id = 3), но это дубликат eventid 2. – Frank

ответ

0

Это решение делает предположение, что вы можете конкатенировать все ArtistIds для данного события в запятую (без пробелов), который будет вписываться в varchar(1000), который будет равен 90, если все ваши значения ArtistId находятся в миллиардах (10 d igits). Varchar(8000) также будет работать, но все может получиться неудовлетворительным, если вам нужно использовать varchar(max).

Я использовал это, чтобы настроить данные:

CREATE TABLE Event 
(
    EventId int not null 
    ,Name  varchar(50) not null 
    ,Date  date not null 
) 

INSERT Event values 
    (1,'E1', 'Jan 1, 2016') 
,(2,'E2', 'Jan 2, 2016') 
,(3,'E3', 'Jan 3, 2016') 
,(4,'E4', 'Jan 4, 2016') 
,(5,'E5', 'Jan 5, 2016') 
,(6,'E6', 'Jan 6, 2016') 
,(7,'E7', 'Jan 7, 2016') 
,(8,'E8', 'Jan 8, 2016') 

CREATE TABLE Headliner 
(
    EventId int not null 
    ,ArtistId int not null 
) 

INSERT Headliner values 
    (1,1) 
,(1,2) 
,(2,3) 
,(3,2) 
,(3,1) 
,(4,5) 
,(4,6) 
,(5,4) 
,(6,5) 
,(8,3) 

CREATE TABLE Artist 
(
    ArtistId int not null 
    ,Name  varchar(50) not null 
    ,Bio  varchar(50) not null 
) 

INSERT Artist values 
    (1, 'A1', 'BI01') 
    ,(2, 'A2', 'BI02') 
    ,(3, 'A3', 'BI03') 
    ,(4, 'A4', 'BI04') 
    ,(5, 'A5', 'BI05') 
    ,(6, 'A6', 'BI06') 

SELECT 
    EvntId 
    ,isnull(
from Headliner 
group by EventId 

Далее я создал функцию для объединения всех ArtistIds для данного EVENTID:

IF objectproperty(object_id('dbo.Concat'), 'isScalarFunction') = 1 
    DROP FUNCTION dbo.Concat 

GO 
CREATE FUNCTION dbo.Concat 
(
    @EventId int 
) 
RETURNS varchar(1000) 
BEGIN 
    DECLARE @Concatenated varchar(1000) 

    SELECT @Concatenated = isnull(@Concatenated + ',' , '') + cast(ArtistId as varchar(10)) 
    from Headliner 
    where EventId = @EventId 
    order by ArtistId 

    RETURN @Concatenated 
END 
GO 

Это позволяет нам использовать двукратно вложенный CTE для выполнения работы. (Может быть сделано с временными таблицами, если производительностью является проблемой, но это кажется маловероятным.)

;WITH cteSetList 
as (-- Build list of artists for each Event, and produce 
    -- an ordering on events by date 
    select 
     EventId 
     ,Name 
     ,Date 
     ,dbo.Concat(EventId) ArtistList 
     ,row_number() over (order by Date) Ranking 
     from Event 
    ) 
,cteRankLists 
    as (-- Find earliest event for each ArtistList 
     select 
     ArtistList 
     ,min(Ranking) FirstForList 
     from cteSetList 
     group by ArtistList 
    ) 
-- Take that earliest list of artists, join it back to the first query 
-- to get the EventId, join that back to the base tables and we're done 
select 
    sl.EventId 
    ,sl.Name 
    ,sl.Date 
    ,he.ArtistId 
    from cteRankLists rl 
    inner join cteSetList sl 
    on sl.Ranking = rl.FirstForList 
    left outer join Headliner he 
    on he.EventId = sl.EventId 
    order by sl.EventId, he.ArtistId 

Дополнительный бонус, он будет даже счет «нет художника» ситуации. Производительность и другие невесомости зависят от размера используемых таблиц и наборов.

+0

@Nielsen Мне не нужны значения, объединенные в результирующем наборе (см. Ожидаемые результаты). Мне нужно, чтобы они были в разных рядах. Спасибо за все усилия. – Frank

+0

Собственно, это * делает * возвращает значения в отдельных строках. Попробуй и посмотри! –

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