PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Общ брой записи на седмица

Простият подход би бил да се реши това с CROSS JOIN, както е показано от @jpw. Въпреки това, има някои скрити проблеми :

  1. Изпълнениената на безусловно CROSS JOIN бързо се влошава с увеличаване на броя на редовете. Общият брой редове се умножава по броя на седмиците, за които тествате, преди тази огромна извлечена таблица да може да бъде обработена в агрегацията. Индексите не могат да помогнат.

  2. Започването на седмици с 1 януари води до несъответствия. Седмици по ISO може да е алтернатива. Вижте по-долу.

Всички следните заявки използват силно индекс на exam_date . Не забравяйте да имате такъв.

Присъединете се само към съответните редове

Трябва да е много по-бързо :

SELECT d.day, d.thisyr
     , count(t.exam_date) AS lastyr
FROM  (
   SELECT d.day::date, (d.day - '1 year'::interval)::date AS day0  -- for 2nd join
        , count(t.exam_date) AS thisyr
   FROM   generate_series('2013-01-01'::date
                        , '2013-01-31'::date  -- last week overlaps with Feb.
                        , '7 days'::interval) d(day)  -- returns timestamp
   LEFT   JOIN tbl t ON t.exam_date >= d.day::date
                    AND t.exam_date <  d.day::date + 7
   GROUP  BY d.day
   ) d
LEFT   JOIN tbl t ON t.exam_date >= d.day0      -- repeat with last year
                 AND t.exam_date <  d.day0 + 7
GROUP  BY d.day, d.thisyr
ORDER  BY d.day;

Това е със седмици, започващи от 1 януари, както в оригинала. Както беше коментирано, това води до няколко несъответствия:Седмиците започват в различен ден всяка година и тъй като прекъсваме в края на годината, последната седмица от годината се състои само от 1 или 2 дни (високосна година).

Същото със седмиците по ISO

В зависимост от изискванията, помислете за ISO седмици вместо това, които започват в понеделник и винаги обхващат 7 дни. Но преминават границата между годините. По документация за EXTRACT() :

Горната заявка е пренаписана с ISO седмици:

SELECT w AS isoweek
     , day::text  AS thisyr_monday, thisyr_ct
     , day0::text AS lastyr_monday, count(t.exam_date) AS lastyr_ct
FROM  (
   SELECT w, day
        , date_trunc('week', '2012-01-04'::date)::date + 7 * w AS day0
        , count(t.exam_date) AS thisyr_ct
   FROM  (
      SELECT w
           , date_trunc('week', '2013-01-04'::date)::date + 7 * w AS day
      FROM   generate_series(0, 4) w
      ) d
   LEFT   JOIN tbl t ON t.exam_date >= d.day
                    AND t.exam_date <  d.day + 7
   GROUP  BY d.w, d.day
   ) d
LEFT   JOIN tbl t ON t.exam_date >= d.day0     -- repeat with last year
                 AND t.exam_date <  d.day0 + 7
GROUP  BY d.w, d.day, d.day0, d.thisyr_ct
ORDER  BY d.w, d.day;

4 януари винаги е в първата ISO седмица на годината. Така че този израз получава датата понеделник на първата ISO седмица от дадената година:

date_trunc('week', '2012-01-04'::date)::date

Опростете с EXTRACT()

Тъй като седмиците по ISO съвпадат с номерата на седмиците, върнати от EXTRACT() , можем да опростим заявката. Първо, кратка и проста форма:

SELECT w AS isoweek
     , COALESCE(thisyr_ct, 0) AS thisyr_ct
     , COALESCE(lastyr_ct, 0) AS lastyr_ct
FROM   generate_series(1, 5) w
LEFT   JOIN (
   SELECT EXTRACT(week FROM exam_date)::int AS w, count(*) AS thisyr_ct
   FROM   tbl
   WHERE  EXTRACT(isoyear FROM exam_date)::int = 2013
   GROUP  BY 1
   ) t13  USING (w)
LEFT   JOIN (
   SELECT EXTRACT(week FROM exam_date)::int AS w, count(*) AS lastyr_ct
   FROM   tbl
   WHERE  EXTRACT(isoyear FROM exam_date)::int = 2012
   GROUP  BY 1
   ) t12  USING (w);

Оптимизирана заявка

Същото с повече подробности и оптимизирано за ефективност

WITH params AS (          -- enter parameters here, once 
   SELECT date_trunc('week', '2012-01-04'::date)::date AS last_start
        , date_trunc('week', '2013-01-04'::date)::date AS this_start
        , date_trunc('week', '2014-01-04'::date)::date AS next_start
        , 1 AS week_1
        , 5 AS week_n     -- show weeks 1 - 5
   )
SELECT w.w AS isoweek
     , p.this_start + 7 * (w - 1) AS thisyr_monday
     , COALESCE(t13.ct, 0) AS thisyr_ct
     , p.last_start + 7 * (w - 1) AS lastyr_monday
     , COALESCE(t12.ct, 0) AS lastyr_ct
FROM params p
   , generate_series(p.week_1, p.week_n) w(w)
LEFT   JOIN (
   SELECT EXTRACT(week FROM t.exam_date)::int AS w, count(*) AS ct
   FROM   tbl t, params p
   WHERE  t.exam_date >= p.this_start      -- only relevant dates
   AND    t.exam_date <  p.this_start + 7 * (p.week_n - p.week_1 + 1)::int
-- AND    t.exam_date <  p.next_start      -- don't cross over into next year
   GROUP  BY 1
   ) t13  USING (w)
LEFT   JOIN (                              -- same for last year
   SELECT EXTRACT(week FROM t.exam_date)::int AS w, count(*) AS ct
   FROM   tbl t, params p
   WHERE  t.exam_date >= p.last_start
   AND    t.exam_date <  p.last_start + 7 * (p.week_n - p.week_1 + 1)::int
-- AND    t.exam_date <  p.this_start
   GROUP  BY 1
   ) t12  USING (w);

Това трябва да е много бързо с поддръжка на индекс и може лесно да се адаптира към избрани интервали. Неявният JOIN LATERAL за generate_series() в последната заявка изисква Postgres 9.3 .

SQL Fiddle.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Perl - DBI и .pgpass

  2. Какво е суперпотребител на postgres

  3. Грешка в Django South:AttributeError:обектът „DateTimeField“ няма атрибут „model“

  4. Функция SUM() в PostgreSQL

  5. Postgresql - не може да се премахне базата данни поради някои автоматични връзки към DB