2

私は5つのテーブルを持っています(質問のために考案され簡略化されています):

Device
---------------
DeviceID - INT IDENTITY - PRIMARY KEY
DeviceGUID - UNIQUEIDENTIFIER - UNIQUE CLUSTERED (used for lookups)
OtherInfo


Device_State
---------------
DeviceStateID - INT IDENTITY - PRIMARY KEY
DeviceID - INT - FOREIGN KEY - UNIQUE CLUSTERED (used for lookups)
Pump1 - TINYINT
Pump2 - TINYINT
Pump3 - TINYINT
Pump4 - TINYINT
Pump5 - TINYINT
Heater1 - TINYINT
Heater2 - TINYINT
Lights - BIT
TimeStamp - SMALLDATETIME

Device_State_History
---------------
DeviceStateHistoryID - INT IDENTITY - PRIMARY KEY
DeviceStateID - INT - FOREIGN KEY - NONCLUSTERED INDEX (used for lookups)
Pump1 - TINYINT
Pump2 - TINYINT
Pump3 - TINYINT
Pump4 - TINYINT
Pump5 - TINYINT
Heater1 - TINYINT
Heater2 - TINYINT
Lights - BIT
TimeStamp - SMALLDATETIME


Peripheral_State
----------------
PeripheralStateID - INT IDENTITY - PRIMARY KEY
DeviceID - INT - FOREIGN KEY - UNIQUE CLUSTERED (used for lookups)
Pump1 - TINYINT
Pump2 - TINYINT
PH - SMALLINT
ORP - SMALLINT
ELECTRODEID - TINYINT
ELECTRODEPOLARITY - TINYINT
TimeStamp - SMALLDATETIME


Peripheral_State_History
----------------
PeripheralStateHistoryID - INT IDENTITY - PRIMARY KEY
PeripheralStateID - INT - FOREIGN KEY - NONCLUSTERED INDEX (used for lookups)
Pump1 - TINYINT
Pump2 - TINYINT
PH - SMALLINT
ORP - SMALLINT
ELECTRODEID - TINYINT
ELECTRODEPOLARITY - TINYINT
TimeStamp - SMALLDATETIME

状況は次のとおりです。

Device_State_Historyテーブルに最大380万のレコードがあり、Peripheral_State_Historyテーブルに最大100Kのレコードがあります。

特定の日時の間に特定のDeviceGUIDのPeripheral_State_historyとDevice_State_Historyのすべてを選択するクエリを作成したいと思います。次のようなレコードを取得したいと思います。

Select 
 Peripheral_State_History.PH,
 Peripheral_State_History.ORP,
 ( 
 if(Device_State_History.Pump1 == 1 ||
    Device_State_History.Pump2 == 1 ||
    Device_State_History.Pump3 == 1 ||
    Device_State_History.Pump4 == 1 || 
    Device_State_History.Pump5 == 1
    ) 
    { 1 }
    else 
    { 0 }
  ) AS PumpsOn, 
    TimeStamp 
FROM CombinedData

また、Device_State_HistoryとPeripheral_State_Historyの間のタイムスタンプは常に同じであるとは限りません。これは、Device_State_Historyテーブル内の1つのデバイスのレコードのタイムスタンプが次のようになる可能性があることを意味します。

Mar-1-2013 12:31
Mar-1-2013 12:33
Mar-1-2013 12:36
Mar-1-2013 12:38
Mar-1-2013 12:41

そして、Peripheral_State_Historyテーブルにレコードがあります。

Mar-1-2013 12:29
Mar-1-2013 12:33
Mar-1-2013 12:34
Mar-1-2013 12:38
Mar-1-2013 12:39
Mar-1-2013 12:41

ご覧のとおり、タイムスタンプは常にオーバーレイされるとは限りません。

私はこれを行うためにストアドプロシージャを作成することを検討してきましたが、これは私のSQLの能力を超えており、ガイダンスと例は本当にありがたいです。ありがとうございました。

編集:

以下は、Peripheral_State_Historyテーブルからデータを取得するために作成したストアドプロシージャです。Device_State_Historyテーブルから必要なPumpデータは含まれていません。

注意すべき点がいくつかあります。複数のレコードを持つタイムスタンプがいくつかあるため、タイムスタンプごとに1つのレコードしか取得していません。このストアドプロシージャは、状態テーブルから現在のレコードを取得し、履歴テーブルからすべてのレコードを取得します。

