1

Я пытаюсь заполнить ежедневные данные за отсутствующие даты и не могу найти ответ, пожалуйста, помогите.Заполните таблицу данными для отсутствующей даты (postgresql, redshift)

Мой daily_table пример:

 url   | timestamp_gmt | visitors | hits | other.. 
-------------------+---------------+----------+-------+------- 
www.domain.com/1 | 2016-04-12 | 1231 | 23423 | 
www.domain.com/1 | 2016-04-13 | 1374 | 26482 | 
www.domain.com/1 | 2016-04-17 | 1262 | 21493 | 
www.domain.com/2 | 2016-05-09 | 2345 | 35471 |   

Ожидаемый результат: Я палочка, чтобы заполнить эту таблицу с данными для каждого домена и каждый день, который просто копировать данные из предыдущего date:

 url   | timestamp_gmt | visitors | hits | other.. 
-------------------+---------------+----------+-------+------- 
www.domain.com/1 | 2016-04-12 | 1231 | 23423 | 
www.domain.com/1 | 2016-04-13 | 1374 | 26482 | 
www.domain.com/1 | 2016-04-14 | 1374 | 26482 |  <-added 
www.domain.com/1 | 2016-04-15 | 1374 | 26482 |  <-added 
www.domain.com/1 | 2016-04-16 | 1374 | 26482 |  <-added 
www.domain.com/1 | 2016-04-17 | 1262 | 21493 | 
www.domain.com/2 | 2016-05-09 | 2345 | 35471 |   

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

РЕЗЮМЕ:

В течение нескольких последних дней я foud, что:

  1. Amazon-красное смещение работает с 8-й версии PostgreSql, поэтому она не поддерживает такую ​​прекрасную команду как JOIN LATERAL
  2. Redshift также не поддерживает generate_series и CTEs
  3. Но он поддерживает простой WITH (спасибо @systemjack), но WITH RECURSIVE не
+0

Очевидный вопрос: * почему *? Разве не имело бы смысла оставлять пробелы такими, какими они есть, и позволить веб-страницам/независимо выбирать, как отображать это? –

+0

Это требование, потому что наши клиенты используют таблицы напрямую, а не через какой-либо интерфейс. –

+0

Поддерживает ли красное смещение (рекурсивное) CTE? – wildplasser

ответ

0

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

Вместо generate_series я использовал этот крюк:

WITH date_range AS (
    SELECT trunc(current_date - (row_number() OVER())) AS date 
    FROM any_table -- any of your table which has enough data 
    LIMIT 365 
) SELECT * FROM date_range; 

Чтобы получить список URL-адресов, которые я должен заполнить с данными я использовал это:

WITH url_list AS (
    SELECT 
    url AS gapsed_url, 
    MIN(timestamp_gmt) AS min_date, 
    MAX(timestamp_gmt) AS max_date 
    FROM daily_table 
    WHERE url IN (
    SELECT url FROM daily_table GROUP BY url 
    HAVING count(url) < (MAX(timestamp_gmt) - MIN(timestamp_gmt) + 1) 
) 
    GROUP BY url 
) SELECT * FROM url_list; 

Тогда я КомбиНу данные данные, давайте называют его url_mapping:

SELECT t1.*, t2.gapsed_url FROM date_range AS t1 CROSS JOIN url_list AS t2 
WHERE t1.date <= t2.max_date AND t1.date >= t2.min_date; 

и получить данные по ближайшим г я сделал следующее:

SELECT sd.* 
FROM url_mapping AS um JOIN daily_table AS sd 
ON um.gapsed_url = sd.url AND (
    sd.timestamp_gmt = (SELECT max(timestamp_gmt) FROM daily_table WHERE url = sd.url AND timestamp_gmt <= um.date) 
) 

Я надеюсь, что это поможет кому-то.

1

Посмотрите на идеи позади запроса:

select distinct on (domain, new_date) * 
from (
    select new_date::date 
    from generate_series('2016-04-12', '2016-04-17', '1d'::interval) new_date 
    ) s 
left join a_table t on date <= new_date 
order by domain, new_date, date desc; 

    new_date |  domain  | date | visitors | hits 
------------+-----------------+------------+----------+------- 
2016-04-12 | www.domain1.com | 2016-04-12 |  1231 | 23423 
2016-04-13 | www.domain1.com | 2016-04-13 |  1374 | 26482 
2016-04-14 | www.domain1.com | 2016-04-13 |  1374 | 26482 
2016-04-15 | www.domain1.com | 2016-04-13 |  1374 | 26482 
2016-04-16 | www.domain1.com | 2016-04-13 |  1374 | 26482 
2016-04-17 | www.domain1.com | 2016-04-17 |  1262 | 21493 
(6 rows) 

Вы должны выбрать начальную и конечную даты в соответствии с вашими требованиями. Запрос может быть довольно дорогим (вы упомянули о миллиардных пробелах), поэтому применяйте его с осторожностью (тест на меньшем подмножестве данных или выполнение по этапам).

В случае отсутствия generate_series() вы можете создать свой собственный генератор. Here is an interesting example. Представления из цитируемой статьи могут использоваться вместо generate_series(). Например, если вам нужен период '2016-04-12' + 5 days:

