Швидкий спосіб виявити кількість рядків таблиці в PostgreSQL


108

Мені потрібно знати кількість рядків у таблиці, щоб обчислити відсоток. Якщо загальний підрахунок перевищує деяку заздалегідь задану константу, я буду використовувати постійне значення. В іншому випадку я буду використовувати фактичну кількість рядків.

Я можу використовувати SELECT count(*) FROM table. Але якщо моє постійне значення становить 500 000, а у мене в таблиці 5 000 000 000 рядків, підрахунок усіх рядків витратить багато часу.

Чи можна припинити підрахунок, як тільки перестане моє постійне значення?

Точна кількість рядків мені потрібна лише до тих пір, поки вона буде нижче заданої межі. В іншому випадку, якщо кількість перевищує ліміт, я замість цього використовую граничне значення і хочу відповідь якомога швидше.

Щось на зразок цього:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;

5
Ви не можете просто спробувати вибрати перші n рядків, де n = константа + 1 ? Якщо він повертається більше, ніж ваш постійний, ви знаєте, що ви повинні використовувати свою константу, а якщо це не добре ви?
gddc

Чи є у вас таблиця особи або автоматичного збільшення
Sparky

1
@Sparky: ПК, що підтримуються послідовністю, не гарантується, що вони будуть суміжними, рядки можна видалити або можливі прогалини, викликані перерваними транзакціями.
mu занадто короткий

Здається, ваше оновлення суперечить вашому первинному питанню ... чи потрібно вам знати точну кількість рядків, чи вам потрібно знати лише точне число, якщо воно нижче за поріг?
Flimzy

1
@ RenatoDinhaniConceição: Можете чи ви пояснити Exact проблему ви намагаєтеся вирішити? Я думаю, що моя відповідь нижче вирішує те, що ви спочатку говорили, що це ваше питання. Оновлення робить його таким, як ви хочете підрахунку (*), а також багатьох інших полів. Це допоможе, якщо ви можете пояснити, що саме ви намагаєтеся зробити. Дякую.
Ritesh

Відповіді:


226

Як відомо, підрахунок рядків у великих таблицях у PostgreSQL є повільним. Щоб отримати точне число, необхідно виконати повну кількість рядків у зв'язку з характером MVCC . Існує спосіб суттєво прискорити це, якщо підрахунок не повинен бути точним, як здається у вашому випадку.

Замість отримання точного рахунку ( повільно з великими таблицями):

SELECT count(*) AS exact_count FROM myschema.mytable;

Ви отримуєте приблизну оцінку, як це ( надзвичайно швидко ):

SELECT reltuples::bigint AS estimate FROM pg_class where relname='mytable';

Наскільки близька оцінка, залежить від того, чи ANALYZEдостатньо ви біжите . Зазвичай це дуже близько.
Див . Поширені запитання про Wikigre PostgreSQL .
Або спеціальна сторінка вікі для виконання підрахунку (*) .

Ще краще

У статті в PostgreSQL Wiki це було трохи неакуратно . Він ігнорував можливість того, що в одній базі даних може бути кілька однойменних таблиць - в різних схемах. Для обліку цього:

SELECT c.reltuples::bigint AS estimate
FROM   pg_class c
JOIN   pg_namespace n ON n.oid = c.relnamespace
WHERE  c.relname = 'mytable'
AND    n.nspname = 'myschema'

Або ще краще

SELECT reltuples::bigint AS estimate
FROM   pg_class
WHERE  oid = 'myschema.mytable'::regclass;

Швидше, простіше, безпечніше, елегантніше. Дивіться посібник про типи ідентифікаторів об'єктів .

Використовуйте to_regclass('myschema.mytable')в Postgres 9.4 і новіших версій, щоб уникнути винятків для недійсних імен таблиць:


TABLESAMPLE SYSTEM (n) у Postgres 9.5+

SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);

Як і прокоментував @a_horse , щойно доданий пункт для SELECTкоманди може бути корисним, якщо статистика в pg_classчомусь недостатня в поточному режимі. Наприклад:

  • Не autovacuumпрацює.
  • Відразу після великого INSERTабо DELETE.
  • TEMPORARYтаблиці (які не охоплені autovacuum).

Це враховує лише випадковий n % ( 1у прикладі) вибір блоків та підрахунок рядків у ньому. Більш великий зразок збільшує вартість і зменшує помилку, ваш вибір. Точність залежить від більшої кількості факторів:

  • Розподіл розміру рядків Якщо в даному блоці трапляється ширше звичайних рядків, кількість нижче, ніж зазвичай тощо.
  • Мертві кортежі або FILLFACTORзаймають простір на блок. Якщо нерівномірно розподілено по таблиці, оцінка може бути вимкнена.
  • Загальні помилки округлення.

У більшості випадків оцінка з боку pg_classбуде швидшою та точнішою.

Відповідь на актуальне запитання

По-перше, мені потрібно знати кількість рядків у цій таблиці, якщо загальний підрахунок перевищує деяку заздалегідь задану константу,

І чи це ...

... можливо в момент, коли підрахунок передасть моє постійне значення, він зупинить підрахунок (і не чекатиме закінчення підрахунку, щоб повідомити, що кількість рядків більша).

Так. Ви можете використовувати підзапит зLIMIT :

SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;

Постгреси насправді перестають рахувати понад задану межу, ви отримуєте точний та поточний підрахунок до n рядків (500000 у прикладі) та n в іншому випадку. pg_classХоча не так швидко, як оцінка в .


8
Зрештою я оновив сторінку Wiki Postgres із покращеним запитом.
Erwin Brandstetter

