我尝试创建一个可以通过唯一的多对多关系进行分组的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
模式
活动表
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
答案 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;的情况。性能和其他不可估量性取决于正在使用的表格和集合的大小。