Объединение разделенных диапазонов дат в запросе SQL - PullRequest
4 голосов
/ 26 сентября 2008

Я работаю над запросом, который должен объединить несколько строк данных на основе диапазонов дат. Эти строки дублируются во всех значениях данных, за исключением диапазонов дат. Например, данные таблицы могут выглядеть как

StudentID   StartDate   EndDate     Field1  Field2
1           9/3/2007    10/20/2007  3       True
1           10/21/2007  6/12/2008   3       True
2           10/10/2007  3/20/2008   4       False
3           9/3/2007    11/3/2007   8       True
3           12/15/2007  6/12/2008   8       True

Результат запроса должен иметь объединенные диапазоны дат. Запрос должен объединять диапазоны дат с разрывом в один день. Если разрыв превышает один день, строки не должны объединяться. Строки, у которых нет разбитого диапазона дат, должны проходить без изменений. Результат будет выглядеть как

StudentID   StartDate   EndDate     Field1  Field2
1           9/3/2007    6/12/2008   3       True
2           10/10/2007  3/20/2008   4       False
3           9/3/2007    11/3/2007   8       True
3           12/15/2007  6/12/2008   8       True

Какой будет инструкция SELECT для этого запроса?

Ответы [ 10 ]

2 голосов
/ 26 сентября 2008

Следующий код должен работать. Я сделал несколько предположений следующим образом: нет совпадений диапазонов дат, нет значений NULL ни в одном из полей, а дата начала для данной строки всегда меньше даты окончания. Если ваши данные не соответствуют этим критериям, вам нужно настроить этот метод, но он должен указать вам правильное направление.

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

CREATE VIEW dbo.StudentStartDates
AS
    SELECT
        S.StudentID,
        S.StartDate,
        S.Field1,
        S.Field2
    FROM
        dbo.Students S
    LEFT OUTER JOIN dbo.Students PREV ON
        PREV.StudentID = S.StudentID AND
        PREV.Field1 = S.Field1 AND
        PREV.Field2 = S.Field2 AND
        PREV.EndDate = DATEADD(dy, -1, S.StartDate)
    WHERE PREV.StudentID IS NULL
GO

CREATE VIEW dbo.StudentEndDates
AS
    SELECT
        S.StudentID,
        S.EndDate,
        S.Field1,
        S.Field2
    FROM
        dbo.Students S
    LEFT OUTER JOIN dbo.Students NEXT ON
        NEXT.StudentID = S.StudentID AND
        NEXT.Field1 = S.Field1 AND
        NEXT.Field2 = S.Field2 AND
        NEXT.StartDate = DATEADD(dy, 1, S.EndDate)
    WHERE NEXT.StudentID IS NULL
GO


SELECT
    SD.StudentID,
    SD.StartDate,
    ED.EndDate,
    SD.Field1,
    SD.Field2
FROM
    dbo.StudentStartDates SD
INNER JOIN dbo.StudentEndDates ED ON
    ED.StudentID = SD.StudentID AND
    ED.Field1 = SD.Field1 AND
    ED.Field2 = SD.Field2 AND
    ED.EndDate > SD.StartDate AND
    NOT EXISTS (SELECT * FROM dbo.StudentEndDates ED2 WHERE ED2.StudentID = SD.StudentID AND ED2.Field1 = SD.Field1 AND ED2.Field2 = SD.Field2 AND ED2.EndDate < ED.EndDate AND ED2.EndDate > SD.StartDate)
GO
0 голосов
/ 21 мая 2010

Вот пример с тестовыми данными с использованием синтаксиса SQL Server 2005/2008.

DECLARE @Data TABLE(
    CalendarDate datetime )

INSERT INTO @Data( CalendarDate )
-- range start
SELECT '1 Jan 2010'
UNION ALL SELECT '2 Jan 2010'
UNION ALL SELECT '3 Jan 2010'
-- range start
UNION ALL SELECT '5 Jan 2010'
-- range start
UNION ALL SELECT '7 Jan 2010'
UNION ALL SELECT '8 Jan 2010'
UNION ALL SELECT '9 Jan 2010'
UNION ALL SELECT '10 Jan 2010'

SELECT DateGroup, Min( CalendarDate ) AS StartDate, Max( CalendarDate ) AS EndDate
FROM(   SELECT NextDay.CalendarDate, 
            DateDiff( d, RangeStart.CalendarDate, NextDay.CalendarDate ) - ROW_NUMBER() OVER( ORDER BY NextDay.CalendarDate ) AS DateGroup
        FROM( SELECT Min( CalendarDate ) AS CalendarDate
                FROM @data ) AS RangeStart
            JOIN @data AS NextDay
                ON NextDay.CalendarDate >= RangeStart.CalendarDate ) A
GROUP BY DateGroup
0 голосов
/ 29 сентября 2008

Это классическая проблема в SQL (языке), например описаны в книгах Джо Селко «SQL для умных» (глава 23 «Регионы, прогоны, пробелы, последовательности и серии») и в его последней книге «Мышление в наборах» (глава 15).

Хотя «забавно» исправлять данные во время выполнения с помощью запроса монстра, для меня это одна из тех ситуаций, которые можно лучше исправить автономно и процедурно (лично я бы сделал это с помощью формул в электронной таблице Excel). ).

