Помогите с запросом (алгоритм)

b2b
Дата: 01.02.2013 15:48:30
Пожалуйста, помогите с запросом. Требуется придумать алгоритм, который из таблички:

select '2013-01-02' as DateN, '2013-01-10' as DateK

union all

select '2013-01-08', '2013-01-13'

union all

select '2013-01-12', '2013-01-18'

union all

select '2013-01-10', '2013-01-14'

union all

select '2013-01-08', '2013-01-15'

union all

select '2013-01-20', '2013-01-25'

сделает:
02.01.2013	18.01.2013
20.01.2013 25.01.2013

Дано: набор периодов. Объединить пересекающиеся периоды в максимально возможный(е). На самом деле мне нужно посчитать количество дней в таких периодах (но разность дат - это не сложно).

В приведенном примере все периоды так или иначе пересекаются между собой, а последний период (см. последний select) стоит отдельно, поэтому на выходе получается 2 периода: в первый входят все кроме последнего, и сам последний период второй строчкой.
Гость333
Дата: 01.02.2013 16:10:20
b2b,

6998963
Добрый Э - Эх
Дата: 01.02.2013 18:04:51
b2b,

если версия сервера позволяет, то через оконный MIN
b2b
Дата: 01.02.2013 18:57:00
Попробовал упростить ваш запрос:

-- Начало эмуляции тестовых данных:
with 
  intervals (b_num, e_num) as 
    (
      select  1,  8 union all
      select  7, 20 union all
      select  6, 15 union all
      select  9, 22 union all
      select  4,  6 union all
      select 25, 28 union all
      select 27, 35
    )
-- Конец эмуляции тестовых данных.
--
-- Непосредственно сам запрос:
select s1.b_num, min(s2.e_num) as e_num
from
	(select s1.b_num
	from
		intervals s1
			left join intervals s2
			on s1.b_num > s2.b_num and s1.b_num <= s2.e_num
	where
		s2.b_num is null) as s1
		inner join (select s1.e_num
		from
			intervals s1
				left join intervals s2
				on s1.e_num < s2.e_num and s1.e_num >= s2.b_num
		where
			s2.b_num is null) as s2
		on s1.b_num <= s2.e_num

group by
	s1.b_num

не проще?
Добрый Э - Эх
Дата: 01.02.2013 19:47:38
b2b,

Проще, сложнее - понятия относительные.
Я бы всё же сказал - другая форма записи. Ибо замена not exists на LEFT JOIN - ни есть упрощение. :)
Более того, по опыту общения с новичками в SQL могу сказать - им not exists понятнее, чем та же мысль, но выраженная через внешние соединения. ;)
b2b
Дата: 01.02.2013 20:04:39
Добрый Э - Эх,

Так или иначе, за решение СПАИБО!
Exproment
Дата: 01.02.2013 20:12:31
Добрый Э - Эх, Я даже более скажу -
  • есть такое золотое правило - не джоинить те таблицы, которых нет в результирующем наборе.
  • not exists в большинстве случаев отработает абсолютно так-же как и left join, но бывают случаи, когда план запроса с exists будет лучше чем c left join(так сходу пример не вспомню, но такое бывает)
  • Да и запрос так на самом деле более понятнее получается... так сказать становится схож с естественным языком - мол верни, если не существует чего-то там.
  • зачастую в exists удобно вставлять intersect или except, что еще более повышает его привлекательность.

    Потому лучше всегда использовать not exists чем left join
  • Гость333
    Дата: 04.02.2013 11:09:43
    Exproment
  • not exists в большинстве случаев отработает абсолютно так-же как и left join, но бывают случаи, когда план запроса с exists будет лучше чем c left join(так сходу пример не вспомню, но такое бывает)

  • Легко — джойн по индексированому полю. В случае LEFT JOIN нужно будет присоединить все записи "правой" таблицы, а в получившемся наборе оставить только те, где выполнится условие [Правая таблица].[Поле соединения] IS NULL. При этом может быть отсеяно много "лишних" записей, но перед этим выборка "лишних" записей всё равно будет произведена.
    В случае же NOT EXISTS для каждого значения [Левая таблица].[Поле соединения] достаточно сделать выборку TOP 1 из таблицы [Правая таблица]. Если в правой таблице есть много дублирующихся записей в [Поле соединения], план запроса может быть выгоднее.
    + Пример, где NOT EXISTS выгодее на 2 порядка
    set nocount on;
    
    create table #parent(parent_id int identity primary key);
    create table #child(child_id int identity primary key, parent_id int not null);
    
    begin transaction;
    go
    
    declare @i int, @id int;
    insert #parent default values;
    set @id = scope_identity();
    set @i = 1;
    while @i <= 1000
    begin
        insert #child(parent_id) values(@id);
        set @i = @i + 1;
    end;
    go 1000
    commit transaction;
    
    create index i on #child(parent_id);
    go
    
    set statistics time on;
    
    select p.*
    from #parent p
         left outer join #child c on c.parent_id = p.parent_id
    where c.parent_id is null;
    
    select p.*
    from #parent p
    where not exists (select * from #child c where c.parent_id = p.parent_id);
    
    /*
    left join:
     SQL Server Execution Times:
       CPU time = 171 ms,  elapsed time = 182 ms.
    
    not exists:
     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 2 ms.
    */