T-SQL通过独特的多对多关系性能进行分组

时间:2016-06-01 15:16:58

标签: sql sql-server database query-performance

我尝试创建一个可以通过唯一的多对多关系进行分组的SQL查询。我有一个 Event 表,可以有一个或多个艺术家。我想将事件分组,只要他们没有相同的阵容(完全相同的艺术家表演)。如果他们有完全相同的阵容,我想只获得最新的(=事件日期)事件。

经过一些调查后,似乎必须使用外部申请,但我真的无法弄明白。

更新

对于每个事件,我需要在结果集中为每个艺术家分别设置行,如“预期结果”表中所述。他们(或艺术家ID)不应该在一个字段中连接。

更新2

我确实得到了以下查询来执行我需要的但是EXISTS条件的性能很差。这可以用更加执行的方式重写吗?

注意: 使用EXISTS条件的SQL语句效率非常低,因为子查询对于外部查询表中的每一行都是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

头条新闻表

 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

1 个答案:

答案 0 :(得分:0)

这个解决方案假定你可以将给定事件的所有ArtistId连接到一个逗号分隔的列表(没有空格),这个列表适合varchar(1000),如果你的所有ArtistId都是90值是十亿(10位数)。 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

接下来,我创建了一个函数来连接给定EventId的所有ArtistId:

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

额外的奖励,它甚至会占到#34;没有艺术家&#34;的情况。性能和其他不可估量性取决于正在使用的表格和集合的大小。