วิธีที่รวดเร็วในการค้นหาจำนวนแถวของตารางใน 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: ไม่รับประกันว่า PK ที่สำรองไว้ตามลำดับจะอยู่ติดกันแถวสามารถลบออกได้หรืออาจมีช่องว่างที่เกิดจากธุรกรรมที่ถูกยกเลิก
สั้นเกินไป

การอัปเดตของคุณดูเหมือนจะขัดแย้งกับคำถามเดิมของคุณ ... คุณจำเป็นต้องทราบจำนวนแถวที่แน่นอนหรือคุณจำเป็นต้องทราบจำนวนที่แน่นอนหากต่ำกว่าเกณฑ์?
Flimzy

1
@ RenatoDinhaniConceição: คุณสามารถอธิบายปัญหาที่แน่นอนที่คุณพยายามแก้ไขได้หรือไม่? ฉันคิดว่าคำตอบของฉันด้านล่างช่วยแก้ปัญหาที่คุณพูดในตอนแรกได้ การอัปเดตทำให้ดูเหมือนว่าคุณต้องการนับ (*) เช่นเดียวกับฟิลด์อื่น ๆ มันจะช่วยได้ถ้าคุณสามารถอธิบายสิ่งที่คุณพยายามทำ ขอบคุณ.
Ritesh

คำตอบ:


226

การนับแถวในตารางขนาดใหญ่เป็นที่ทราบกันดีว่า PostgreSQL ช้า เพื่อให้ได้จำนวนที่แม่นยำมันได้ทำนับเต็มรูปแบบของแถวเนื่องจากลักษณะของMVCC มีวิธีที่จะเร่งให้เร็วขึ้นอย่างมากหากการนับไม่จำเป็นต้องตรงตามที่เห็นในกรณีของคุณ

แทนที่จะได้รับการนับที่แน่นอน ( ช้าด้วยตารางขนาดใหญ่):

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

คุณจะได้ค่าประมาณใกล้เคียงเช่นนี้ ( เร็วมาก ):

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

การปิดประมาณการขึ้นอยู่กับว่าคุณทำงานANALYZEเพียงพอหรือไม่ มันมักจะใกล้มาก
ดูPostgreSQL วิกิพีเดียคำถามที่พบบ่อย
หรือหน้าวิกิพีเดียทุ่มเทสำหรับการนับ (*) ผลการดำเนินงาน

ยังดีกว่า

บทความในวิกิพีเดีย PostgreSQL จะเป็นบิตเลอะเทอะ โดยไม่สนใจความเป็นไปได้ที่อาจมีหลายตารางที่มีชื่อเดียวกันในฐานข้อมูลเดียว - ในสคีมาที่ต่างกัน ในการพิจารณาเรื่องนั้น:

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;

Postgres หยุดนับเกินขีด จำกัด ที่กำหนดจริง ๆ แล้วคุณจะได้รับการนับที่แน่นอนและปัจจุบันมากถึงnแถว (500000 ในตัวอย่าง) และnอย่างอื่น pg_classแม้ว่าจะไม่เร็วเท่าที่คาดการณ์ไว้


8
ในที่สุดฉันก็อัปเดตเพจ Postgres Wiki ด้วยแบบสอบถามที่ปรับปรุงแล้ว
Erwin Brandstetter

5
ด้วย 9.5 การได้รับการประมาณการอย่างรวดเร็วควรเป็นไปได้โดยใช้tablesampleประโยค: เช่นselect count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name

1
@JeffWidman: ค่าประมาณทั้งหมดนี้อาจมากกว่าจำนวนแถวจริงด้วยเหตุผลหลายประการ อย่างน้อยการลบอาจเกิดขึ้นในระหว่างนี้
Erwin Brandstetter

2
@ErwinBrandstetter ทราบว่าคำถามนี้เก่า แต่ถ้าคุณรวมข้อความค้นหาไว้ในแบบสอบถามย่อยแล้วขีด จำกัด นี้จะยังคงมีประสิทธิภาพหรือไม่หรือจะเรียกใช้การสืบค้นย่อยทั้งหมดจากนั้น จำกัด ในแบบสอบถามภายนอก SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(ฉันถามเพราะฉันกำลังพยายามนับจากข้อความค้นหาตามอำเภอใจที่อาจมีประโยค จำกัด อยู่แล้ว)
Nicholas Erdenberger

1
@NicholasErdenberger: ขึ้นอยู่กับแบบสอบถามย่อย Postgres อาจต้องพิจารณาแถวมากกว่าขีด จำกัด อยู่ดี (เช่นเดียวกับORDER BY somethingในขณะที่ไม่สามารถใช้ดัชนีหรือกับฟังก์ชันรวม) นอกจากนั้นระบบจะประมวลผลเฉพาะแถวจำนวน จำกัด จากแบบสอบถามย่อย
Erwin Brandstetter

