2016-01-06 2 views
1

Я пытаюсь понять, что функции окна PostgreSQL (9.3) немного лучше. Предположим, у меня есть простая таблица, как:Как выполнить отфильтрованный запрос в окне окна PostgreSQL?

SimpleTable 
    id int, 
    tservice timestamp 

и хотите:

Select id, tservice , count(*) OVER (PARTITION BY id ....) as counter 
from SimpleTable 

, где записи в SimpleTable имеют TService раз уходящие 40 лет, но счетчик должен быть ограничен только за три года до отметки времени tcervice каждой записи.

Как я могу произвести подсчет для каждой записи в SimpleTable?

Следствие вопрос: Как бы тот же запрос можно изменить, чтобы добавить количество всех записей, которые произошли за три года до сегодняшнего дня?

Edit # 1: Теперь я вижу, где этот вопрос является расплывчатым (я узнал что-то :)). Используя ответ ниже, я хотел бы получить отсчитывать 3yrs и отсчет от текущей даты, что-то вроде:

       3yrs prior current date 
1, 100, '2001-01-01 00:00:00', 0    0 
2, 100, '2002-01-01 00:00:00', 1    0 
3, 100, '2003-01-01 00:00:00', 2    0 
4, 100, '2004-01-01 00:00:00', 3    0 
5, 100, '2005-01-01 00:00:00', 3    0    
6, 100, '2006-01-01 00:00:00', 3    0 
7, 100, '2007-01-01 00:00:00', 3    0 
8, 100, '2008-01-01 00:00:00', 3    0 
9, 100, '2009-01-01 00:00:00', 3    0 
10, 100, '2010-01-01 00:00:00',3    0 
11, 100, '2011-01-01 00:00:00',3    0 
12, 100, '2012-01-01 00:00:00',3    0 
13, 100, '2013-01-01 00:00:00',3    0 
14, 100, '2014-01-01 00:00:00',3    1 
15, 100, '2015-01-01 00:00:00',3    2 
16, 100, '2016-01-01 00:00:00',3    3 (today is 2016-01-06) 

Edit # 2: Это работает, чтобы получить ответ, что нужно, но не использовать оконный раздел. Я думаю, что PostgreSQL не реализует RANGE с интервалами - вот что я думаю, что эта проблема требует.

 select s1.recid, s1.tservice, s1.client_recid, 
    (select count(*) from simpletable s2 
     where (s1.tservice - s2.tservice)::INTERVAL <= interval '3 years' and 
     s2.tservice < s1.tservice and 
     s2.client_recid = s1.client_recid) 
from simpletable s1 
order by client_recid, tservice 

На пару сотен тысяч записей это занимает около 10 секунд на моем ноутбуке. Есть ли более быстрый способ?

Добавление Примечание: Использование функционального подхода с курсором очерченного Эрвин сократило время выполнения до 146ms. Спасибо всем за отличный учебник.

+0

Что касается COUNT «за три года до обслуживания каждой записи», вы можете предоставить «данные образца» и «ожидаемый результат», пожалуйста, –

ответ

1

Что вы имеете в виду, это просто не возможно с использованием функции окна frame definition. (Вы начали подозревать столько же.) Статьи RANGE или ROWS подсчитывают разные значения или строки и не имеют понятия значения значений.

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

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

Весы много лучше. Я добавил подробные контрольные показатели.

+0

Отличное учение! Предлагаемый метод тестирования и оценки очень полезен. Благодаря :) –

1

Не совсем уверен, что вы стремитесь, и как правило, не будет разделить на ID (предполагается, что идентификатор является уникальным для каждой строки). Обычно вы разделяете какое-то значение, которое разделяется несколькими строками. Примером может помочь: SQL Fiddle

PostgreSQL 9.3 Схема установки:

CREATE TABLE SimpleTable 
    ("id" int, "client_id" int, "tservice" timestamp) 
; 

INSERT INTO SimpleTable 
    ("id", "client_id", "tservice") 
VALUES 
    (1, 100, '2001-01-01 00:00:00'), 
    (2, 100, '2002-01-01 00:00:00'), 
    (3, 100, '2003-01-01 00:00:00'), 
    (4, 100, '2004-01-01 00:00:00'), 
    (5, 100, '2005-01-01 00:00:00'), 
    (6, 100, '2006-01-01 00:00:00'), 
    (7, 100, '2007-01-01 00:00:00'), 
    (8, 100, '2008-01-01 00:00:00'), 
    (9, 100, '2009-01-01 00:00:00'), 
    (10, 100, '2010-01-01 00:00:00'), 
    (11, 100, '2011-01-01 00:00:00'), 
    (12, 100, '2012-01-01 00:00:00'), 
    (13, 100, '2013-01-01 00:00:00'), 
    (14, 100, '2014-01-01 00:00:00'), 
    (15, 100, '2015-01-01 00:00:00'), 
    (16, 100, '2016-01-01 00:00:00') 
; 

Запрос 1:

