web-dev-qa-db-ja.com

ビューから*を選択すると、非常に時間がかかります

5つのテーブルからデータを取得するビューを作成しました。そのうち3つは200k +行、340k +行、2.1mill +行を含みます。

ビューには、group by句、caseステートメント、およびCTEが含まれます。

単純な1000行の選択またはビューから*を選択すると、上位1000行を取得するだけで約15分という長い時間がかかります。

これは、Excelファイルにインポートしようとしたときにも発生し、50分以上かかります。それをスピードアップする方法について何か考えはありますか?

これがビューのスクリプトです

CREATE VIEW [iot].[Occupancy_View]
AS

WITH CTE1
AS
(
SELECT A.[TpID], A.[RoomCode], A.[RoomName], A.[BuildingCode],A.[SchoolCode],A.[SchoolCode1], b.[DeviceID], b.[LocalTime], b.[Occupancy], b.[Temperature],c.[semesterid],c.[academicyear]
FROM iot.Device A
inner JOIN iot.DeviceMessageHistory b ON  A.[DeviceID] = b.[DeviceID] 
LEFT OUTER JOIN iot.SEMESTER c ON b.[LocalTime] BETWEEN c.[semesterstartDATE] and c.[semesterendDATE]
),
CTE2
AS
(
SELECT
    ro.[RoomCode],
    ro.[RoomName],
    ro.[BuildingCode],
    ro.[SchoolCode],
    ro.[SchoolCode1],
    ro.[DeviceID],
    ro.[TpID],
    ro.[semesterid],
    ro.[academicyear],
    CAST(avg(ro.[Temperature]) AS decimal(10, 1)) AS [Temperature],
    CASE
        WHEN Ceiling(AVG(CAST(ro.Occupancy AS DECIMAL))) BETWEEN 0.1 AND 1.0
                  THEN '1'
        ELSE
            '0'
    END   AS [Occupancy],
    DATEADD(ss,-1,
    DATEADD(HOUR, DATEPART(HOUR, ro.[LocalTime]), DATEADD( MINUTE, 30 * CAST((DATEDIFF(MINUTE, '19000101', ro.[LocalTime]) / 30) % 2 AS INT),
    CAST(CAST(ro.[LocalTime] AS DATE) AS DATETIME))) 
    )
    AS [Time],
    (DATEPART(day,ro.[LocalTime])-1)/7 + 1 AS [WEEKNO],
    rbt.[booker] AS Booker,
    coalesce((ctt.[duration]/60),rbt.[duration]) as Duration,
    coalesce(ctt.[Day],rbt.[day]) AS [Day],
    coalesce(ctt.[CLASSSTARTDATE], rbt.[startdatetime]) AS [StartDateTime],
    coalesce(ctt.[CLASSENDDATE], rbt.[enddatetime]) AS [EndDateTime]


    FROM
    CTE1 ro
    LEFT OUTER JOIN
        [iot].[ClassTimeTable] ctt ON ro.RoomCode = ctt.ROOMID AND (ro.LocalTime BETWEEN ctt.CLASSSTARTDATE AND ctt.CLASSENDDATE)
    LEFT OUTER JOIN 
        [iot].[RoomBooking] rbt ON ro.RoomCode = rbt.facilityname and (ro.LocalTime BETWEEN rbt.startdatetime AND rbt.enddatetime)
                    GROUP BY
    ro.[RoomCode],
    ro.[RoomName],
    ro.[BuildingCode],
    ro.[SchoolCode],
    ro.[SchoolCode1],
    ro.[DeviceID],
    ro.[TpID],
    ro.[semesterid],
    ro.[academicyear],
    DATEADD(HOUR, DATEPART(HOUR, ro.[LocalTime]), DATEADD( MINUTE, 30 * CAST((DATEDIFF(MINUTE, '19000101', ro.[LocalTime]) / 30) % 2 AS INT),
    CAST(CAST(ro.[LocalTime] AS DATE) AS DATETIME))),
    (DATEPART(day,ro.[LocalTime])-1)/7 + 1,
    rbt.[booker],
    coalesce((ctt.[duration]/60),rbt.[duration]) ,
    coalesce(ctt.[Day],rbt.[day]),
    coalesce(ctt.[CLASSSTARTDATE], rbt.[startdatetime]) ,
    coalesce(ctt.[CLASSENDDATE], rbt.[enddatetime]) 

),
CTE3 
AS
(
    SELECT * 
    FROM CTE2
    CROSS APPLY
        (
        SELECT 
         CASE
                WHEN  (CTE2.Time BETWEEN CTE2.StartDateTime AND CTE2.EndDateTime) AND
                        (CAST(CTE2.[Occupancy]AS DECIMAL)) > 0.0
                        THEN 'Booked / Occupied'

                WHEN  (CTE2.Time BETWEEN CTE2.StartDateTime AND CTE2.EndDateTime) AND 
                        (CAST(CTE2.[Occupancy]AS DECIMAL)) <= 0.0
                        THEN 'Booked / Unoccupied'

                WHEN  (CTE2.Time NOT BETWEEN CTE2.StartDateTime AND CTE2.EndDateTime) OR ((CTE2.SEMESTERID IS NULL ) or (CTE2.Booker IS NULL)) AND 
                        (CAST(CTE2.[Occupancy]AS DECIMAL)) > 0.0
                        THEN 'Not Booked / Occupied'

                WHEN (CTE2.Time NOT BETWEEN CTE2.StartDateTime AND CTE2.EndDateTime) OR ((CTE2.SEMESTERID IS NULL) or (CTE2.Booker IS NULL)) AND
                        (CAST(CTE2.[Occupancy]AS DECIMAL)) <= 0.0
                        THEN 'Not Booked / Unoccupied'


              --(Add another conditon)
            ELSE 
            'ERROR'
            END AS ClassroomStatus
            ) AS CS


)
SELECT * FROM CTE3;
GO


