Haddeleme toplamı / sayısı / tarih aralığındaki ortalama


20

18 aydan uzun bir süredir 1.000 kişiyi kapsayan bir işlem veritabanında, 30 günlük dönemde entity_idişlem tutarlarının toplamı ve işlemlerinin COUNT değeri ile her 30 günlük dönemde bir grup oluşturmak için bir sorgu çalıştırmak ve Verileri daha sonra sorgulayabileceğim şekilde döndürür. Bir sürü testten sonra, bu kod istediğimin çoğunu başarıyor:

SELECT id, trans_ref_no, amount, trans_date, entity_id,
    SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
    COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
  FROM transactiondb;

Ve ben gibi bir şey yapılandırılmış daha büyük bir sorguda kullanacağım:

SELECT * FROM (
  SELECT id, trans_ref_no, amount, trans_date, entity_id,
      SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
      COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
    FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;

Bu sorgunun kapsamadığı durum, işlem sayılarının birkaç ay içerdiği, ancak yine de birbirinin 30 gün içinde olacağı durumudur. Postgres ile bu tür bir sorgu mümkün mü? Eğer öyleyse, herhangi bir girişi hoş geldiniz. Diğer konuların birçoğu " çalışan " toplamaları tartışıyor , yuvarlamıyor .

Güncelleştirme

CREATE TABLEkomut:

CREATE TABLE transactiondb (
    id integer NOT NULL,
    trans_ref_no character varying(255),
    amount numeric(18,2),
    trans_date date,
    entity_id integer
);

Örnek veriler burada bulunabilir . PostgreSQL 9.1.16 kullanıyorum.

İdeal çıkış yer alacağını SUM(amount)ve COUNT()yuvarlanan 30 günlük süre içinde tüm işlemlerin. Bu resme bakın, örneğin:

İdeal olarak bir "kümeye" dahil edilecek ancak kümemin aya göre statik olması nedeniyle olmayan satırlara örnek.

Yeşil tarih vurgulaması, sorguma nelerin dahil edildiğini gösterir. Sarı satır vurgulaması, setin bir parçası olmak istediğim kayıtları gösterir.

Önceki okuma:


1
By every possible 30-day period by entity_idsize dönemi başlayabilir anlamına herhangi , bir gün bir (eksik olmayan) yıl içinde bu kadar 365 olası dönemlerini? Veya gerçek bir işlemin olduğu günleri yalnızca herhangi bir dönem için tek başına bir dönem olarak mı değerlendirmek istiyorsunuz entity_id ? Her iki durumda da, lütfen tablo tanımınızı, Postgres sürümünüzü, bazı örnek verileri ve örnek için beklenen sonucu sağlayın.
Erwin Brandstetter

Teorik olarak, herhangi bir gün demek istedim, ama pratikte işlemin olmadığı günleri düşünmeye gerek yok. Örnek verileri ve tablo tanımını gönderdim.
tufelkinder

Böylece , her bir gerçek işlemden başlayarakentity_id 30 günlük bir pencerede aynı satırları biriktirmek istersiniz . Aynı işlem için birden fazla işlem olabilir mi veya bu kombinasyon benzersiz mi tanımlanmış? Tablo tanımınızın PK kısıtlaması yok veya kısıtlaması var, ancak kısıtlamalar eksik görünüyor ...(trans_date, entity_id)UNIQUE
Erwin Brandstetter

Tek kısıt idbirincil anahtardadır. Varlık başına günlük birden çok işlem olabilir.
tufelkinder

Veri dağıtımı hakkında: çoğu gün boyunca (varlık_kimliği başına) giriş var mı?
Erwin Brandstetter

Yanıtlar:


26

Sahip olduğunuz sorgu

Sorgunuzu bir WINDOWyan tümce kullanarak basitleştirebilirsiniz , ancak bu, sözdizimini kısaltır, sorgu planını değiştirmez.

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date)
             ORDER BY trans_date
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
  • Ayrıca biraz daha hızlı kullanarak count(*)beri, idkesinlikle tanımlanır NOT NULL?
  • Ve ORDER BY entity_idzaten olduğundan beri ihtiyacın yokPARTITION BY entity_id

Yine de basitleştirebilirsiniz: Pencere tanımına
hiç eklemeyin ORDER BY, bu sorgunuzla ilgili değildir. Ardından, özel bir pencere çerçevesi tanımlamanız gerekmez:

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date);

Daha basit, ne daha hızlı, ama yine de sadece daha iyi bir versiyonu var olan statik ay.

