私はまだクエリの最適化に慣れていないので、カーソルを使用してテーブル内の各行を移動し、次の操作を実行するストアドプロシージャを持っています。
このカーソルをWHILEループに変換しようとしましたが、パフォーマンスが低下しました。これをSET BASED
アプローチではなくProcedural Based
アプローチに変換する手助けが必要です
したがって、カーソルはこのロジックを実行します。
-- READ Current Row into Cursor Variables
FETCH NEXT FROM crAssetIgnitionOnOff INTO
@current_iVehicleMonitoringID
, @current_iAssetID
, @current_dtUTCDateTime
, @current_sptGeoLocationPoint
, @current_fLatitude
, @current_fLongitude
, @current_fAngle
, @current_fSpeedKPH
, @current_sIgnitionStatus
, @current_eEventCode
, @current_sEventCode
IF(@current_iAssetID = @prev_iAssetID)
BEGIN
---- Calculate Time Difference from previous Point
DECLARE @diffInSeconds INT
SET @diffInSeconds = DATEDIFF(SECOND, @prev_dtUTCDateTime, @current_dtUTCDateTime)
DECLARE @diffInMinutes INT
SET @diffInMinutes = @diffInSeconds / 60
-- Calcualte the Distance from previous position
DECLARE @tempDistance FLOAT;
SELECT @tempDistance = @current_sptGeoLocaitonPoint.STDistance(@prev_sptGeoLocaitonPoint);
-- Check if distance travelled less than 5, AND Time difference between points greater than user selected Idle Minutes (@iIdleMinutes) AND prev ignition status = On
IF(@diffInSeconds > @iIdleMinutes AND @tempDistance < 5 AND @prev_sIgnitionStatus = 'On')
BEGIN
DECLARE @sTime VARCHAR(30)
SELECT @sTime = dbo.xPT_ConvertTimeToDDHHMMSS(@diffInSeconds,'s')
INSERT INTO @tblExcessiveIdleTime(
AssetID,
PreviousDate,
CurrentDate,
TimeString,
TimeInSeconds
)
VALUES
(
@current_iAssetId,
@prev_dtUTCDateTime,
@current_dtUTCDateTime,
@sTime,
@diffInSeconds
)
END
END
-- Set Previous Values End of Loop
SET @prev_iVehicleMonitoringID = @current_iVehicleMonitoringID
SET @prev_iAssetID = @current_iAssetID
SET @prev_dtUTCDateTime = @current_dtUTCDateTime
SET @prev_sptGeoLocationPoint = @current_sptGeoLocationPoint
SET @prev_fLatitude = @current_fLatitude
SET @prev_fLongitude = @current_fLongitude
SET @prev_fAngle = @current_fAngle
SET @prev_fSpeedKPH = @current_fSpeedKPH
SET @prev_sIgnitionStatus = @current_sIgnitionStatus
SET @prev_eEventCode = @current_eEventCode
SET @prev_sEventCode = @current_sEventCode
END
これで、実行に17分かかる場合があるため、WHILEループに変換してみました ---(http://www.sqlbook.com/SQL/Avoiding-using-SQL-Cursors-20.aspx )
これは良いアイデアではありませんでした。論理読み取りの数でのパフォーマンスはカーソルの4倍でした。そして処理に時間がかかりました:
WHILE @RowCount <= @NumberRecords
BEGIN
-- Check for First Row
IF @RowCount = 1
BEGIN
-- Set First Row as Previous
SELECT @previous_iAssetID = iAssetID, @previous_sptGeoLocaitonPoint = sptGeoLocaitonPoint, @previous_dtUTCDateTime = dtUTCDateTime, @previous_sIgnitionStatus = sIgnitionStatus
FROM #tblVehicleMonitoringLog WHERE RowID = @RowCount
END
ELSE
BEGIN
/* Select current Row */
SELECT @current_iAssetID = iAssetID, @current_sptGeoLocaitonPoint = sptGeoLocaitonPoint, @current_dtUTCDateTime = dtUTCDateTime, @current_sIgnitionStatus = sIgnitionStatus
FROM #tblVehicleMonitoringLog WHERE RowID = @RowCount
/******** IMPLEMENT REPORT LOGIC **********/
IF(@current_iAssetID = @previous_iAssetID)
BEGIN
---- Calculate Time Difference from previous Point
DECLARE @diffInSeconds INT
SET @diffInSeconds = DATEDIFF(SECOND, @previous_dtUTCDateTime, @current_dtUTCDateTime)
DECLARE @diffInMinutes INT
SET @diffInMinutes = @diffInSeconds / 60
-- Calcualte the Distance from previous position
DECLARE @tempDistance FLOAT;
SELECT @tempDistance = @current_sptGeoLocaitonPoint.STDistance(@previous_sptGeoLocaitonPoint);
-- Check if distance travelled less than 5, AND Time difference between points greater than user selected Idle Minutes (@iIdleMinutes) AND prev ignition status = On
IF(@diffInSeconds > @iIdleMinutes AND @tempDistance < 5 AND @previous_sIgnitionStatus = 'On')
BEGIN
DECLARE @sTime VARCHAR(30)
SELECT @sTime = dbo.xPT_ConvertTimeToDDHHMMSS(@diffInSeconds,'s')
INSERT INTO @tblExcessiveIdleTime(
iAssetID,
dtIgnitionOn,
dtNextPeriodic,
sTime,
iTimeDurationInSeconds
)
VALUES
(
@current_iAssetId,
DATEADD(hour, @fGmtOffSet, @previous_dtUTCDateTime),
DATEADD(hour, @fGmtOffSet, @current_dtUTCDateTime),
@sTime,
@diffInSeconds
)
END
END
-- Set Previous Values End of Loop
SET @previous_iAssetID = @current_iAssetID;
SET @previous_sptGeoLocaitonPoint = @current_sptGeoLocaitonPoint;
SET @previous_dtUTCDateTime = @current_dtUTCDateTime;
SET @previous_sIgnitionStatus = @current_sIgnitionStatus;
END
-- increment Row Number
SET @RowCount = @RowCount + 1
END -- END OF WHILE LOOP
もう一度オンラインで調べたところ、2つの行の時間差を計算できることがわかりました。 ( https://stackoverflow.com/questions/2357515/calculate-time-difference-between-two-rows )
これがRAWデータの外観です。
各行間の時間差と距離を計算する必要がありますWHERE
これは私が思いついたクエリです:
WITH rows AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY dtUTCDateTime) AS rn
FROM VehicleMonitoringLog
Where dtUTCDateTime > GetDate() - 1
--Order by iAssetId, dtUTCDateTime
)
SELECT mc.iVehicleMonitoringId as CurrentID, mp.iVehicleMonitoringId as PreviousID,
mc.iAssetId as CurrentAsset, mp.iAssetId As PreviousAsset, mc.dtUTCDateTime as CurrentTime, mp.dtUTCDateTime as PreviousTime,
DATEDIFF(second, mc.dtUTCDateTime, mp.dtUTCDateTime) AS DateDiffSeconds
FROM rows mc
JOIN rows mp
ON mc.rn = mp.rn - 1
[〜#〜]編集[〜#〜]
私のクエリは現在機能しています-これでパフォーマンスの問題が発生した場合はお知らせください:
SELECT dt.CurrentAsset,
dt.Distance,
dt.DateDiffSeconds,
dt.CurrentIgnition,
dt.PreviousIgnition,
ta.sReference,
ta.sCategoryName,
ta.sSiteName,
dbo.xPT_ConvertTimeToDDHHMMSS(DateDiffSeconds,'s')
FROM (
SELECT iVehicleMonitoringId AS CurrentID,
LEAD(iVehicleMonitoringId, 1) OVER (PARTITION BY iAssetID ORDER BY dtUTCDateTime) AS PreviousID,
iAssetId AS CurrentAsset,
LEAD(iAssetId, 1) OVER (PARTITION BY iAssetID ORDER BY dtUTCDateTime) AS PreviousAsset,
sDigitalInputValue AS CurrentIgnition,
LEAD(sDigitalInputValue, 1) OVER (PARTITION BY iAssetID ORDER BY dtUTCDateTime) AS PreviousIgnition,
dtUTCDateTime AS CurrentTime,
LEAD(dtUTCDateTime, 1) OVER (PARTITION BY iAssetID ORDER BY dtUTCDateTime) AS PreviousTime,
DATEDIFF(second, dtUTCDateTime, LEAD(dtUTCDateTime, 1) OVER (PARTITION BY iAssetID ORDER BY dtUTCDateTime)) AS DateDiffSeconds,
sptGeoLocaitonPoint.STDistance(LEAD(sptGeoLocaitonPoint, 1) OVER (PARTITION BY iAssetID ORDER BY dtUTCDateTime)) AS Distance
FROM VehicleMonitoringLog
WHERE dtUTCDateTime > @utcStartDate AND dtUTCDateTime < @utcEndDate
) AS dt
Inner join #tblAssets ta on ta.iAssetID = dt.CurrentAsset
WHERE CurrentIgnition = '10000000' AND Distance < 5 AND DateDiffSeconds > @iIdleMinutes
ウィンドウ関数を使用したCTEベースのアプローチは、非常に良い出発点です。使用できる別のさらに適切なウィンドウ関数があります:LAG()。
方法は次のとおりです。
_SELECT iVehicleMonitoringId AS CurrentID,
LAG(iVehicleMonitoringId, 1) OVER (ORDER BY dtUTCDateTime) AS PreviousID,
iAssetId AS CurrentAsset,
LAG(iAssetId, 1) OVER (ORDER BY dtUTCDateTime) AS PreviousAsset,
dtUTCDateTime AS CurrentTime,
LAG(dtUTCDateTime, 1) OVER (ORDER BY dtUTCDateTime) AS PreviousTime,
DATEDIFF(second,
dtUTCDateTime,
LAG(dtUTCDateTime, 1) OVER (ORDER BY dtUTCDateTime)
) AS DateDiffSeconds
FROM VehicleMonitoringLog
WHERE dtUTCDateTime > DATEADD(day, -1, SYSDATETIME());
_
基本的に、LAG(column, n) OVER (ORDER BY x)
はcolumn
の値を返し、n
行を戻して(n = 1は前の行を返します)、x
の順に並べます。
CTEソリューションはVehicleMonitoringLog
を2回スキャンしてから、2つのストリームを結合します。このクエリは単一のスキャンのみを実行するため、はるかに効率的です。 LAG()
関数(およびその従属関数LEAD()
)は、SQL Server 2012以降で使用できます。
パーティショニング
何らかのタイプのパーティション用語(iAssetID、たぶん?)を忘れたようです。分割条件は、2台の車両が同時に往路と復路の場合に、異なる車両間でデータポイントを分離するために使用されます。 OVER()句をOVER (ORDER BY dtUTCDateTime)
からOVER (PARTITION BY iAssetID ORDER BY dtUTCDateTime)
に変更して、このパーティション項をクエリに追加します。
インデックス作成
このソリューションを本当に機能させるために、VehicleMonitoringLog
に次のインデックスを作成します。
_CREATE INDEX... (iAssetID, dtUTCDateTime); --- if you're using PARTITION BY
_
..または
_CREATE INDEX... (dtUTCDateTime); --- without PARTITION BY
_
SQL Server 2012では、LEAD
とLAG
など、欠落しているウィンドウ化された集計関数がいくつか導入されました。 SQL Serverのウィンドウ関数:パート2-フレーム または Microsoft SQL Server 2012のウィンドウ関数の使用方法
LAG
を使用すると、カーソルを使用せずに前の行のデータにアクセスできます。
SELECT ...
FROM
(
SELECT ...
DATEDIFF(second, dtUTCDateTime,
LAG(dtUTCDateTime)
OVER (ORDER BY dtUTCDateTime) AS DateDiffSeconds
FROM VehicleMonitoringLog
) AS dt
WHERE DateDiffSeconds > 180
Dnoethが言及しているように、ラグを使用して以前の値を確認できるはずです。
SELECT ...
, DATEDIFF(second, dtUTCDateTime, prev_dtUTCDateTime) AS DateDiffSeconds
FROM (
SELECT ...
, LAG(dtUTCDateTime) OVER (ORDER BY dtUTCDateTime)
AS prev_dtUTCDateTime
FROM VehicleMonitoringLog
Where dtUTCDateTime > GetDate() - 1
) AS x;