LIMIT / OFFSET ile bir sorgu çalıştırın ve ayrıca toplam satır sayısını alın


103

Sayfalandırma amacıyla, LIMITve OFFSETyan tümceleriyle bir sorgu çalıştırmaya ihtiyacım var . Ancak, bu sorgu tarafından LIMITve OFFSETyan tümceleri olmadan döndürülecek satır sayısına da ihtiyacım var .

Koşmak istiyorum:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?

Ve:

SELECT COUNT(*) FROM table WHERE /* whatever */

Aynı zamanda. Bunu yapmanın bir yolu var mı, özellikle de Postgres'in her ikisini de ayrı ayrı çalıştırmaktan daha hızlı olması için onu optimize etmesine izin veren bir yol var mı?


Yanıtlar:


179

Evet. Basit bir pencere işlevi ile:

SELECT *, count(*) OVER() AS full_count
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
OFFSET ?
LIMIT  ?

Maliyetin toplam sayı olmadan önemli ölçüde daha yüksek olacağını, ancak yine de tipik olarak iki ayrı sorudan daha ucuz olacağını unutmayın. Postgres her iki şekilde de tüm satırları saymak zorundadır , bu da uygun satırların toplam sayısına bağlı olarak bir maliyet getirir. Detaylar:

Bununla birlikte , Dani'nin belirttiği gibiOFFSET , en azından temel sorgudan döndürülen satır sayısı kadar büyük olduğunda , hiçbir satır döndürülmez. Yani biz de anlamıyoruz full_count.

Bu kabul edilebilir değilse, her zaman tam sayımı döndürmek için olası bir geçici çözüm bir CTE ve bir OUTER JOIN:

WITH cte AS (
   SELECT *
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY col1
   LIMIT  ?
   OFFSET ?
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(full_count) ON true;

Çok büyükse full_counteklenen bir NULL değer satırı alırsınız OFFSET. Aksi takdirde, ilk sorguda olduğu gibi her satıra eklenir.

Tüm NULL değerlere sahip bir satır olası geçerli bir offset >= full_countsonuçsa, boş satırın başlangıç noktasını kontrol etmeniz gerekir .

Bu yine de temel sorguyu yalnızca bir kez yürütür. Ancak sorguya daha fazla ek yük ekler ve yalnızca sayı için temel sorguyu tekrar etmekten daha azsa ödeme yapar.

Son sıralama düzenini destekleyen dizinler mevcutsa, ORDER BYCTE'ye (fazlalık olarak) dahil etmek faydalı olabilir .


3
Hem LIMIT hem de koşullara göre, döndürülecek satırlarımız var, ancak verilen ofset ile sonuç döndürmeyecek. Bu durumda satır sayısını nasıl elde edebiliriz?
Dani Mathew

çok güzel, teşekkürler, sayfalandırma, datatables kullanırken harika çalışıyor, sadece bunu sql'inizin başına ekleyin ve kullanın, toplam sayım için fazladan bir sorgu kaydedin.
Ahmed Sunny

Sayma, sorguda bir girdi parametresi aracılığıyla dinamik olarak etkinleştirilebiliyorsa, bu konuyu biraz daha açabilir misiniz? Benzer bir gereksinimim var ancak kullanıcı satır içi sayımı isteyip istemediğine karar veriyor.
julealgon

1
@julealgon: Lütfen tanımlayıcı ayrıntılarla yeni bir soru başlatın . Bağlam için her zaman buna bağlantı verebilir ve isterseniz geri bağlantı (ve dikkatimi çekmek) için buraya bir yorum ekleyebilirsiniz.
Erwin Brandstetter

1
@JustinL .: Eklenen ek yük yalnızca nispeten ucuz temel sorgular için önemli olmalıdır. Ayrıca Postgres 12, CTE performansını birçok yönden iyileştirmiştir. (Bu CTE hala MATERIALIZEDvarsayılan olarak iki kez referans gösteriliyor olsa da )
Erwin Brandstetter

0

edit: bu cevap, filtrelenmemiş tabloyu alırken geçerlidir. Birine yardım etmesi durumunda izin vereceğim, ancak ilk soruyu tam olarak cevaplamayabilir.

Doğru bir değere ihtiyacınız varsa, Erwin Brandstetter'ın cevabı mükemmel. Bununla birlikte, büyük masalarda genellikle yalnızca oldukça iyi bir yaklaşıma ihtiyacınız vardır. Postgres size tam da bunu verir ve her satırı değerlendirmek zorunda kalmayacağı için çok daha hızlı olacaktır:

SELECT *
FROM (
    SELECT *
    FROM tbl
    WHERE /* something */
    ORDER BY /* something */
    OFFSET ?
    LIMIT ?
    ) data
RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;

Aslında bunu dışsallaştırmanın RIGHT JOINveya standart bir sorguda olduğu gibi elde etmenin bir avantajı olup olmadığından emin değilim . Bazı testleri hak ederdi.

SELECT t.*, pgc.reltuples AS total_count
FROM tbl as t
RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl'
WHERE /* something */
ORDER BY /* something */
OFFSET ?
LIMIT ?

2
Hızlı sayım tahmini hakkında: stackoverflow.com/a/7945274/939860 Dediğin gibi: tüm tabloyu alırken geçerlidir - bu, WHEREsorgularınızdaki cümle ile çelişir . İkinci sorgu, mantıksal olarak yanlıştır (DB'deki her tablo için bir satır alır ) - ve düzeltildiğinde daha pahalıdır.
Erwin Brandstetter

0

Erwin Brandstetter'ın cevabı bir cazibe gibi çalışırken , aşağıdaki gibi her satırdaki toplam satır sayısını döndürür :

col1 - col2 - col3 - total
--------------------------
aaaa - aaaa - aaaa - count
bbbb - bbbb - bbbb - count
cccc - cccc - cccc - count

Aşağıdaki gibi, toplam sayıyı yalnızca bir kez döndüren bir yaklaşım kullanmayı düşünebilirsiniz :

total - rows
------------
count - [{col1: 'aaaa'},{col2: 'aaaa'},{col3: 'aaaa'}
         {col1: 'bbbb'},{col2: 'bbbb'},{col3: 'bbbb'}
         {col1: 'cccc'},{col2: 'cccc'},{col3: 'cccc'}]

SQL sorgusu:

SELECT 
    (SELECT COUNT(*) FROM table) as count, 
    (SELECT json_agg(t.*) FROM (
        SELECT * FROM table
        WHERE /* whatever */
        ORDER BY col1
        OFFSET ?
        LIMIT ?
    ) AS t) AS rows 

-6

Geri dönen sonucun toplam satır sayısını elde etmek için aynı sorguyu iki kez çağırmak kötü bir uygulamadır. Yürütme süresi alacak ve sunucu kaynağını boşa harcayacaktır.

Daha da iyisi, SQL_CALC_FOUND_ROWSsorguda MySQL'e sınır sorgusu sonuçlarıyla birlikte toplam satır sayısı sayısını getirmesini söyleyen kullanabilirsiniz .

Örnek set:

SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10;

SELECT FOUND_ROWS();

Yukarıdaki SQL_CALC_FOUND_ROWSSorguda, Geriye kalan gerekli sorguya sadece seçenek ekleyin ve ikinci satırı çalıştırın, yani SELECT FOUND_ROWS()sonuç kümesindeki satır sayısını o ifade tarafından döndürülür.


1
Çözüm, mysql değil postgres gerektirir.
MuffinMan

@MuffinMan, aynısını mysql'de de kullanabilirsiniz. MYSQL 4.0'dan beri sorguda SQL_CALC_FOUND_ROWS seçeneği kullanılmaktadır. Ancak MYSQL 8.0'dan itibaren kullanımdan kaldırılmıştır.
Mohd Rashid

İlgili değil. Bu soru yıllar önce cevaplandı. Katkıda bulunmak istiyorsanız, aynı konuyla ancak MySQL'e özgü yeni bir soru gönderin.
MuffinMan

her zaman alakalı olun
Ali Hussain

-15

Hayır.

Kaputun altında yeterince karmaşık makineyle onları tek tek çalıştırarak teorik olarak kazanabileceğiniz belki de bazı küçük kazançlar vardır. Ancak, kaç satırın bir koşulla eşleştiğini bilmek istiyorsanız, bunları yalnızca SINIRLI bir alt küme yerine saymanız gerekir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.