İsteyebileceğiniz sorgu

... açıkça tanımlanmadı, bu yüzden bu varsayımları temel alacağım:

Herhangi bir ilk ve son işlemdeki her 30 günlük dönem için işlemleri ve tutarı sayın entity_id. Etkinlik olmadan baştaki ve sondaki dönemleri hariç tutun, ancak bu dış sınırlar dahilinde olası tüm 30 günlük süreleri dahil edin.

SELECT entity_id, trans_date
     , COALESCE(sum(daily_amount) OVER w, 0) AS trans_total
     , COALESCE(sum(daily_count)  OVER w, 0) AS trans_count
FROM  (
   SELECT entity_id
        , generate_series (min(trans_date)::timestamp
                         , GREATEST(min(trans_date), max(trans_date) - 29)::timestamp
                         , interval '1 day')::date AS trans_date
   FROM   transactiondb 
   GROUP  BY 1
   ) x
LEFT JOIN (
   SELECT entity_id, trans_date
        , sum(amount) AS daily_amount, count(*) AS daily_count
   FROM   transactiondb
   GROUP  BY 1, 2
   ) t USING (entity_id, trans_date)
WINDOW w AS (PARTITION BY entity_id ORDER BY trans_date
             ROWS BETWEEN CURRENT ROW AND 29 FOLLOWING);

entity_idToplamalarınız ve trans_datedönemin ilk günü (dahil) olmak üzere her biri için 30 günlük süreleri listeler . Her bir satırın değerlerini almak için bir kez daha temel tabloya katılın ...

Temel zorluk burada tartışılanla aynıdır:

Bir pencerenin çerçeve tanımı geçerli satırın değerlerine bağlı olamaz.

Daha ziyade girdi generate_series()ile arayın timestamp:

Aslında istediğiniz sorgu

Soru güncelleme ve tartışmadan sonra: Her bir gerçek işlemden başlayarak 30 günlük bir pencerede
aynı satırları biriktirin entity_id.

Verileriniz seyrek olarak dağıtıldığından, Postgres 9.1'in henüz katılmadığından, bir aralık koşulu ile kendi kendine katılımı çalıştırmak daha verimli olmalıdır LATERAL:

SELECT t0.id, t0.amount, t0.trans_date, t0.entity_id
     , sum(t1.amount) AS trans_total, count(*) AS trans_count
FROM   transactiondb t0
JOIN   transactiondb t1 USING (entity_id)
WHERE  t1.trans_date >= t0.trans_date
AND    t1.trans_date <  t0.trans_date + 30  -- exclude upper bound
-- AND    t0.entity_id = 114284  -- or pick a single entity ...
GROUP  BY t0.id  -- is PK!
ORDER  BY t0.trans_date, t0.id

SQL Fiddle.

Yuvarlanan bir pencere, çoğu güne ait verilerle yalnızca (performans açısından) anlamlı olabilir.

Bu does not üzerine agrega çiftleri (trans_date, entity_id)günde ancak aynı günün tüm satırları her zaman 30 günlük bir zaman aralığında yer almaktadır.

Büyük bir tablo için, böyle bir kaplama dizini biraz yardımcı olabilir:

CREATE INDEX transactiondb_foo_idx
ON transactiondb (entity_id, trans_date, amount);

Son sütun amountyalnızca, yalnızca dizin içeren taramaları alırsanız kullanışlıdır. Başka bırakın.

Ama yine de tüm tabloyu seçerken kullanılmayacak. Küçük bir alt küme için sorguları destekleyecektir.


Bu gerçekten iyi görünüyor, şimdi veriler üzerinde test ve sorgunuzun aslında yaptığı her şeyi anlamaya çalışıyorum ...
tufelkinder

@tufelkinder: Güncellenmiş soru için bir çözüm eklendi.
Erwin Brandstetter

Şimdi gözden geçiriyorum. SQL Fiddle'da çalıştığını merak ediyorum ... Doğrudan transactiondb üzerinde çalıştırmayı denediğimde, hata column "t0.amount" must appear in the GROUP BY clause...
veriyor

@tufelkinder: Test vakasını 100 satıra kadar kestim. sqlfiddle test verilerinin boyutunu sınırlar. Jake (yazar) birkaç ay önce limit sınırını azalttı, böylece site daha az durdu.
Erwin Brandstetter

1
Tam veritabanında test etmek için gereken gecikme için özür dileriz. Cevabınız her zamanki gibi son derece derinlemesine ve eğiticiydi. Teşekkür ederim!
tufelkinder
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.