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

Вземете пагинирани редове и общ брой в една заявка

Най-напред:виеможете използвайте резултатите от CTE многократно в една и съща заявка, това е основна характеристика на CTE .) Това, което имате, ще работи по следния начин (докато все още използвате CTE само веднъж):

WITH cte AS (
   SELECT * FROM (
      SELECT *, row_number()  -- see below
                OVER (PARTITION BY person_id
                      ORDER BY submission_date DESC NULLS LAST  -- see below
                             , last_updated DESC NULLS LAST  -- see below
                             , id DESC) AS rn
      FROM  tbl
      ) sub
   WHERE  rn = 1
   AND    status IN ('ACCEPTED', 'CORRECTED')
   )
SELECT *, count(*) OVER () AS total_rows_in_cte
FROM   cte
LIMIT  10
OFFSET 0;  -- see below

Предупреждение 1:rank()

rank() може да върне множество редове на person_id с rank = 1 . DISTINCT ON (person_id) (както Gordon предостави) е приложим заместител на row_number() - което работи за вас, като допълнителна информация е изяснена. Вижте:

Предупреждение 2:ORDER BY submission_date DESC

Нито submission_date нито last_updated са дефинирани NOT NULL . Може да има проблем с ORDER BY submission_date DESC, last_updated DESC ... Вижте:

Трябва ли тези колони наистина да са NOT NULL ?

Вие отговорихте:

Празни низове не са разрешени за тип date . Поддържайте колоните nullable. NULL е правилната стойност за тези случаи. Използвайте NULLS LAST както е показано, за да избегнете NULL се сортира отгоре.

Предупреждение 3:OFFSET

Ако OFFSET е равен или по-голям от броя на редовете, върнати от CTE, получавате няма ред , така че също няма общ брой. Вижте:

Временно решение

Като разгледаме всички предупреждения досега и въз основа на добавената информация, може да стигнем до тази заявка:

WITH cte AS (
   SELECT DISTINCT ON (person_id) *
   FROM   tbl
   WHERE  status IN ('ACCEPTED', 'CORRECTED')
   ORDER  BY person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY person_id  -- ?? see below
   LIMIT  10
   OFFSET 0
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(total_rows_in_cte) ON true;

Сега CTE е всъщност използвана два пъти. RIGHT JOIN гарантира, че получаваме общия брой, независимо от OFFSET . DISTINCT ON трябва да изпълнява ОК за само няколко реда на (person_id) в основната заявка.

Но имате широки редове. Колко широк е средно? Заявката вероятно ще доведе до последователно сканиране на цялата таблица. Индексите няма да помогнат (много). Всичко това ще остане изключително неефективно за страниране . Вижте:

Не можете да включите индекс за страниране, тъй като той се основава на извлечената таблица от CTE. И вашите действителни критерии за сортиране за страниране все още не са ясни (ORDER BY id ?). Ако странирането е целта, отчаяно се нуждаете от различен стил на заявка. Ако се интересувате само от първите няколко страници, все още имате нужда от различен стил на заявка. Най-доброто решение зависи от информацията, която все още липсва във въпроса ...

Радиално по-бързо

За вашата актуализирана цел:

(Игнориране на "за определени критерии за филтриране, тип, план, състояние" за простота.)

И:

Въз основа на тези два специализирани индекса :

CREATE INDEX ON tbl (submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST)
WHERE  status IN ('ACCEPTED', 'CORRECTED'); -- optional

CREATE INDEX ON tbl (person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST);

Изпълнете тази заявка:

WITH RECURSIVE cte AS (
   (
   SELECT t  -- whole row
   FROM   tbl t
   WHERE  status IN ('ACCEPTED', 'CORRECTED')
   AND    NOT EXISTS (SELECT FROM tbl
                      WHERE  person_id = t.person_id 
                      AND   (  submission_date,   last_updated,   id)
                          > (t.submission_date, t.last_updated, t.id)  -- row-wise comparison
                      )
   ORDER  BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
   LIMIT  1
   )

   UNION ALL
   SELECT (SELECT t1  -- whole row
           FROM   tbl t1
           WHERE ( t1.submission_date, t1.last_updated, t1.id)
               < ((t).submission_date,(t).last_updated,(t).id)  -- row-wise comparison
           AND    t1.status IN ('ACCEPTED', 'CORRECTED')
           AND    NOT EXISTS (SELECT FROM tbl
                              WHERE  person_id = t1.person_id 
                              AND   (   submission_date,    last_updated,    id)
                                  > (t1.submission_date, t1.last_updated, t1.id)  -- row-wise comparison
                              )
           ORDER  BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
           LIMIT  1)
   FROM   cte c
   WHERE  (t).id IS NOT NULL
   )
SELECT (t).*
FROM   cte
LIMIT  10
OFFSET 0;

Всеки набор от скоби тук е задължителен.

Това ниво на сложност би трябвало да извлече относително малък набор от най-горни редове радикално по-бързо чрез използване на дадените индекси и без последователно сканиране. Вижте:

submission_date най-вероятно трябва да е тип timestamptz или date , а не character varying(255) - което във всеки случай е дефиниция на нечетен тип в Postgres. Вижте:

Много повече подробности може да бъдат оптимизирани, но това излиза извън контрол. Може да обмислите професионална консултация.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Избирането от pg_catalog.pg_settings се забави след актуализиране до PostgreSQL 12 (windows)

  2. Връзка грешка не съществува

  3. как да съхранявате PostgreSQL jsonb с помощта на SpringBoot + JPA?

  4. SQL:как да изберете реда с най-известните стойности?

  5. Извличане на редове от множество таблици с UNION ALL или използване на една таблица в производството?