2017-01-13 12 views
2

Выборочные данные

CREATE TEMP TABLE a AS 
SELECT id, adate::date, name 
FROM (VALUES 
    (1,'1/1/1900','test'), 
    (1,'3/1/1900','testing'), 
    (1,'4/1/1900','testinganother'), 
    (1,'6/1/1900','superbtest'), 
    (2,'1/1/1900','thebesttest'), 
    (2,'3/1/1900','suchtest'), 
    (2,'4/1/1900','test2'), 
    (2,'6/1/1900','test3'), 
    (2,'7/1/1900','test4') 
) AS t(id,adate,name); 

CREATE TEMP TABLE b AS 
SELECT id, bdate::date, score 
FROM (VALUES 
    (1,'12/31/1899', 7), 
    (1,'4/1/1900' , 45), 
    (2,'12/31/1899', 19), 
    (2,'5/1/1900' , 29), 
    (2,'8/1/1900' , 14) 
) AS t(id,bdate,score); 

Что я хочу

Что мне нужно сделать, это совокупный текст столбец из таблицы а где идентификатор соответствует таблице b, а дата из таблицы a находится между двумя ближайшими датами из таблицы b. Желаемый результат:Совокупный текст столбцов, даты в таблице а находятся между датами в таблице б

id date score textagg 
1 12/31/1899 7 test, testing 
1 4/1/1900 45 testinganother, superbtest 
2 12/31/1899 19 thebesttest, suchtest, test2 
2 5/1/1900 29 test3, test4 
2 8/1/1900 14 

Мои мысли сделать что-то вроде этого:

create table date_join 
select a.id, string_agg(a.text, ','), b.* 
from tablea a 
left join tableb b 
on a.id = b.id 
*having a.date between b.date and b.date*; 

, но я действительно борется с последней строки, выяснить, как агрегировать только тогда, когда дата в таблице Ь между ближайшие две даты в таблице b. Любое руководство очень ценится.

+0

Схема с повторяющимися значениями в 'id' не имеет большого смысла. Сказав это, проверьте тип данных 'tsrange' и посмотрите, можете ли вы использовать его для этой цели. –

+0

Ваш пример не работает,' superbtest' не * между * соответствующими датами в таблице 'b'. Также отсутствуют определения таблиц и версия Postgres. –

+0

Обратите внимание, что нужный результат не в первой нормальной форме. Разве вам не лучше иметь что-то вроде (id, date, score, text) с 9 строками вывода? –

ответ

2

Я не могу обещать, что это лучший способ сделать это, но это a способ сделать это.

with b_values as (
    select 
    id, date as from_date, score, 
    lead (date, 1, '3000-01-01') 
     over (partition by id order by date) - 1 as thru_date 
    from b 
) 
select 
    bv.id, bv.from_date, bv.score, 
    string_agg (a.text, ',') 
from 
    b_values as bv 
    left join a on 
    a.id = bv.id and 
    a.date between bv.from_date and bv.thru_date 
group by 
    bv.id, bv.from_date, bv.score 
order by 
    bv.id, bv.from_date 

Я предполагающий вы никогда не будете иметь дату в таблице выше, чем 12/31/2999, так что если вы все еще используете этот запрос после этой даты, пожалуйста, примите мои извинения.

Вот результат я получил, когда я побежал это:

id from_date score string_agg 
1 0   7  test,testing 
1 92   45  testinganother,superbtest 
2 0   19  thebesttest,suchtest,test2 
2 122   29  test3,test4 
2 214   14 

Я хотел бы также отметить, что between в объединении является убийцей производительности. Если у вас большие объемы данных, могут быть более эффективные идеи о том, как подойти к этому, но это во многом зависит от того, как выглядят ваши фактические данные.

+0

Вы можете уменьшить вероятность принести извинения, используя lag() с датой, как 1800-01-01. Конечно, вы невосприимчивы к изобретению путешествий во времени, но там ваш запрос еще не должен существовать, если он не используется для запуска машины с временным путешествием (там вы в беде!) –

+0

Я добавил примеры данных на вопрос, который может или не может работать с вашими данными. Было бы здорово в будущем, хотя, если бы вы сами обновили вопрос с помощью DDL, вы использовали для создания и работы среды. –

+1

Это, как правило, довольно хорошо, вы можете использовать 'tsrange', если вы хотите предварительно рассчитать диапазон и найти, когда даты сидят внутри. Это немного синтаксис. Однако, скорее всего, быстрее. Вы также можете поставить «бесконечность» :: date в верхней части, просто чтобы выйти из bizzare 3000-01-01 lol. –