TSQL: вернуть строки с самыми ранними датами - PullRequest
0 голосов
/ 16 июня 2009

Имеются 2 таблицы с именами "table1" и "table1_hist", которые структурно напоминают это:

TABLE1
id  status  date_this_status
1   open    2008-12-12
2   closed  2009-01-01
3   pending 2009-05-05
4   pending 2009-05-06
5   open    2009-06-01


TABLE1_hist
id  status  date_this_status
2   open    2008-12-24
2   pending 2008-12-26
3   open    2009-04-24
4   open    2009-05-04

Если table1 является текущим состоянием, а table1_hist - таблицей истории table1, как я могу вернуть строки для каждого идентификатора с самой ранней датой. Другими словами, для каждого идентификатора мне нужно знать его самый ранний статус и дату.

EXAMPLE:

For id 1 earliest status and date is open and 2008-12-12.
For id 2 earliest status and date is open and 2008-12-24.

Я пытался использовать MIN (datetime), объединения, динамический SQL и т. Д. Я только сегодня достиг блока писателей tsql и застрял.

Отредактировано, чтобы добавить: Тьфу. Это для базы данных SQL2000, поэтому ответ Алекса Мартелли не сработает. ROW_NUMBER не был представлен до SQL2005.

Ответы [ 7 ]

6 голосов
/ 16 июня 2009

SQL Server 2005 и более поздние версии поддерживают интересный (относительно недавний) аспект стандартов SQL, «функции ранжирования / управления окнами», что позволяет:

WITH AllRows AS (
  SELECT id, status, date_this_status,
    ROW_NUMBER() OVER(PARTITION BY id ORDER BY date_this_status ASC) AS row,
  FROM (SELECT * FROM Table1 UNION SELECT * FROM Table1_hist) Both_tables
)
SELECT id, status, date_this_status
FROM AllRows
WHERE row = 1
ORDER BY id;

, где я также использую синтаксис nice (и в равной степени «новый») WITH, чтобы избежать вложения подзапроса в основной SELECT.

В этой статье показано, как можно взломать эквивалент ROW_NUMBER (а также RANK и DENSE_RANK, двух других «новых» функций ранжирования / управления окнами) в SQL Server 2000 - но это не обязательно красиво и не особенно хорошо, увы.

3 голосов
/ 17 июня 2009

Следующий пример кода полностью самодостаточен, просто скопируйте его и вставьте в запрос студии управления и нажмите F5 =)

DECLARE @TABLE1 TABLE
        (
        id                  INT,
        status              VARCHAR(50),
        date_this_status    DATETIME
        )

DECLARE @TABLE1_hist TABLE
        (
        id                  INT,
        status              VARCHAR(50),
        date_this_status    DATETIME
        )

--TABLE1
INSERT  @TABLE1
SELECT  1,  'open',     '2008-12-12'    UNION ALL
SELECT  2,  'closed',   '2009-01-01'    UNION ALL
SELECT  3,  'pending',  '2009-05-05'    UNION ALL
SELECT  4,  'pending',  '2009-05-06'    UNION ALL
SELECT  5,  'open',     '2009-06-01'

--TABLE1_hist
INSERT  @TABLE1_hist
SELECT  2,  'open',     '2008-12-24'    UNION ALL
SELECT  2,  'pending',  '2008-12-26'    UNION ALL
SELECT  3,  'open',     '2009-04-24'    UNION ALL
SELECT  4,  'open',     '2009-05-04'

SELECT      x.id,
            ISNULL(y.[status], x.[status])                  AS [status],
            ISNULL(y.date_this_status, x.date_this_status)  AS date_this_status
FROM        @TABLE1 x
LEFT JOIN   (
            SELECT      a.*
            FROM        @TABLE1_hist a
            INNER JOIN  (
                        SELECT      id,
                                    MIN(date_this_status) AS date_this_status
                        FROM        @TABLE1_hist
                        GROUP BY    id
                        ) b
                    ON  a.id = b.id
                    AND a.date_this_status = b.date_this_status
            ) y
        ON  x.id = y.id
2 голосов
/ 16 июня 2009
SELECT  id,
        status,
        date_this_status
FROM    ( SELECT    *
          FROM      Table1
          UNION
          SELECT    *
          from      TABLE1_hist
        ) a
WHERE   date_this_status = ( SELECT MIN(date_this_status)
                             FROM   ( SELECT    *
                                      FROM      Table1
                                      UNION
                                      SELECT    *
                                      from      TABLE1_hist
                                    ) t
                             WHERE  id = a.id
                           ) 

Это немного уродливо, но, похоже, работает в MS SQL Server 2005.

1 голос
/ 17 июня 2009

Если я правильно понимаю ОП, данный идентификатор может появиться в TABLE1 или TABLE1_HISTORY или в обоих.

В вашем наборе результатов вы хотите вернуть каждый отдельный идентификатор и самый старый статус / дату, связанные с этим идентификатором, независимо от того, в какой таблице находится самый старый из них.

Итак, посмотрите в ОБОИХ таблицах и верните все записи, в которых нет записей в или таблице, для идентификатора с меньшим значением date_this_status .

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

