使用多个UNION ALL来构建视图:有更好的方法吗?

时间:2010-02-10 21:04:58

标签: sql sql-server sql-server-2005 query-optimization

这个问题存在一些限制;我没有能力从根本上改变任何数据库结构。

这里的挑战是我在数据库中有行,其中包含真正应该在其自己行上的信息。列结构的一个简化示例:

[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

由于我使用它来生成视图,因此我必须为每组guest列引用相同的表,并使用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")

3 个答案:

答案 0 :(得分:2)

取决于您要对视图执行的操作。
视图查询必须在您对其执行的任何操作之前执行。因此,如果您正在处理仅处理视图返回的结果的10%的事情,则会浪费处理。执行的操作可能与视图中已存在的逻辑同时完成。

可以在必要时添加索引吗?那会有所帮助......

物化视图,称为indexed view in SQL Server terminology,是一种可行的可能性,但众所周知是不适应的。

答案 1 :(得分:2)

您是否考虑过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

在一个选择中使用多个unpiv是很棘手的;我从这些家伙那里得到了上述观点:

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

答案 2 :(得分:1)

以下是我实施上述答案的结果。希望这对接下来遇到类似挑战的任何人都有帮助。

从概念上讲,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中收到相同的警告,但它返回结果就好了。怪异。

再次感谢您的所有答案。