Важно установить эффективные ограничения базы данных, чтобы предотвратить повторение повторяющихся периодов. Снова, написание последовательных ограничений в SQL является классическим: см. Snodgrass (http://www.cs.arizona.edu/people/rts/tdbbook.pdf). Подсказка для пользователей MS Access: вам нужно использовать ограничения CHECK.

0 голосов
/ 26 сентября 2008

Альтернативный окончательный запрос к тому, что предоставил Том Х. в принятом ответе:

SELECT
    SD.StudentID,
    SD.StartDate,
    MIN(ED.EndDate),
    SD.Field1,
    SD.Field2
FROM
    dbo.StudentStartDates SD
INNER JOIN dbo.StudentEndDates ED ON
    ED.StudentID = SD.StudentID AND
    ED.Field1 = SD.Field1 AND
    ED.Field2 = SD.Field2 AND
    ED.EndDate > SD.StartDate
GROUP BY
    SD.StudentID, SD.Field1, SD.Field2, SD.StartDate

Это также сработало для всех тестовых данных.

0 голосов
/ 26 сентября 2008

Рассматривали ли вы не равное объединение? Это будет выглядеть примерно так:

SELECT A.StudentID, A.StartDate, A.EndDate, A.Field1, A.Field2
FROM tblEnrollment AS A LEFT JOIN tblEnrollment AS B ON (A.StudentID = B.StudentID) 
   AND (A.EndDate=B.StartDate-1)
WHERE B.StudentID Is Null;

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

[Предупреждение: остерегайтесь того, что вы можете редактировать неэквивалентное объединение только в конструкторе запросов Access в представлении SQL - переключение в представление конструктора может привести к потере соединения (хотя при переключении Access сообщит о проблеме , и если вы немедленно переключитесь обратно в SQL View, вы его не потеряете)]

Если вы тогда СОЮЗИТЕ что с этим:

SELECT A.StudentID, A.StartDate, B.EndDate, A.Field1, A.Field2
FROM tblEnrollment AS A INNER JOIN tblEnrollment AS B ON (A.StudentID = B.StudentID) 
   AND (A.EndDate= B.StartDate-1)

Он должен дать вам то, что вам нужно, при условии, что одновременно не может быть более двух непрерывных записей. Я не уверен, как бы вы это сделали, если бы у вас было более двух смежных записей (это может включать в себя просмотр StartDate-1 по сравнению с EndDate), но это может привести вас в правильном направлении.

0 голосов
/ 26 сентября 2008

РЕДАКТИРОВАТЬ: сделать еще один набор SQL для доступа. Я проверял все это, но по частям, потому что я не знаю, как сделать несколько заявлений одновременно в Access. Поскольку я также не знаю, как делать комментарии, вы можете увидеть комментарии в версии SQL ниже.

select 
studentid, min(startdate) as Starter, max(enddate) as Ender, field1, field2, 
max(startDate) - Min(endDate)  as MaxGap 
into tempIDs
from student 
group by studentid, field1, field2 ;  

delete from tempIDs where MaxGap > 1;

UPDATE student INNER JOIN TempIDs ON Student.studentID = TempIDS.StudentID
SET Student.StartDate = [TempIDs].[Starter],
 Student.EndDate = [TempIDs].[Ender];

Я думаю, что это так, в SQL Server - я не делал этого в Access. Я не проверял его на необычные условия, такие как наложение нескольких записей и т. Д., Но это должно помочь вам начать. Он обновляет все повторяющиеся записи с небольшими пробелами, оставляя дополнения в базе данных. В MSDN есть страница по устранению дубликатов: http://support.microsoft.com/kb/139444

select 
studentid, min(startdate) as StartDate, max(enddate) as EndDate, field1, field2, 
datediff(dd, Min(endDate),max(startDate)) as MaxGap 
into #tempIDs
from #student 
group by studentid, field1, field2    

-- Update the relevant records.  Keeps two copies of the massaged record 
-- - extra will need to be deleted.

update #student 
set startdate = #TempIDS.startdate, enddate = #tempIDS.EndDate
from #tempIDS 
where #student.studentid = #TempIDs.StudentID and MaxGap < 2
0 голосов
/ 26 сентября 2008

Если решения min () / max () недостаточно хороши (например, если даты не являются смежными, и вы хотите сгруппировать отдельные диапазоны дат по отдельности), мне интересно, сработает ли что-то с использованием предложений Oracle START WITH и CONNECT BY , Что, конечно, не подойдет для каждой базы данных.

0 голосов
/ 26 сентября 2008
select StudentID, min(StartDate) StartDate, max(EndDate) EndDate, Field1, Field2 
  from table
 group by StudentID, Field1, Field2
0 голосов
/ 26 сентября 2008
SELECT StudentID, MIN(startdate) AS startdate, MAX(enddate), field1, field2
FROM tablex
GROUP BY StudentID, field1, field2

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

0 голосов
/ 26 сентября 2008

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

РЕДАКТИРОВАТЬ: Мой ответ предполагает, что у вас более одного диапазона дат на одного учащегося, а не только начало и конец. Если у вас есть только один диапазон дат без пропусков, то другие упомянутые решения - путь.

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