USE my_db
GO
IF EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND name = 'SP_Select_Stuff') 
DROP PROCEDURE 'SP_Select_Stuff') 
GO
CREATE PROCEDURE 'SP_Select_Stuff') 
( 
     @DeviceGUID uniqueidentifier
) 
AS
DECLARE @RetCode INT
;WITH combined AS
(
    SELECT 
    ROW_NUMBER() OVER 
   (PARTITION BY [TimeStamp] ORDER BY [TimeStamp]) as num, ORP, PH, [TimeStamp] 
    FROM Peripheral_State
    INNER JOIN Device
        ON Peripheral_State.DeviceID = Device.DeviceID
        WHERE Device.DeviceGUID = @DeviceGUID 
    UNION ALL
    SELECT
    ROW_NUMBER() OVER 
   (PARTITION BY [TimeStamp] ORDER BY [TimeStamp]) as num, ORP, PH, [TimeStamp]
    FROM Peripheral_State_History
    INNER JOIN Device
        ON Peripheral_State.DeviceID = Device.DeviceID
        WHERE Device.DeviceGUID = @DeviceGUID 

)
SELECT ORP, PH, [TimeStamp]
FROM combined
WHERE ORP > 0
AND PH > 0
AND combined.num = 1
ORDER BY [TimeStamp] ASC

IF @@ERROR <> 0
   BEGIN
        SET @RetCode = 5
        RETURN @RetCode
   END
ELSE
    BEGIN
        SET @RetCode = 0
        RETURN @RetCode
    END
GO

編集:

これが私が思いついたものです。

USE my_db
--"combined" gets all of the data. 
--A row from the peripheral history will look like: ORP-Value, PH-Value, Current-Value, NULL, TimeStamp.
--A row from the device history will look like: NULL, NULL, NULL, PumpsOn-Value, TimeStamp
-- I also get a num for filtering 1 TimeStamp per minute. My TimeStamps are accurate to the minute, so if one minute has 30 records, I will use the first record.
;WITH combined AS
(   
    SELECT  
    ROW_NUMBER() OVER 
   (PARTITION BY ph.[TimeStamp] ORDER BY ph.[TimeStamp]) as num, ph.ORP, ph.PH, ph.[Current], NULL as PumpsOn, ph.[TimeStamp] 
    FROM Device as d

    LEFT JOIN Peripheral_State as ps
    ON ps.DeviceID = d.DeviceID

        LEFT JOIN Peripheral_State_History as ph
        ON ph.DeviceID = d.DeviceID

        WHERE d.DeviceGUID = @DeviceGUID
        AND ph.TimeStamp BETWEEN '2013-03-02 00:00:00' AND GETDATE() 

    UNION ALL   

    SELECT
    ROW_NUMBER() OVER 
   (PARTITION BY dh.[TimeStamp] ORDER BY dh.[TimeStamp]) as num, NULL as ORP, NULL as PH, NULL as [Current], IsNull(COALESCE(dh.Pump1, dh.Pump2, dh.Pump3, dh.Pump4, dh.Pump5),0) as PumpsOn, dh.[TimeStamp]
    FROM Device as d

    LEFT JOIN Device_State as ds
    ON ds.DeviceID = d.DeviceID

       LEFT JOIN Device_State_History as dh
       ON dh.DeviceID = d.DeviceID

        WHERE d.DeviceGUID = @DeviceGUID
        AND ph.TimeStamp BETWEEN '2013-03-02 00:00:00' AND GETDATE()            
),
--"FilteredAndAddID" gets a record set that has an ID added to the row. This is required for the 3rd step.
--It also filters the data so that records with 0 orp/ph are omitted.
FilteredAndAddID AS
(
    SELECT 
        ROW_NUMBER() OVER (ORDER BY [TimeStamp]) as ID,
        ORP,
        PH,
        [Current],
        PumpsOn,
        [TimeStamp]
    FROM combined
    WHERE 
    (ORP is NULL OR ORP > 0) AND (PH is NULL OR PH > 0)
    AND combined.num = 1
),
--"filled" is used to fill the "FilteredAddID" record set NULLS with the previous row that has a value instead of NULL.
filled AS
(
    SELECT  Curr.ID,
            ISNULL(Curr.ORP, (SELECT TOP 1 ORP FROM FilteredAddID WHERE ID < Curr.ID AND ORP IS NOT NULL ORDER BY ID)) ORP,
            ISNULL(Curr.PH, (SELECT TOP 1 PH FROM FilteredAddID WHERE ID < Curr.ID AND PH IS NOT NULL ORDER BY ID)) PH,
            ISNULL(Curr.[Current], (SELECT TOP 1 [Current] FROM FilteredAddID WHERE ID < Curr.ID AND [Current] IS NOT NULL ORDER BY ID)) [Current],
            ISNULL(Curr.PumpsOn, (SELECT TOP 1 PumpsOn FROM FilteredAddID WHERE ID < Curr.ID AND PumpsOn IS NOT NULL ORDER BY ID)) PumpsOn, 
            Curr.[TimeStamp]
    FROM    FilteredAddID Curr
)
SELECT ID, ORP, PH, [Current], PumpsOn, [TimeStamp]
FROM filled
ORDER BY [TimeStamp] ASC