SELECT 
     id 
    , tservice 
    , COUNT(*) OVER (PARTITION BY client_id) AS C1 
    , COUNT(CASE WHEN tservice >= (CURRENT_DATE - INTERVAL '3 years') THEN 1 ELSE NULL END) 
      OVER (PARTITION BY client_id) AS C3 

FROM SimpleTable 

Results:

| id |     tservice | c1 | c3 | 
|----|---------------------------|----|----| 
| 1 | January, 01 2001 00:00:00 | 16 | 3 | 
| 2 | January, 01 2002 00:00:00 | 16 | 3 | 
| 3 | January, 01 2003 00:00:00 | 16 | 3 | 
| 4 | January, 01 2004 00:00:00 | 16 | 3 | 
| 5 | January, 01 2005 00:00:00 | 16 | 3 | 
| 6 | January, 01 2006 00:00:00 | 16 | 3 | 
| 7 | January, 01 2007 00:00:00 | 16 | 3 | 
| 8 | January, 01 2008 00:00:00 | 16 | 3 | 
| 9 | January, 01 2009 00:00:00 | 16 | 3 | 
| 10 | January, 01 2010 00:00:00 | 16 | 3 | 
| 11 | January, 01 2011 00:00:00 | 16 | 3 | 
| 12 | January, 01 2012 00:00:00 | 16 | 3 | 
| 13 | January, 01 2013 00:00:00 | 16 | 3 | 
| 14 | January, 01 2014 00:00:00 | 16 | 3 | 
| 15 | January, 01 2015 00:00:00 | 16 | 3 | 
| 16 | January, 01 2016 00:00:00 | 16 | 3 | 
+0

, пожалуйста, обратитесь к редакции. +1 для концепции общей ценности. Благодарю. –

+0

Как только таблица разбита на разделы, существует ли способ динамически назначать оконный фрейм, который должен пройти три года до службы на этой конкретной записи? –

1

Вот «путь», упомянутый выше, с использованием LATERAL, чтобы получить динамический счет, который не был достигнут с помощью оконной функции.

SQL Fiddle

PostgreSQL 9.3 Настройка схемы:

CREATE TABLE SimpleTable 
    ("id" int, "client_id" int, "tservice" timestamp) 
; 

INSERT INTO SimpleTable 
    ("id", "client_id", "tservice") 
VALUES 
    (1, 100, '2001-01-01 00:00:00'), 
    (2, 100, '2002-01-01 00:00:00'), 
    (3, 100, '2003-01-01 00:00:00'), 
    (4, 100, '2004-01-01 00:00:00'), 
    (5, 100, '2005-01-01 00:00:00'), 
    (6, 100, '2006-01-01 00:00:00'), 
    (7, 100, '2007-01-01 00:00:00'), 
    (8, 100, '2008-01-01 00:00:00'), 
    (9, 100, '2009-01-01 00:00:00'), 
    (10, 100, '2010-01-01 00:00:00'), 
    (11, 100, '2011-01-01 00:00:00'), 
    (12, 100, '2012-01-01 00:00:00'), 
    (13, 100, '2013-01-01 00:00:00'), 
    (14, 100, '2014-01-01 00:00:00'), 
    (15, 100, '2015-01-01 00:00:00'), 
    (16, 100, '2016-01-01 00:00:00') 
; 

Запрос 1:

select 
* 
from SimpleTable 
cross join lateral (
        select count(*) as countLT3yrs 
        from SimpleTable st 
        where st.client_id = SimpleTable.client_id 
        and st.tservice >= (SimpleTable.tservice - INTERVAL '3 years') 
        and st.tservice < SimpleTable.tservice 
        ) x 

Results:

| id | client_id |     tservice | countlt3yrs | 
|----|-----------|---------------------------|-------------| 
| 1 |  100 | January, 01 2001 00:00:00 |   0 | 
| 2 |  100 | January, 01 2002 00:00:00 |   1 | 
| 3 |  100 | January, 01 2003 00:00:00 |   2 | 
| 4 |  100 | January, 01 2004 00:00:00 |   3 | 
| 5 |  100 | January, 01 2005 00:00:00 |   3 | 
| 6 |  100 | January, 01 2006 00:00:00 |   3 | 
| 7 |  100 | January, 01 2007 00:00:00 |   3 | 
| 8 |  100 | January, 01 2008 00:00:00 |   3 | 
| 9 |  100 | January, 01 2009 00:00:00 |   3 | 
| 10 |  100 | January, 01 2010 00:00:00 |   3 | 
| 11 |  100 | January, 01 2011 00:00:00 |   3 | 
| 12 |  100 | January, 01 2012 00:00:00 |   3 | 
| 13 |  100 | January, 01 2013 00:00:00 |   3 | 
| 14 |  100 | January, 01 2014 00:00:00 |   3 | 
| 15 |  100 | January, 01 2015 00:00:00 |   3 | 
| 16 |  100 | January, 01 2016 00:00:00 |   3 |