Получить родственные строки в SQL - PullRequest
1 голос
/ 30 сентября 2011

Мне нужно получить две строки одного и того же ряда в запросе.

SELECT pkUserId, name
FROM tblUsers
ORDER BY CreateDate

Дает результат:

10 User1
18 User3
25 User4
79 User8
12 User2

Я хочу запрос, который дает мне user3 и user8, если я предоставлю userId 25

10 User1
18 User3 --> Output
25 User4 <-- Input
79 User8 --> Output
12 User2

Если вы считаете, что можно получить это одним запросом (без объединения), используя ROW_NUMBER(), но я не уверен, как его создать.

Ответы [ 4 ]

4 голосов
/ 01 октября 2011

Редактировать: я добавил четыре замечания для второго решения.

Два решения:

(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

Чтобы избежать ошибокрезультаты:

  1. Я использовал подсказку запроса MAXDOP 1 для предотвращения "параллелизма".
  2. Я создал уникальный индекс, который включает все необходимые поля, начиная с полей, используемых в GROUP BY и ORDER BY.
  3. Я принудительно INDEX=IX_TestData_CreateDate_PKUserID index.

Или самое простое решение - форсировать план выполнения, используя OPTION (USE PLAN N'<?xml...><ShowPlanXML...').

1 голос
/ 30 сентября 2011

Вот полное решение, включая виртуальные объявления, так что любой может проверить решение:

declare @tblUsers table (pkUserId int, name varchar(20), createdate datetime)

insert into @tblUsers values 
(10, 'User1','2011-01-01'),
(18, 'User3','2011-01-02'),
(25, 'User4','2011-01-03'),
(79, 'User8','2011-01-04'),
(12, 'User2','2011-01-05')

;with sel as(
SELECT pkUserId, name, ROW_NUMBER() over (order by createdate) rn
FROM @tblUsers
) 
select sel.pkUserId, sel.name 
from sel, 
(
select rn from sel where pkUserId = 25
) item
where sel.rn in (item.rn -1, item.rn+1)
1 голос
/ 30 сентября 2011

Попробуйте это:

WITH CTE
AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY CreateDate) [rn], pkUserId, name
    FROM tblUsers
)

SELECT *
FROM CTE c
WHERE c.rn =
(
    SELECT c2.rn
    FROM CTE c2
    WHERE c2.pkUserId = 25
) - 1
OR
c.rn =
(
    SELECT c3.rn
    FROM CTE c3
    WHERE c3.pkUserId = 25
) + 1
0 голосов
/ 08 апреля 2014

Используйте подзапросы в следующем порядке:

  1. Запрос для строки ввода
  2. Запрос для внешнего ключа
  3. Запрос для родных элементов

Вот пример:

Поиск текста в других ответах на вопросы, на которые я ответил

...