最初の2つのクエリ「combined」と「FilterAndAddID」はミリ秒単位で実行されますが、「filled」クエリは約1700レコードのnullを埋めるのに約6分かかります。

これをスピードアップする方法はありますか?

私はこれに正しい方法でアプローチしていますか?

理想的には、これを数秒で実行する必要があります。

4

1 に答える 1

1

このクエリをどのくらい実行しますか?

;with combined as
(
select
   psh.PH, psh.ORP, psh.TimeStamp, 
   max(dsh.TimeStamp) as Device_Stamp
from Peripheral_State_History psh
  join Peripheral_State ps on ps.PeripheralStateID = psh.PeripheralStateID 
  join Device d on d.DeviceID = ps.DeviceID
  left join Device_State ds on ds.DeviceID = d.DeviceID
  left join Device_State_History dsh on dsh.DeviceStateID = ds.DeviceStateID 
    and dsh.TimeStamp <= psh.TimeStamp
where d.DeviceGUID = @DeviceGUID
  and psh.TimeStamp between @date1 and @date2
group by psh.PH, psh.ORP, psh.TimeStamp
)
select 
  c.PH, c.ORP, c.TimeStamp, 
  case 
    when dsh.Pump1 = 1 and dsh.Pump2 = 1 and dsh.Pump3 = 1 
      and dsh.Pump4 = 1 and dsh.Pump5 = 1
    then 1 else 0 end as PumpsOn
from combined c
  left join Device_State_History dsh on dsh.TimeStamp = c.Device_Stamp
  left join Device_State ds on ds.DeviceStateID = dsh.DeviceStateID
  left join Device d on ds.DeviceID = d.DeviceID
where d.DeviceGUID = @DeviceGUID

編集

Device_State_Historyの同じタイムスタンプを持つ複数のレコードの問題に対処します。

より大きなDeviceStateHistoryIDが常にタイムスタンプ以上である場合は、このクエリを試すことができます。最大DeviceStateHistoryIDを持つ1つのレコードを選択します。ここで、レコードのタイムスタンプはDevice_State_Historyと同じです。

;with combined as
(
select
   psh.PH, psh.ORP, psh.TimeStamp, 
   max(dsh.DeviceStateHistoryID) as DeviceStateHistoryID
from Peripheral_State_History psh
  join Peripheral_State ps on ps.PeripheralStateID = psh.PeripheralStateID 
  join Device d on d.DeviceID = ps.DeviceID
  left join Device_State ds on ds.DeviceID = d.DeviceID
  left join Device_State_History dsh on dsh.DeviceStateID = ds.DeviceStateID 
    and dsh.TimeStamp <= psh.TimeStamp
where d.DeviceGUID = @DeviceGUID
  and psh.TimeStamp between @date1 and @date2
group by psh.PH, psh.ORP, psh.TimeStamp
)
select 
  c.PH, c.ORP, c.TimeStamp, 
  case 
    when dsh.Pump1 = 1 and dsh.Pump2 = 1 and dsh.Pump3 = 1 
      and dsh.Pump4 = 1 and dsh.Pump5 = 1
    then 1 else 0 end as PumpsOn
from combined c
  left join Device_State_History dsh on dsh.DeviceStateHistoryID = c.DeviceStateHistoryID
  left join Device_State ds on ds.DeviceStateID = dsh.DeviceStateID
  left join Device d on ds.DeviceID = d.DeviceID
where d.DeviceGUID = @DeviceGUID
于 2013-03-07T20:51:19.813 に答える