12

ฉันทำสิ่งนี้ครั้งเดียวในแอป postgres โดยเรียกใช้:

EXPLAIN SELECT * FROM foo;

จากนั้นตรวจสอบผลลัพธ์ด้วย regex หรือตรรกะที่คล้ายกัน สำหรับ SELECT * อย่างง่ายบรรทัดแรกของเอาต์พุตควรมีลักษณะดังนี้:

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

คุณสามารถใช้rows=(\d+)ค่านี้เป็นค่าประมาณคร่าวๆของจำนวนแถวที่จะถูกส่งกลับจากนั้นทำตามจริงก็ต่อSELECT COUNT(*)เมื่อค่าประมาณนั้นน้อยกว่า 1.5x เกณฑ์ของคุณ (หรือตัวเลขใดก็ตามที่คุณเห็นว่าเหมาะสมสำหรับแอปพลิเคชันของคุณ)

ขึ้นอยู่กับความซับซ้อนของข้อความค้นหาของคุณตัวเลขนี้อาจมีความแม่นยำน้อยลงเรื่อย ๆ อันที่จริงในแอปพลิเคชันของฉันเมื่อเราเพิ่มการรวมและเงื่อนไขที่ซับซ้อนมันกลายเป็นความไม่ถูกต้องดังนั้นมันจึงไร้ค่าโดยสิ้นเชิงแม้จะรู้ว่าภายใน 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;

โปรดทราบอย่างรวดเร็วว่าคุณต้องวิเคราะห์ตารางของคุณอย่างรวดเร็วเพื่อให้วิธีนี้ได้ผล
William Abma

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 table จะส่งกลับแถวเดียวเสมอ ไม่แน่ใจว่า LIMIT จะเพิ่มผลประโยชน์ที่นั่นได้อย่างไร
Chris Bednarski

@ChrisBednarski: ฉันตรวจสอบเวอร์ชัน Oracle ของคำตอบของฉันบนฐานข้อมูล Oracle มันใช้งานได้ดีและแก้สิ่งที่ฉันคิดว่าเป็นปัญหาของ OP (0.05 วินาทีcount(*)กับ rownum, 1 วินาทีโดยไม่ต้องใช้ rownum) ใช่SELECT count(*) cnt FROM tableจะส่งคืน 1 แถวเสมอ แต่ด้วยเงื่อนไข LIMIT จะส่งคืน "500001" เมื่อขนาดของตารางเกิน 500000 และ <size> เมื่อขนาดของตาราง <= 500000
Ritesh

2
แบบสอบถาม PostgreSQL ของคุณเป็นเรื่องไร้สาระโดยสิ้นเชิง ผิดทางไวยากรณ์และมีเหตุผล โปรดแก้ไขหรือลบออก
Erwin Brandstetter

@ErwinBrandstetter: ลบออกไม่ทราบว่า PostgreSQL แตกต่างกันมาก
พิธี

@allrite: ไม่ต้องสงสัยเลยว่าแบบสอบถาม Oracle ของคุณทำงานได้ดี LIMIT ทำงานแตกต่างกันไป ในระดับพื้นฐานจะ จำกัด จำนวนแถวที่ส่งกลับไปยังไคลเอนต์ไม่ใช่จำนวนแถวที่ถูกค้นหาโดยเครื่องมือฐานข้อมูล
Chris Bednarski

0

คอลัมน์ข้อความกว้างแค่ไหน?

ด้วย GROUP BY คุณไม่สามารถทำอะไรได้มากนักเพื่อหลีกเลี่ยงการสแกนข้อมูล (อย่างน้อยการสแกนดัชนี)

ฉันขอแนะนำ:

  1. หากเป็นไปได้ให้เปลี่ยนสคีมาเพื่อลบข้อมูลข้อความที่ซ้ำกัน ด้วยวิธีนี้การนับจะเกิดขึ้นในฟิลด์ Foreign Key ที่แคบในตาราง 'many'

  2. อีกวิธีหนึ่งคือการสร้างคอลัมน์ที่สร้างขึ้นพร้อมกับแฮชของข้อความจากนั้นจัดกลุ่มตามคอลัมน์แฮช อีกครั้งนี่คือการลดภาระงาน (สแกนผ่านดัชนีคอลัมน์แคบ ๆ )

แก้ไข:

คำถามเดิมของคุณไม่ตรงกับการแก้ไขของคุณ ฉันไม่แน่ใจว่าคุณทราบหรือไม่ว่า 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 คือตารางสั่งซื้อ (ดัชนีคลัสเตอร์)

วิธีการที่เร็วยิ่งขึ้น (แต่ไม่น่าเชื่อถือ) มีรายละเอียดที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.