5
З 9.5 швидкість отримання оцінки повинна бути можливою за допомогою tablesampleпункту: напр.select count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name

1
@JeffWidman: Усі ці оцінки з різних причин можуть бути більшими за фактичну кількість рядків. Не в останню чергу, тим часом можуть бути і видалення.
Ервін Брандстетер

2
@ErwinBrandstetter розуміє, що це питання давнє, але якщо ви загорнули запит у підзапит, то чи обмеження все-таки було б ефективним, або весь підзапит буде виконаний, а потім обмежений у зовнішньому запиті. SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Запитую, тому що я намагаюся отримати підрахунок від довільного запиту, який, можливо, вже має застережне обмеження)
Nicholas Erdenberger

1
@NicholasErdenberger: Це залежить від підзапиту. Постгресу може знадобитися врахувати більше рядків, ніж межа (як, наприклад, ORDER BY somethingпоки він не може використовувати індекс, або з сукупними функціями). Крім цього, обробляється лише обмежена кількість рядків із підзапиту.
Ервін Брандстеттер

12

Я робив це один раз у додатку postgres, запустивши:

EXPLAIN SELECT * FROM foo;

Потім вивчаємо висновок за допомогою регулярного вираження або подібної логіки. Для простого SELECT * перший рядок виводу повинен виглядати приблизно так:

Seq Scan on uids  (cost=0.00..1.21 rows=8 width=75)

Ви можете використовувати це rows=(\d+)значення як приблизну оцінку кількості рядків, які будуть повернуті, а потім зробити фактичне лише SELECT COUNT(*)тоді, коли оцінка буде, наприклад, меншою, ніж у 1,5 раза пороговою (або будь-яке число, яке ви вважаєте, має сенс для вашої програми).

Залежно від складності вашого запиту, ця цифра може ставати все менш точною. Насправді, в моїй заяві, коли ми додавали приєднання та складні умови, це стало настільки неточним, що було абсолютно нікчемним, навіть знати про те, як за 100-ти рядків ми повернули б рядків, тому нам довелося відмовитися від цієї стратегії.

Але якщо ваш запит досить простий, що Pg може передбачити в межах розумного похибки, скільки рядків він поверне, він може працювати для вас.


2

Довідка взята з цього блогу.

Ви можете використовувати нижче для запиту, щоб знайти кількість рядків.

Використання pg_class:

 SELECT reltuples::bigint AS EstimatedCount
    FROM   pg_class
    WHERE  oid = 'public.TableName'::regclass;

Використання pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;

Просто швидко зауважте, що для цього методу потрібно ВАКУУМУВАТИ АНАЛІЗУВАТИ ваші таблиці.
Вільям Абма

1

В Oracle ви можете використовувати rownumобмеження кількості повернених рядків. Я здогадуюсь, подібна конструкція існує і в інших SQL. Отже, для прикладу, який ви навели, ви можете обмежити кількість повернутих рядків до 500001 і застосувати count(*)тоді:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)

1
SELECT count (*) cnt FROM таблиця завжди буде повертати один рядок. Не впевнений, як LIMIT збирається додати будь-яку перевагу там.
Кріс Беднарський

@ChrisBednarski: Я перевірив версію оракула своєї відповіді на Oracle db. Це чудово працює і вирішує те, що я вважав проблемою ОП (0,05 с count(*)з роунумом, 1 с без використання роунума). Так, SELECT count(*) cnt FROM tableзавжди повертається 1 рядок, але за умови LIMIT він поверне "500001", коли розмір таблиці перевищує 500000, а <розмір>, коли розмір таблиці <= 500000.
Ritesh

2
Ваш запит PostgreSQL - повна дурниця. Синтаксично і логічно неправильно. Виправте або видаліть його.
Ервін Брандстеттер

@ErwinBrandstetter: Видалено, не зрозумів, що PostgreSQL був таким іншим.
Ritesh

@allrite: без сумніву, ваш запит Oracle прекрасно працює. LIMIT працює інакше. На базовому рівні він обмежує кількість рядків, повернених клієнтові, а не кількість рядків, запитуваних двигуном бази даних.
Кріс Беднарський

0

Наскільки широкий стовпець тексту?

З GROUP BY не дуже багато ви можете зробити, щоб уникнути сканування даних (принаймні, сканування індексів).

Я рекомендую:

  1. Якщо можливо, зміни схеми для видалення дублювання текстових даних. Таким чином, підрахунок відбуватиметься у вузькому зовнішньому ключовому полі таблиці "багато".

  2. Крім того, створити генерований стовпець з HASH тексту, а потім GROUP BY HASH. Знову ж таки, це зменшити навантаження (сканування через вузький індекс стовпця)

Редагувати:

Ваше первісне запитання не зовсім відповідало вашій редакції. Я не впевнений, чи знаєте ви, що COUNT при використанні GROUP BY поверне кількість елементів у групі, а не кількість елементів у всій таблиці.


0

Підрахунок можна отримати за поданим нижче запитом (без * або будь-яких імен стовпців).

select from table_name;

2
Це, здається, не швидше, ніж count(*).
Сонячно

-3

Для SQL Server (2005 або вище) швидкий та надійний метод:

SELECT SUM (row_count)
FROM sys.dm_db_partition_stats
WHERE object_id=OBJECT_ID('MyTableName')   
AND (index_id=0 or index_id=1);

Деталі про sys.dm_db_partition_stats пояснюються в MSDN

Запит додає рядки з усіх частин (можливо) розділеної таблиці.

index_id = 0 - це не упорядкована таблиця (Heap), а index_id = 1 - упорядкована таблиця (кластерний індекс)

Навіть більш швидкі (але ненадійні) методи тут детально описані .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.