Рекурсивный запрос SQL Server

Несколько дней назад я задал вопрос по SO относительно помощи по рекурсивному запросу.

Проблема этого вопроса заключалась в том, «Как получить историю назначений человека».

Теперь у меня проблема, похожая на эту, но она должна ответить на немного другой вопрос:

How to get an Appointment history?

Например, если встреча с ID = 5 была отложена один раз, и это была отсрочка другой встречи, как мне получить следующий результат?

AppointmentID         PrevAppointmentID
-----------------    ----------------------
1                     NULL
5                     1
12                    5

Спасибо за помощь

Обновление:

Эти скрипты помогут создать таблицу для ваших испытаний

CREATE TABLE [dbo].[Appointments](
    [AppointmentID] [int] IDENTITY(1,1) NOT NULL,
    [IssueID] [int] NOT NULL,
    [Location] [varchar](255) NOT NULL,
    [Description] [varchar](255) NOT NULL,
    [AppointmentDate] [datetime] NOT NULL,
    [AppointmentHour] [datetime] NOT NULL,
    [Done] [bit] NOT NULL,
    [PrevAppointmentID] [int] NULL,
 CONSTRAINT [PK_Appointments] PRIMARY KEY CLUSTERED 
(
    [AppointmentID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

person Lorenzo    schedule 25.10.2010    source источник
comment
если дизайн — это то, что вы можете изменить, подумайте об использовании эффективных датированных строк.   -  person Brad    schedule 25.10.2010
comment
@Brad: что вы подразумеваете под эффективными датированными строками?   -  person Lorenzo    schedule 25.10.2010
comment
это было длинное объяснение, поэтому я добавил еще один ответ.   -  person Brad    schedule 25.10.2010


Ответы (3)


Я не хотел полностью перехватывать его ответ, поэтому:

Используя ответ Брэда

Вот запрос для получения полной истории:

WITH    cte
          AS ( SELECT   AppointmentId ,
                        PrevAppointmentId
               FROM     Appointments
               WHERE    AppointmentId = @AppointmentId
               UNION ALL
               SELECT   prev.AppointmentId ,
                        prev.PrevAppointmentId
               FROM     Appointments prev
                        INNER JOIN cte curr ON prev.AppointmentId = curr.PrevAppointmentId
             ),
        cte1
          AS ( SELECT   AppointmentId ,
                        PrevAppointmentId
               FROM     Appointments
               WHERE    AppointmentId = @AppointmentId
               UNION ALL
               SELECT   prev.AppointmentId ,
                        prev.PrevAppointmentId
               FROM     Appointments prev
                        INNER JOIN cte1 curr ON prev.PrevAppointmentId = curr.AppointmentId
             )
    SELECT  *
    FROM    cte
    UNION
    SELECT  *
    FROM    cte1
person Martin    schedule 25.10.2010
comment
@Martin: в нем говорится, что до завершения запроса было достигнуто максимальное количество рекурсий 100 ... - person Lorenzo; 25.10.2010
comment
@Lorenzo - это имеет смысл, поскольку он будет продолжать двигаться вперед и назад, назад и вперед ... обновите мой ответ, но до сих пор не знаю, работает ли он. - person Martin; 25.10.2010
comment
@Martin: cteAppointments, когда вы выполняете внутренние соединения, не определены. Вы имеете в виду соответственно cteAppointments1 и 2? Извините, но я ничего не знаю о cte :( - person Lorenzo; 25.10.2010
comment
@Lorenzo - это даст вам полную историю, независимо от того, введете ли вы 12, 5 или 1. - person Martin; 25.10.2010
comment
@Мартин: Это работает. Миллион Спасибо!!!! Без предварительной работы Брэда это не было бы сделано так быстро. +1 за то, что отдал ему должное! - person Lorenzo; 25.10.2010

Логика:

  1. Получить встречу в вопросе
  2. Повторное присоединение к родительскому приложению
  3. Выбрать все результаты

Запрос:

DECLARE @appointmentId INT
SET @appointmentId = 3

--
;
WITH  past
      AS ( SELECT   AppointmentId ,
                    PrevAppointmentId
           FROM     Appointments
           WHERE    AppointmentId = @AppointmentId
           UNION ALL
           SELECT   prev.AppointmentId ,
                    prev.PrevAppointmentId
           FROM     Appointments prev
                    INNER JOIN cte curr ON prev.AppointmentId = curr.PrevAppointmentId
         ),
    future
      AS ( SELECT   AppointmentId ,
                    PrevAppointmentId
           FROM     Appointments
           WHERE    AppointmentId = @AppointmentId
           UNION ALL
           SELECT   prev.AppointmentId ,
                    prev.PrevAppointmentId
           FROM     Appointments prev
                    INNER JOIN cte1 curr ON prev.PrevAppointmentId = curr.AppointmentId
         )
SELECT  *
FROM    past OPTION(MAXRECURSION 500)
UNION
SELECT  *
FROM    future OPTION(MAXRECURSION 500)
person Brad    schedule 25.10.2010
comment
@Brad: он возвращает ошибку о том, что рекурсивная ссылка не допускается в подзапросах, соответствующих предложению WHERE внутри EXISTS (WHERE prev.AppointmentId = curr.PrevAppointmentId) - person Lorenzo; 25.10.2010
comment
Если вы публикуете код или XML, пожалуйста выделите эти строки в текстовом редакторе и нажмите кнопку кода (101 010) на панели инструментов редактора, чтобы правильно отформатировать и выделить синтаксис! - person marc_s; 25.10.2010
comment
Это работает, мне просто нужно было добавить prev. во второй выбор - person Martin; 25.10.2010
comment
@Brad: теперь выдает еще одну ошибку. Я обновил вопрос, чтобы включить сценарий создания для ваших испытаний. - person Lorenzo; 25.10.2010
comment
@Lorenzo - попробуйте код сейчас. Он работает в моей небольшой тестовой среде, которую я создал. - person Martin; 25.10.2010
comment
@Мартин и Брэд: Да, похоже, работает. Но есть проблема, когда у меня всего одна отсрочка и я параметр соответствую отложенной встрече - person Lorenzo; 25.10.2010
comment
@Lorenzo, запрос предназначен для работы в обратном направлении во времени (т. Е. Получает предыдущую историю встречи). Вам нужно, чтобы получить полную историю встреч (в обоих направлениях)? - person Brad; 25.10.2010
comment
Фактически, когда встреча была отложена только один раз, и я использую идентификатор последней встречи, запрос не возвращает строк. Если я использую идентификатор исходной встречи, запрос возвращает две строки. Я также вижу, что запрос не переходит более чем на один уровень в глубину, в то время как он возвращается на несколько уровней. - person Lorenzo; 25.10.2010
comment
@Брэд: Да. Оптимальным будет то, что запрос будет перемещаться вперед и назад во времени. - person Lorenzo; 25.10.2010
comment
@Lorenzo, я добавил перспективный UNION (непроверенный), но я думаю, что вам следует серьезно подумать об использовании EffectiveDates - person Brad; 25.10.2010
comment
@Brad: Это был бы вариант, Брэд, но не сейчас. В данный момент это невозможно. Последнее обновление выдает ошибку с более чем 100 рекурсиями... :( - person Lorenzo; 25.10.2010
comment
@Lorenzo, я добавил параметр MAXRECURSION и установил для него значение 500. Вы должны установить для него наименьшее значение, которое даст желаемые результаты (0 без ограничений, но будьте осторожны!). msdn.microsoft.com/en-us/library/ms181714.aspx msdn.microsoft.com/en-us/library/ms175972.aspx - person Brad; 26.10.2010
comment
@Брэд: Большое спасибо. Однако я могу просто оценить это число, потому что в действительности я не знаю, сколько раз встреча может быть отложена. Я сделаю запрос к базе данных, чтобы просто оценить это число. Большое спасибо за Вашу помощь :) - person Lorenzo; 27.10.2010
comment
Эй, как я получил прыгнул от ответа? :) - person Brad; 27.10.2010

Ответ на вопрос от OP:

Эффективные знакомства

Дата вступления в силу — это когда вы добавляете в таблицу столбец DateTime, который контролирует, когда запись становится «действующей». Затем столбец добавляется к PK таблицы, делая каждую запись записью о том, что было «действующим» в данный момент времени (дата вступления в силу). При эффективном датировании вы никогда не DELETE или UPDATE, а только INSERT, что означает, что у вас всегда есть полная история объекта с течением времени.

Чтобы найти наиболее эффективную запись, вы выбираете строку с максимальной эффективной записью, которой нет в будущем.

SELECT *
FROM   Appointments a1
WHERE  EffectiveDate = (SELECT MAX(EffectiveDate)
                        FROM   Appointments a2
                        WHERE  a1.AppointmentId = a2.AppointmentId
                               AND a2.EffectiveDate <= ISNULL(@asOfDate, GETDATE()
                       )

Это означает, что вы также можете предварительно датировать записи. Например, сегодня вам одобрили прибавку к зарплате, но она вступит в силу только через 2 недели.

Таким образом, чтобы найти историю встречи, вы должны просто:

SELECT *
FROM   Appointments
WHERE  AppointmentId = @appointmentId
ORDER BY EffectiveDate
person Brad    schedule 25.10.2010
comment
Я знал технику! Но я никогда не знал, что могу называть это «Эффективным свиданием». Большое спасибо за объяснение - person Lorenzo; 25.10.2010