PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Całkowita liczba rekordów na tydzień

Prostym podejściem byłoby rozwiązanie tego za pomocą CROSS JOIN, jak pokazano w @jpw. Istnieją jednak pewne ukryte problemy :

  1. wydajność bezwarunkowego CROSS JOIN pogarsza się szybko wraz ze wzrostem liczby rzędów. Łączna liczba wierszy jest mnożona przez liczbę tygodni, w których testujesz, zanim ta ogromna tabela pochodna będzie mogła zostać przetworzona w agregacji. Indeksy nie mogą pomóc.

  2. Rozpoczęcie tygodni od 1 stycznia prowadzi do niespójności. Tygodnie ISO może być alternatywą. Zobacz poniżej.

Wszystkie poniższe zapytania intensywnie wykorzystują indeks w exam_date . Upewnij się, że go masz.

Dołącz tylko do odpowiednich wierszy

Powinno być znacznie szybsze :

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;

To jest z tygodniami zaczynającymi się od 1 stycznia, tak jak w oryginale. Jak skomentowano, powoduje to kilka niespójności:Tygodnie zaczynają się w innym dniu każdego roku, a ponieważ odcinamy się pod koniec roku, ostatni tydzień roku składa się tylko z 1 lub 2 dni (rok przestępny).

To samo z tygodniami ISO

W zależności od wymagań rozważ tygodnie ISO zamiast tego, które zaczynają się w poniedziałki i zawsze obejmują 7 dni. Ale przekraczają granicę lat. Zgodnie z dokumentacją dotyczącą EXTRACT() :

Powyższe zapytanie przepisane z tygodniami 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 stycznia jest zawsze w pierwszym tygodniu ISO w roku. Zatem to wyrażenie otrzymuje datę poniedziałku pierwszego tygodnia ISO w danym roku:

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

Uprość dzięki EXTRACT()

Ponieważ tygodnie ISO pokrywają się z numerami tygodni zwracanymi przez EXTRACT() , możemy uprościć zapytanie. Najpierw krótki i prosty formularz:

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);

Zoptymalizowane zapytanie

To samo z większą ilością szczegółów i zoptymalizowane pod kątem wydajności

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);

Powinno to być bardzo szybkie z obsługą indeksów i można je łatwo dostosować do wybranych interwałów. Niejawne JOIN LATERAL dla generate_series() w ostatnim zapytaniu wymaga Postgres 9.3 .

Skrzypce SQL.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wyszukiwanie pełnotekstowe Postgresql dla podobnych słów

  2. Postgres wybierając aktualne dane godzinowe

  3. Jak wycofać dataframe.to_sql w Pythonie w SQLAlchemy?

  4. Wybierz nie zwraca wartości Postgres-11.4

  5. Jak poprawić wydajność podzapytania?