次に実行計画を示します。 https://imgur.com/a/cFPSk3M

1

情報が限られている場合、最も簡単な解決策は、ストアドプロシージャを使用して、CTEを一時テーブルに分割することです。

CTEは具体化されていないため、ビューは実際には1つの大きな複雑なクエリです。

クエリが大きくなると、最適化が難しくなります。それを分割すると、オプティマイザは最適化にいくらかの余地を与えます。

2つの一時テーブルの例(テーブルと列を定義し、INSERT INTO)。

CREATE PROCEDURE [iot].[Occupancy_View]
AS

SELECT A.[TpID], A.[RoomCode], A.[RoomName], A.[BuildingCode],A.[SchoolCode],A.[SchoolCode1], b.[DeviceID], b.[LocalTime], b.[Occupancy], b.[Temperature],c.[semesterid],c.[academicyear]
INTO #TEMP1
FROM iot.Device A
inner JOIN iot.DeviceMessageHistory b ON  A.[DeviceID] = b.[DeviceID] 
LEFT OUTER JOIN iot.SEMESTER c ON b.[LocalTime] BETWEEN c.[semesterstartDATE] and c.[semesterendDATE]


SELECT
    ro.[RoomCode],
    ro.[RoomName],
    ro.[BuildingCode],
    ro.[SchoolCode],
    ro.[SchoolCode1],
    ro.[DeviceID],
    ro.[TpID],
    ro.[semesterid],
    ro.[academicyear],
    CAST(avg(ro.[Temperature]) AS decimal(10, 1)) AS [Temperature],
    CASE
        WHEN Ceiling(AVG(CAST(ro.Occupancy AS DECIMAL))) BETWEEN 0.1 AND 1.0
                  THEN '1'
        ELSE
            '0'
    END   AS [Occupancy],
    DATEADD(ss,-1,
    DATEADD(HOUR, DATEPART(HOUR, ro.[LocalTime]), DATEADD( MINUTE, 30 * CAST((DATEDIFF(MINUTE, '19000101', ro.[LocalTime]) / 30) % 2 AS INT),
    CAST(CAST(ro.[LocalTime] AS DATE) AS DATETIME))) 
    )
    AS [Time],
    (DATEPART(day,ro.[LocalTime])-1)/7 + 1 AS [WEEKNO],
    rbt.[booker] AS Booker,
    coalesce((ctt.[duration]/60),rbt.[duration]) as Duration,
    coalesce(ctt.[Day],rbt.[day]) AS [Day],
    coalesce(ctt.[CLASSSTARTDATE], rbt.[startdatetime]) AS [StartDateTime],
    coalesce(ctt.[CLASSENDDATE], rbt.[enddatetime]) AS [EndDateTime]

    INTO #TEMP2
    FROM
    #TEMP1 ro
    LEFT OUTER JOIN
        [iot].[ClassTimeTable] ctt ON ro.RoomCode = ctt.ROOMID AND (ro.LocalTime BETWEEN ctt.CLASSSTARTDATE AND ctt.CLASSENDDATE)
    LEFT OUTER JOIN 
        [iot].[RoomBooking] rbt ON ro.RoomCode = rbt.facilityname and (ro.LocalTime BETWEEN rbt.startdatetime AND rbt.enddatetime)
                    GROUP BY
    ro.[RoomCode],
    ro.[RoomName],
    ro.[BuildingCode],
    ro.[SchoolCode],
    ro.[SchoolCode1],
    ro.[DeviceID],
    ro.[TpID],
    ro.[semesterid],
    ro.[academicyear],
    DATEADD(HOUR, DATEPART(HOUR, ro.[LocalTime]), DATEADD( MINUTE, 30 * CAST((DATEDIFF(MINUTE, '19000101', ro.[LocalTime]) / 30) % 2 AS INT),
    CAST(CAST(ro.[LocalTime] AS DATE) AS DATETIME))),
    (DATEPART(day,ro.[LocalTime])-1)/7 + 1,
    rbt.[booker],
    coalesce((ctt.[duration]/60),rbt.[duration]) ,
    coalesce(ctt.[Day],rbt.[day]),
    coalesce(ctt.[CLASSSTARTDATE], rbt.[startdatetime]) ,
    coalesce(ctt.[CLASSENDDATE], rbt.[enddatetime]) 


    SELECT * 
    FROM #TEMP2 TEMP2
    CROSS APPLY
        (
        SELECT 
         CASE
                WHEN  (TEMP2.Time BETWEEN TEMP2.StartDateTime AND TEMP2.EndDateTime) AND
                        (CAST(TEMP2.[Occupancy]AS DECIMAL)) > 0.0
                        THEN 'Booked / Occupied'

                WHEN  (TEMP2.Time BETWEEN TEMP2.StartDateTime AND TEMP2.EndDateTime) AND 
                        (CAST(TEMP2.[Occupancy]AS DECIMAL)) <= 0.0
                        THEN 'Booked / Unoccupied'

                WHEN  (TEMP2.Time NOT BETWEEN TEMP2.StartDateTime AND TEMP2.EndDateTime) OR ((TEMP2.SEMESTERID IS NULL ) or (TEMP2.Booker IS NULL)) AND 
                        (CAST(TEMP2.[Occupancy]AS DECIMAL)) > 0.0
                        THEN 'Not Booked / Occupied'

                WHEN (TEMP2.Time NOT BETWEEN TEMP2.StartDateTime AND TEMP2.EndDateTime) OR ((TEMP2.SEMESTERID IS NULL) or (TEMP2.Booker IS NULL)) AND
                        (CAST(TEMP2.[Occupancy]AS DECIMAL)) <= 0.0
                        THEN 'Not Booked / Unoccupied'


              --(Add another conditon)
            ELSE 
            'ERROR'
            END AS ClassroomStatus
            ) AS CS
1
Randi Vertongen