select distinct on (domain, new_date) * 
from (
    select '2016-04-12'::date+ n new_date 
    from generator_16 
    where n < 6 
    ) s 
left join a_table t on date <= new_date 
order by domain, new_date, date desc; 

вы получите тот же результат, как и в первом примере.

+0

Это действительно интересно, спасибо за совместное использование, попробуем и дадим айсбергу –

1

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

with days as (
    select (dateadd(day, -row_number() over (order by true), sysdate::date+'1 day'::interval)) as day 
      from stv_blocklist limit 30 
) 
select day from days order by day 

нацелить на определенный временной диапазон изменения sysdate к буквальным, который был бы в последний день после окончания диапазона вы хотите, и предел на сколько дней, чтобы покрыть.

Вкладыш будет что-то вроде этого:

with days as (
    select (dateadd(day, -row_number() over (order by true), sysdate::date+'1 day'::interval)) as day 
      from stv_blocklist limit 30 
) 
insert into your_table (domain, date) (
    select dns.domain, d.day 
    from days d 
    cross join (select distinct(domain) from your_table) dns 
    left join your_table y on y.domain=dns.domain and y.date=d.day 
    where y.date is null 
) 

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

Ссылка на таблицу stv_blocklist может быть любой таблицей с достаточным количеством строк в ней, чтобы покрыть предел диапазона в предложении with и используется для предоставления семени для оконной функции row_number().

После того как вы дата только строки в месте, вы можете обновить их с последней полной записью, как так:

update your_table set visitors=t.visitors, hits=t.hits 
from (
    select a.domain, a.date, b.visitors, b.hits 
    from your_table a 
    inner join your_table b 
     on b.domain=a.domain and b.date=(SELECT max(date) FROM your_table where domain=a.domain and hits is not null and date < a.date) 
    where a.hits is null 
) t 
where your_table.domain=t.domain and your_table.date=t.date 

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

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

update your_table set visitors=t.prev_visitors, hits=t.prev_hits 
from (
    select domain, date, hits 
     lag(visitors,1) ignore nulls over (partition by domain order by date) as prev_visitors, 
     lag(hits,1) ignore nulls over (partition by domain order by date) as prev_hits 
    from your_table 
) t 
where t.hits is null and your_table.domain=t.domain and your_table.date=t.date 

Должно быть возможно объединить это с первоначальным запросом населения и сделать все сразу.

+0

Я попробую и дам вам знать КАК МОЖНО СКОРЕЕ –

+0

CTE не поддерживаются в redsift. (см. комментарий OP чуть ниже вопроса) Я сомневаюсь, что функции окна поддерживаются, поскольку они были введены в postgres-8.4. – wildplasser

+0

@wildplasser Redshift поддерживает функции окна просто отлично. Я использую их все время. http://docs.aws.amazon.com/redshift/latest/dg/c_Window_functions.html – systemjack

1

Альтернативное решение, избегая всех «современные» функции; -]

-- \i tmp.sql 

     -- NOTE: date and domain are keywords in SQL 
CREATE TABLE ztable 
     (zdomain  TEXT NOT NULL 
     , zdate  DATE NOT NULL 
     , visitors  INTEGER NOT NULL DEFAULT 0 
     , hits   INTEGER NOT NULL DEFAULT 0 
     , PRIMARY KEY (zdomain,zdate) 
     ); 
INSERT INTO ztable (zdomain,zdate,visitors,hits) VALUES 
    ('www.domain1.com', '2016-04-12' ,1231 ,23423) 
,('www.domain1.com', '2016-04-13' ,1374 ,26482) 
,('www.domain1.com', '2016-04-17' ,1262 ,21493) 
,('www.domain3.com', '2016-04-14' ,3245 ,53471)  -- << cheating! 
,('www.domain3.com', '2016-04-15' ,2435 ,34571) 
,('www.domain3.com', '2016-04-16' ,2354 ,35741) 
,('www.domain2.com', '2016-05-09' ,2345 ,35471) ; 

     -- Create "Calendar" table with all possible dates 
     -- from the existing data in ztable. 
     -- [if there are sufficient different domains 
     -- in ztable there will be no gaps] 
     -- [Normally the table would be filled by generate_series() 
     -- or even a recursive CTE] 
     -- An exta advantage is that a table can be indexed. 
CREATE TABLE date_domain AS 
SELECT DISTINCT zdate AS zdate 
FROM ztable; 
ALTER TABLE date_domain ADD PRIMARY KEY (zdate); 
-- SELECT * FROM date_domain; 

     -- Finding the closest previous record 
     -- without using window functions or aggregate queries. 
SELECT d.zdate, t.zdate, t.zdomain 
     ,t.visitors, t.hits 
     , (d.zdate <> t.zdate) AS is_fake -- for fun 
FROM date_domain d 
LEFT JOIN ztable t 
     ON t.zdate <= d.zdate 
     AND NOT EXISTS (SELECT * FROM ztable nx 
       WHERE nx.zdomain = t.zdomain 
       AND nx.zdate > d.zdate 
       AND nx.zdate < t.zdate 
       ) 
ORDER BY t.zdomain, d.zdate 
     ; 
Смежные вопросы