Редактировать: я добавил четыре замечания для второго решения.
Два решения:
(1) Первое решение основано на функциях MAX, MIN:
CREATE TABLE dbo.TestData
(
PKUserID INT PRIMARY KEY
,Name VARCHAR(25) NOT NULL
,CreateDate DATETIME NOT NULL
);
CREATE UNIQUE INDEX IX_TestData_CreateDate_PKUserID
ON dbo.TestData(CreateDate, PKUserID);
INSERT dbo.TestData
SELECT 10,'User1','2011-01-01T00:00:00'
UNION ALL
SELECT 18,'User3','2011-01-01T00:10:00'
UNION ALL
SELECT 25,'User4','2011-01-01T00:20:00'
UNION ALL
SELECT 79,'User8','2011-01-01T00:30:00'
UNION ALL
SELECT 12,'User2','2011-01-01T00:40:00';
DECLARE @UserID INT;
SELECT @UserID = 25;
DECLARE @UserCreateDate DATETIME;
SELECT @UserCreateDate = a.CreateDate
FROM dbo.TestData a
WHERE a.PKUserID = @UserID;
SELECT *
FROM
(
SELECT TOP 1 a.PKUserID
FROM dbo.TestData a
WHERE a.CreateDate < @UserCreateDate
ORDER BY a.CreateDate DESC, a.PKUserID DESC
) a
UNION ALL
SELECT *
FROM
(
SELECT TOP 1 a.PKUserID
FROM dbo.TestData a
WHERE @UserCreateDate < a.CreateDate
ORDER BY a.CreateDate ASC, a.PKUserID ASC
) b
DROP TABLE dbo.TestData;
Результаты ( 10 логических чтений ):
PKUserID
-----------
18
79
(2) Второе решение каким-то образом основано на методе своеобразного обновления , но не подразумевает какого-либоUPDATE
, это просто SELECT
:
CREATE TABLE dbo.TestData
(
PKUserID INT PRIMARY KEY
,Name VARCHAR(25) NOT NULL
,CreateDate DATETIME NOT NULL
);
CREATE UNIQUE INDEX IX_TestData_CreateDate_PKUserID
ON dbo.TestData(CreateDate, PKUserID);
INSERT dbo.TestData
SELECT 10,'User1','2011-01-01T00:00:00'
UNION ALL
SELECT 18,'User3','2011-01-01T00:10:00'
UNION ALL
SELECT 25,'User4','2011-01-01T00:20:00'
UNION ALL
SELECT 79,'User8','2011-01-01T00:30:00'
UNION ALL
SELECT 12,'User2','2011-01-01T00:40:00';
DECLARE @UserID INT;
SELECT @UserID = 25;
DECLARE @PreviousID INT
,@NextID INT
,@IsFound BIT
,@CountAfter INT;
SELECT @IsFound = 0
,@CountAfter = 0;
SELECT @IsFound = CASE WHEN a.PKUserID = @UserID THEN 1 ELSE @IsFound END
,@PreviousID = CASE WHEN @IsFound = 0 THEN a.PKUserID ELSE @PreviousID END
,@CountAfter = CASE WHEN @IsFound = 1 THEN @CountAfter + 1 ELSE 0 END
,@NextID = CASE WHEN @CountAfter = 2 THEN a.PKUserID ELSE @NextID END
FROM dbo.TestData a WITH(INDEX=IX_TestData_CreateDate_PKUserID)
GROUP BY a.CreateDate, a.PKUserID
ORDER BY a.CreateDate ASC, a.PKUserID ASC
OPTION (MAXDOP 1);
SELECT @UserID UserID, @IsFound IsFound, @PreviousID [Prev], @NextID [Next]
DROP TABLE dbo.TestData;
Результаты ( 2 логических чтения ):
UserID IsFound Prev Next
----------- ------- ----------- -----------
25 1 18 79
Чтобы избежать ошибокрезультаты:
- Я использовал подсказку запроса
MAXDOP 1
для предотвращения "параллелизма". - Я создал уникальный индекс, который включает все необходимые поля, начиная с полей, используемых в
GROUP BY
и ORDER BY
. - Я принудительно
INDEX=IX_TestData_CreateDate_PKUserID
index.
Или самое простое решение - форсировать план выполнения, используя OPTION (USE PLAN N'<?xml...><ShowPlanXML...')
.