SELECT ID, status, date_this_status FROM table1 ta WHERE
     NOT EXISTS(SELECT null FROM table1 tb WHERE
         tb.id = ta.id
         AND tb.date_this_status < ta.date_this_status)
     AND NOT EXISTS(SELECT null FROM table1_history tbh WHERE
         tbh.id = ta.id
         AND tbh.date_this_status < ta.date_this_status)

UNION ALL

SELECT ID, status, date_this_status FROM table1_history tah WHERE
     NOT EXISTS(SELECT null FROM table1 tb WHERE
         tb.id = tah.id
         AND tb.date_this_status < tah.date_this_status)
     AND NOT EXISTS(SELECT null FROM table1_history tbh WHERE
         tbh.id = tah.id
         AND tbh.date_this_status < tah.date_this_status)

Здесь три основных допущения:

  1. У каждого идентификатора, который вы хотите вернуть, будет хотя бы одна запись хотя бы в одной из таблиц.
  2. В одной таблице не будет несколько записей с одним и тем же идентификатором с одним и тем же значением date_this_status (можно уменьшить с помощью DISTINCT)
  3. Не будет записей для того же идентификатора в таблице other с тем же значением date_this_status (может быть уменьшено с помощью UNION вместо UNION ALL)

Мы можем сделать две небольшие оптимизации:

  1. Если идентификатор имеет запись в TABLE1_HISTORY, он всегда будет старше записи в TABLE1 для этого идентификатора.
  2. TABLE1 никогда не будет содержать несколько записей для одного и того же идентификатора (но таблица истории может).

Итак:

SELECT ID, status, date_this_status FROM table1 ta WHERE
     NOT EXISTS(SELECT null FROM table1_history tbh WHERE
         tbh.id = ta.id
         )

UNION ALL

SELECT ID, status, date_this_status FROM table1_history tah WHERE
     NOT EXISTS(SELECT null FROM table1_history tbh WHERE
         tbh.id = tah.id
         AND tbh.date_this_status < tah.date_this_status)
1 голос
/ 16 июня 2009

Вы можете сделать это с помощью эксклюзивного самостоятельного соединения. Присоединитесь к таблице истории, а затем в другой раз ко всем более ранним записям истории. В операторе where вы указываете, что не должно быть никаких более ранних записей.

select t1.id,
    isnull(hist.status, t1.status),
    isnull(hist.date_this_status, t1.date_this_status)
from table1 t1
left join (
    select h1.id, h1.status, h1.date_this_status
    from table1_hist h1
    left join table1_hist h2
        on h2.id = h1.id
        and h2.date_this_status < h1.date_this_status
    where h2.date_this_status is null
) hist on hist.id = t1.id

Немного душераздирающий, но довольно гибкий и эффективный!

Предполагается, что нет двух записей истории с одинаковой датой. Если есть, напишите само присоединение как:

left join table1_hist h2
    on h2.id = h1.id
    and (
        h2.date_this_status < h1.date_this_status
        or (h2.date_this_status = h1.date_this_status and h2.id < h1.id)
    )
0 голосов
/ 17 июня 2009

Игнорируя проблемы «двух таблиц» на мгновение, я использовал бы следующую логику ...

SELECT
   id, status, date
FROM
   Table1_hist AS [data]
WHERE
   [data].date = (SELECT MIN(date) FROM Table1_hist WHERE id = [data].id)

(РЕДАКТИРОВАТЬ: согласно комментарию BlackTigerX предполагается, что ни один идентификатор не может иметь более одного статуса с одинаковой датой.)

Простой способ экстраполировать это на две таблицы - использовать ответ breitak67. Замените все экземпляры my_table подзапросами, которые объединяют две таблицы вместе. Потенциальная проблема здесь связана с производительностью, поскольку вы можете обнаружить, что индексы становятся непригодными для использования.

Одним из способов ускорить это может быть использование подразумеваемых знаний:
1. В основной таблице всегда есть запись для каждого идентификатора.
2. В таблице истории не всегда есть запись.
3. Любая запись в таблице истории всегда «старше» записи в основной таблице.

SELECT
   [main].id,
   ISNULL([hist].status, [main].status),
   ISNULL([hist].date, [main].date)
FROM
   Table1          AS [main]
LEFT JOIN
(
   SELECT
      id, status, date
   FROM
      Table1_hist AS [data]
   WHERE
      [data].date = (SELECT MIN(date) FROM Table1_hist WHERE id = [data].id)
)
   AS [hist]
      ON [hist].id = [main].id
  • Найти самый старый статус для каждого идентификатора в таблице истории. (Можно использовать его индексы)
  • СЛЕВА ПРИСОЕДИНЯЙТЕСЬ к главной таблице (которая всегда имеет ровно одну запись для каждого идентификатора)
  • Если значение [hist] содержит значение, оно является более старым по определению
  • Если у [hist] нет значения, используйте значение [main]
0 голосов
/ 17 июня 2009

Если это фактическая структура ваших таблиц, вы не можете получить 100% точный ответ, проблема в том, что вы можете иметь 2 разных статуса на одну и ту же (самую раннюю) дату для любой данной записи, и вы не будете знать какой был введен первым, потому что у вас нет первичного ключа в таблице истории

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...