使用多个 UNION ALL 构建视图:有更好的方法吗?
Posted
技术标签:
【中文标题】使用多个 UNION ALL 构建视图:有更好的方法吗?【英文标题】:Using multiple UNION ALLs to build a view: is there a better way? 【发布时间】:2010-02-10 21:04:57 【问题描述】:这个问题有一些限制;我没有能力从根本上改变任何数据库结构。
这里的挑战是我在数据库中有包含确实应该在其自己的行上的信息的行。列结构的简化示例:
[PersonID] [FirstName] [LastName] [FirstNameGuest1] [LastNameGuest1]
1 Ringo Starr John Lennon
2 George Harrison Paul McCartney
我需要像这样拆分这些,以便能够运行我需要的报告:
[PersonID] [FirstName] [LastName]
1 Ringo Starr
1 John Lennon
2 George Harrison
2 Paul McCartney
由于我使用它来生成视图,因此我只需要为每组来宾列引用同一个表并使用UNION ALLs
将它们绑定在一起。
但是,从那时起,我不得不在派生视图的基础上构建越来越复杂的查询。每一层的复杂性都会导致结果返回的速度越来越慢。
我是否采取了一种根本上糟糕的方法?还有其他更正确的方法来按照我需要的方式对数据进行建模吗?
这是实际查询的一部分,因此您可以看到我正在处理的内容:
--Primary Record
SELECT
'Franchisee' AS 'Type',
Confirmation AS 'BelongingTo',
0 AS 'GuestNo',
FirstName,
LastName,
FF_27557_152972 AS 'HotelChoice',
HotelCheckIn,
HotelCheckOut,
HotelSmoking AS 'Smoking',
(CASE FF_27554_1 WHEN 'Yes' THEN 1 ELSE 0 END) AS 'PrimaryRoomHolder',
'' AS 'SharingWith',
'None' AS 'SharingWithName'
FROM dbo.[Table]
WHERE Type = 'Production' AND Submitted = 1 AND Cancelled = 0 AND Label = 'Primary'
UNION ALL
-- First Guest
SELECT
'Guest' AS 'Type',
Confirmation AS 'BelongingTo',
1 AS 'GuestNo',
FF_27637_1 AS 'FirstName',
FF_27637_152806 AS 'LastName',
FF_27637_152822 AS 'HotelChoice',
FF_27637_152813 AS 'HotelCheckIn',
FF_27637_152821 AS 'HotelCheckOut',
FF_27637_152824 AS 'Smoking',
(CASE WHEN FF_27637_152822 IS NOT NULL THEN 1 ELSE 0 END) AS 'PrimaryRoomHolder',
FF_27637_154245 AS 'SharingWith',
(CASE CAST(FF_27637_154245 AS integer)
WHEN 0 THEN FirstName + ' ' + LastName
WHEN 1 THEN FF_27637_1 + ' ' + FF_27637_152806
WHEN 2 THEN FF_27742_1 + ' ' + FF_27742_153577
WHEN 3 THEN FF_27638_1 + ' ' + FF_27638_152814
WHEN 4 THEN FF_27639_1 + ' ' + FF_27639_152817
WHEN 5 THEN FF_27640_1 + ' ' + FF_27640_152852
WHEN 6 THEN FF_27641_1 + ' ' + FF_27641_152860
WHEN 7 THEN FF_27642_1 + ' ' + FF_27642_152868
WHEN 8 THEN FF_27643_1 + ' ' + FF_27643_152877
WHEN 9 THEN FF_27644_1 + ' ' + FF_27644_152885
WHEN 10 THEN FF_27645_1 + ' ' + FF_27645_152893
ELSE 'None' END) AS 'SharingWithName'
FROM dbo.Event_213_1546 AS Event_213_1546_10
WHERE Type = 'Production' AND Submitted = 1 AND Cancelled = 0 AND Label = 'Primary' AND FF_27637_1 IS NOT NULL
.
.
.
(Iterates through 9 more guests exactly like "First Guest")
【问题讨论】:
我能感觉到你的痛苦... x_X. 与查询相关的性能是否存在大问题,或者您只是想知道是否有更好的方法? 我感受到了您的痛苦,因为您无法更改数据库结构,但这确实是您问题的最佳解决方案。你需要一个相关的表。 【参考方案1】:您是否考虑过 UNPIVOT 运算符?它做同样的事情,但可能不那么痛苦(也许!:-)。需要 2005 或更高版本。
http://blogs.msdn.com/craigfr/archive/2007/07/17/the-unpivot-operator.aspx
例子:
declare @names table ( personid int,
firstname1 varchar(50),
lastname1 varchar(50),
firstname2 varchar(50),
lastname2 varchar(50),
firstname3 varchar(50),
lastname3 varchar(50)
-- <etc.>
)
Insert @names values ( 1, 'Fred', 'Flintstone', 'Barney', 'Rubble', 'Wilma', 'Flintstone' )
Insert @names values ( 2, 'Super', 'Man', 'Aqua', 'Man', 'Wonder', 'Woman' )
select * from @names
select personid, firstnamecol, firstname, lastnamecol, lastname
from @names
unpivot( firstname for firstnamecol in ( firstname1, firstname2, firstname3 ) ) firstnames
unpivot( lastname for lastnamecol in ( lastname1, lastname2, lastname3 ) ) lastnames
where right(firstnamecol, 1) = right( lastnamecol, 1 ) -- This is the tricky bit
在一次选择中使用多个 unpivot 很棘手;我从这些家伙那里得到了上述概念:
http://weblogs.sqlteam.com/jeffs/archive/2008/04/23/unpivot.aspx http://mangalpardeshi.blogspot.com/2009/04/unpivot-multiple-columns.html
请务必注意,标记为“棘手”的部分会因重复列的名称中不同数量的数字而中断(例如,FirstName11 和 FirstName1 将是一个问题)。您可以使用一些 substring() 技巧来解决这个问题。可能不是最好的主意,但是...
另外,2 月 22 日:这是一篇关于 unpivot 的惊人文章:http://bradsruminations.blogspot.com/2010/02/spotlight-on-unpivot-part-1.html
【讨论】:
哇,我不知道 unpivot 运算符。从概念上讲,这正是我所需要的。 请小心 - 我不知道您可以期待什么样的性能影响。可能很糟糕:-)【参考方案2】:取决于您要对视图执行的操作。 视图查询必须在您对其执行的任何操作之前执行。因此,如果您正在做的事情只处理视图返回的结果的 10%,那么就会浪费处理。执行的操作很可能与视图中已有的逻辑同时完成。
如有必要,您可以添加索引吗?这会有所帮助...
一种称为indexed view in SQL Server terminology 的物化视图是一种可行的可能性,但出了名的不适应。
【讨论】:
从技术上讲,视图不一定“在”其他操作之前“执行”。事实上,我相信视图在封闭查询的上下文中被扩展,然后整个业务被优化为一个计划。使用“noexpand”提示时例外。 @onupdatecascade:为了对视图提供的结果集进行操作,视图必须首先执行。实际上,子查询/派生表。SELECT LEFT(...) FROM v_view
必须先运行视图查询 - 视图查询不会中断以添加其他条件。
很好奇,如果我从视图中省略选择列,我会在视图上运行 select 语句,运行速度会更快(此列基于设计不佳的 UDF。)。如果视图需要先运行,它们会执行相同的操作吗?
查询运行得更快是合理的,因为您之后省略了该列 - 查询计划已经存在。第一次是硬解析,后面是软解析。
@OMGPonies - 这取决于视图的内容和优化规则。如果那里有一个函数(如您的 left() 示例),那么可能需要处理视图中的所有行,但这是因为函数,而不是视图的存在。如果您将视图定义作为派生表复制并粘贴到外部查询中,则优化规则将是相同的。【参考方案3】:
这是实施我在上面接受的答案的结果。希望这对所有面临类似挑战的人有所帮助。
从概念上讲,UNPIVOT 运算符正是我想要完成的模型;它是否没有某种实现限制使我无法使用它还有待观察。由于我有许多列要“解包”,因此有两种基本方法可以使用 UNPIVOT 运算符。 (1) 我可以为每个相关列运行一个单独的 UNPIVOT,或者 (2),我可以运行一个 UNPIVOT,然后有条件地计算其余列那个结果。我选择了第二种方法,因为它对我来说更容易掌握,但我仍然有兴趣在未来测试这两种方法。
生成的查询仍然非常重复(它必须一遍又一遍地执行相同的十条件案例块),但与我最初使用的怪物相比,它的简化令人难以置信。 最重要的是,当我按照旧查询运行时,它仅占总查询成本的 9%,而旧查询的成本为 91%。 所以它似乎也更有效率.
这是修改后的查询(仍然很长,但可能只有旧查询的 20% 左右)
SELECT
Case(FN) WHEN 'FirstName' THEN 'Primary' ELSE 'Guest' END as RegType,
(Case(FN) WHEN 'FirstName' THEN 0
WHEN 'FF_27637_1' THEN 1
WHEN 'FF_27742_1' THEN 2
WHEN 'FF_27638_1' THEN 3
WHEN 'FF_27639_1' THEN 4
WHEN 'FF_27640_1' THEN 5
WHEN 'FF_27641_1' THEN 6
WHEN 'FF_27642_1' THEN 7
WHEN 'FF_27643_1' THEN 8
WHEN 'FF_27644_1' THEN 9
WHEN 'FF_27645_1' THEN 10 END) as GuestNo,
Confirmation,
FirstNames as FirstName,
(Case(FN) WHEN 'FirstName' THEN LastName
WHEN 'FF_27637_1' THEN FF_27637_152806
WHEN 'FF_27742_1' THEN FF_27742_153577
WHEN 'FF_27638_1' THEN FF_27638_152814
WHEN 'FF_27639_1' THEN FF_27639_152817
WHEN 'FF_27640_1' THEN FF_27640_152852
WHEN 'FF_27641_1' THEN FF_27641_152860
WHEN 'FF_27642_1' THEN FF_27642_152868
WHEN 'FF_27643_1' THEN FF_27643_152877
WHEN 'FF_27644_1' THEN FF_27644_152885
WHEN 'FF_27645_1' THEN FF_27645_152893 END) as LastName,
(Case(FN) WHEN 'FirstName' THEN Email
WHEN 'FF_27637_1' THEN FF_27637_152807
WHEN 'FF_27742_1' THEN FF_27742_153578
WHEN 'FF_27638_1' THEN FF_27638_152815
WHEN 'FF_27639_1' THEN FF_27639_152818
WHEN 'FF_27640_1' THEN FF_27640_152853
WHEN 'FF_27641_1' THEN FF_27641_152861
WHEN 'FF_27642_1' THEN FF_27642_152869
WHEN 'FF_27643_1' THEN FF_27643_152878
WHEN 'FF_27644_1' THEN FF_27644_152886
WHEN 'FF_27645_1' THEN FF_27645_152894 END) as Email,
(Case(FN) WHEN 'FirstName' THEN HotelCheckOut
WHEN 'FF_27637_1' THEN FF_27637_152821
WHEN 'FF_27742_1' THEN FF_27645_152896
WHEN 'FF_27638_1' THEN FF_27742_153580
WHEN 'FF_27639_1' THEN FF_27638_152847
WHEN 'FF_27640_1' THEN FF_27639_152842
WHEN 'FF_27641_1' THEN FF_27640_152855
WHEN 'FF_27642_1' THEN FF_27641_152863
WHEN 'FF_27643_1' THEN FF_27642_152871
WHEN 'FF_27644_1' THEN FF_27643_152880
WHEN 'FF_27645_1' THEN FF_27644_152888 END) as HotelChoice,
(Case(FN) WHEN 'FirstName' THEN HotelCheckIn
WHEN 'FF_27637_1' THEN FF_27637_152813
WHEN 'FF_27742_1' THEN FF_27742_153579
WHEN 'FF_27638_1' THEN FF_27638_152816
WHEN 'FF_27639_1' THEN FF_27639_152819
WHEN 'FF_27640_1' THEN FF_27640_152854
WHEN 'FF_27641_1' THEN FF_27641_152862
WHEN 'FF_27642_1' THEN FF_27642_152870
WHEN 'FF_27643_1' THEN FF_27643_152879
WHEN 'FF_27644_1' THEN FF_27644_152887
WHEN 'FF_27645_1' THEN FF_27645_152895 END) as HotelCheckIn,
(Case(FN) WHEN 'FirstName' THEN HotelCheckOut
WHEN 'FF_27637_1' THEN FF_27637_152821
WHEN 'FF_27742_1' THEN FF_27742_153580
WHEN 'FF_27638_1' THEN FF_27638_152847
WHEN 'FF_27639_1' THEN FF_27639_152842
WHEN 'FF_27640_1' THEN FF_27640_152855
WHEN 'FF_27641_1' THEN FF_27641_152863
WHEN 'FF_27642_1' THEN FF_27642_152871
WHEN 'FF_27643_1' THEN FF_27643_152880
WHEN 'FF_27644_1' THEN FF_27644_152888
WHEN 'FF_27645_1' THEN FF_27645_152896 END) as HotelCheckOut,
(Case(FN) WHEN 'FirstName' THEN HotelRoomPreference
WHEN 'FF_27637_1' THEN FF_27637_152823
WHEN 'FF_27742_1' THEN FF_27742_153582
WHEN 'FF_27638_1' THEN FF_27638_152849
WHEN 'FF_27639_1' THEN FF_27639_152844
WHEN 'FF_27640_1' THEN FF_27640_152857
WHEN 'FF_27641_1' THEN FF_27641_152865
WHEN 'FF_27642_1' THEN FF_27642_152874
WHEN 'FF_27643_1' THEN FF_27643_152882
WHEN 'FF_27644_1' THEN FF_27644_152890
WHEN 'FF_27645_1' THEN FF_27645_152898 END) as RoomType,
(Case(FN) WHEN 'FirstName' THEN HotelSmoking
WHEN 'FF_27637_1' THEN FF_27637_152824
WHEN 'FF_27742_1' THEN FF_27742_153583
WHEN 'FF_27638_1' THEN FF_27638_152850
WHEN 'FF_27639_1' THEN FF_27639_152845
WHEN 'FF_27640_1' THEN FF_27640_152858
WHEN 'FF_27641_1' THEN FF_27641_152866
WHEN 'FF_27642_1' THEN FF_27642_152875
WHEN 'FF_27643_1' THEN FF_27643_152883
WHEN 'FF_27644_1' THEN FF_27644_152891
WHEN 'FF_27645_1' THEN FF_27645_152899 END) as Smoking,
(Case(FN) WHEN 'FirstName' THEN NULL
WHEN 'FF_27637_1' THEN FF_27637_154245
WHEN 'FF_27742_1' THEN FF_27742_154247
WHEN 'FF_27638_1' THEN FF_27638_154249
WHEN 'FF_27639_1' THEN FF_27639_154251
WHEN 'FF_27640_1' THEN FF_27640_154253
WHEN 'FF_27641_1' THEN FF_27641_154255
WHEN 'FF_27642_1' THEN FF_27642_154257
WHEN 'FF_27643_1' THEN FF_27643_154259
WHEN 'FF_27644_1' THEN FF_27644_154261
WHEN 'FF_27645_1' THEN FF_27645_154263 END) as SharingWith,
FROM Event
UNPIVOT (FirstNames for FN in (FirstName, FF_27637_1, FF_27742_1, FF_27638_1, FF_27639_1, FF_27640_1, FF_27641_1, FF_27642_1, FF_27643_1, FF_27644_1, FF_27645_1)) as FirstNames
WHERE Audience = 'Primary' and Submitted = 1 and Cancelled = 0 and Type = 'Production' ORDER BY Confirmation
附带说明,如果我尝试将其作为视图运行,则会收到不支持 UNPIVOT 的错误消息。如果我将相同的内容放在视图中并使其成为派生表 –SELECT * FROM (...query...)
)–,我会在 Studio Manager 中收到相同的警告,但它返回的结果很好。很奇怪。
再次感谢您的所有回答。
【讨论】:
以上是关于使用多个 UNION ALL 构建视图:有更好的方法吗?的主要内容,如果未能解决你的问题,请参考以下文章
在不使用 Union ALL 的情况下添加多个 select 语句