PostgreSQL Çapraz Sorgu


196

PostgreSQL'de çapraz sorgular oluşturmayı bilen var mı?
Örneğin, aşağıdaki tablo var:

Section    Status    Count
A          Active    1
A          Inactive  2
B          Active    4
B          Inactive  5

Aşağıdaki crosstab dönmek için sorgu istiyorum:

Section    Active    Inactive
A          1         2
B          4         5

Mümkün mü?


1
Biraz farklı bir yapıya sahiptim ve bu örneği anlaması biraz zor buldum, bu yüzden bu stackoverflow.com/q/49051959/808723 hakkında düşünme tarzımı belgeledim . Belki herkes için yararlı.
GameScripting

Yanıtlar:


317

İşlevi sağlayan ek modülütablefunc her veritabanı için bir kez kurun crosstab(). Postgres 9.1'den beri CREATE EXTENSIONbunun için kullanabilirsiniz :

CREATE EXTENSION IF NOT EXISTS tablefunc;

Geliştirilmiş test senaryosu

CREATE TABLE tbl (
   section   text
 , status    text
 , ct        integer  -- "count" is a reserved word in standard SQL
);

INSERT INTO tbl VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                    , ('C', 'Inactive', 7);  -- ('C', 'Active') is missing

Basit form - eksik özellikler için uygun değil

crosstab(text)ile 1 giriş parametresi:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- needs to be "ORDER BY 1,2" here
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

İadeler:

Bölüm | Aktif | pasif
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C |      7 | - !!
  • Döküm ve yeniden adlandırma gerekmez.
  • Şunun için yanlış sonuca dikkat edin C: değer 7ilk sütun için doldurulur. Bazen, bu davranış istenir, ancak bu kullanım durumu için değil.
  • Basit form ayrıca sağlanan giriş sorgusundaki tam olarak üç sütunla sınırlıdır : satır_adı , kategori , değer . Aşağıdaki 2 parametreli alternatifte olduğu gibi fazladan sütunlar için yer yoktur .

Güvenli form

crosstab(text, text)ile 2 giriş parametreleri:

SELECT *
FROM   crosstab(
   'SELECT section, status, ct
    FROM   tbl
    ORDER  BY 1,2'  -- could also just be "ORDER BY 1" here

  , $$VALUES ('Active'::text), ('Inactive')$$
   ) AS ct ("Section" text, "Active" int, "Inactive" int);

İadeler:

Bölüm | Aktif | pasif
--------- + -------- + ----------
 A | 1 | 2
 B | 4 | 5
 C | |        7   - !!
  • İçin doğru sonucu not edin C.

  • İkinci parametre döner bir herhangi bir sorgu olabilir satır özellik başına sonunda sütun tanımının sırasını bulundu. Genellikle, temel tablodan aşağıdaki gibi farklı özellikleri sorgulamak istersiniz:

    'SELECT DISTINCT attribute FROM tbl ORDER BY 1'

    Bu kılavuzda.

    Yine de bir sütun tanımı listesindeki tüm sütunları (önceden tanımlanmış varyantlar hariç) hecelemeniz gerektiğinden, gösterildiği gibi bir ifadede kısa bir liste sağlamak genellikle daha etkilidir :crosstabN()VALUES

    $$VALUES ('Active'::text), ('Inactive')$$)

    Veya (kılavuzda değil):

    $$SELECT unnest('{Active,Inactive}'::text[])$$  -- short syntax for long lists
  • Fiyat teklifini kolaylaştırmak için dolar fiyatını kullandım .

  • Değer sütununun metin gösterimi hedef tür için geçerli girdi olduğu sürece , farklı veri türlerine sahip sütunlar bile verebilirsiniz crosstab(text, text). Aklınıza gelebilecek Bu şekilde farklı tür ve çıkış niteliklerini text, date, numericilgili özellikler için vs. Kılavuzdaki bölümüncrosstab(text, text) sonunda bir kod örneği vardır .

burada keman

Gelişmiş örnekler


\crosstabview psql'de

Postgres 9.6 bu meta komutu varsayılan interaktif terminal psql'sine ekledi . İlk crosstab()parametre olarak kullanacağınız sorguyu çalıştırabilir ve \crosstabview(hemen veya sonraki adımda) besleyebilirsiniz . Sevmek:

db=> SELECT section, status, ct FROM tbl \crosstabview

Yukarıdaki ile benzer sonuç, ancak yalnızca istemci tarafında bir temsil özelliğidir . Giriş satırları biraz farklı şekilde ele alınır, bu nedenle ORDER BYgerekli değildir. İçin ayrıntılar \crosstabviewkılavuzu içinde. Sayfanın altında daha fazla kod örneği var.

Daniel Vérité (psql özelliğinin yazarı) tarafından dba.SE ile ilgili cevap:



Daha önce kabul edilen cevap eski.

  • Fonksiyonun varyantı crosstab(text, integer)eski. İkinci integerparametre yok sayılır. Mevcut kılavuzu alıntıladım :

    crosstab(text sql, int N) ...

    Eski sürümü crosstab(text). NDeğer sütunlarının sayısı her zaman çağıran sorgu tarafından belirlendiğinden, parametre artık yoksayılır

  • Gereksiz döküm ve yeniden adlandırma.

  • Bir satırın tüm öznitelikleri yoksa başarısız olur. Eksik öznitelikleri doğru şekilde işlemek için yukarıdaki iki giriş parametresine sahip güvenli değişkene bakın.

  • ORDER BYöğesinin tek parametreli biçiminde gereklidir crosstab(). Kullanım kılavuzu:

    Uygulamada, SQL sorgusu her zaman ORDER BY 1,2giriş satırlarının düzgün sıralandığından emin olmak için belirtmelidir


3
+1, iyi yazma, fark ettiğiniz için teşekkürlerIn practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered
ChristopheD

$$ VALUES kullanırken sorun yaşıyorum .. $$. Bunun yerine 'VALUES (' '<attr>' ':: <type>), ..' kullandım
Marco Fantasia

Çapraz sorguda parametre bağlaması belirleyebilir miyiz? Bu hatayı alıyorum => parametre 2 $ veri türü belirlenemedi
Ashish

1
Çapraz sorguda sütun için varsayılan değer ayarlamak mümkün müdür?
Ashish

2
@Ashish: Lütfen yeni bir soru başlatın. Yorumlar yer değil. Bağlam için her zaman buna bağlayabilirsiniz.
Erwin Brandstetter

30

Her veritabanı için bir kez kurmanız gereken ek modül tablefunccrosstab() işlevini kullanabilirsiniz . PostgreSQL 9.1'den bu yana aşağıdakileri kullanabilirsiniz :CREATE EXTENSION

CREATE EXTENSION tablefunc;

Sizin durumunuzda, bunun şöyle görüneceğine inanıyorum:

CREATE TABLE t (Section CHAR(1), Status VARCHAR(10), Count integer);

INSERT INTO t VALUES ('A', 'Active',   1);
INSERT INTO t VALUES ('A', 'Inactive', 2);
INSERT INTO t VALUES ('B', 'Active',   4);
INSERT INTO t VALUES ('B', 'Inactive', 5);

SELECT row_name AS Section,
       category_1::integer AS Active,
       category_2::integer AS Inactive
FROM crosstab('select section::text, status, count::text from t',2)
            AS ct (row_name text, category_1 text, category_2 text);

Çapraz sorgusunda bir parametre kullanırsanız, parametreden düzgün şekilde kaçmanız gerekir. Örnek: (yukarıdan) sadece etkin olanları istediğinizi söyleyin: SEÇ ... çapraz tablodan ('bölüm seçin :: metin, durum, sayı :: durumdan t = metin =' 'etkin' '', 2) AS. .. (çift tırnak dikkat edin). Parametrenin çalışma zamanında kullanıcı tarafından iletilmesi durumunda (örneğin bir işlev parametresi olarak) şunu söyleyebilirsiniz: SELECT ... FROM çapraz sekmesi ('bölüm :: metin, durum, sayım :: t'den metin = burada durum =' ' '|| par_active ||' '' ', 2) AS ... (burada üç tırnak!). BIRT de bu ile çalışır? Yer tutucu.
Wim Verhavert

26
SELECT section,
       SUM(CASE status WHEN 'Active' THEN count ELSE 0 END) AS active, --here you pivot each status value as a separate column explicitly
       SUM(CASE status WHEN 'Inactive' THEN count ELSE 0 END) AS inactive --here you pivot each status  value as a separate column explicitly

FROM t
GROUP BY section

1
Birisi tablefunc modülündeki çapraz tablo işlevinin bu cevaba ne eklediğini açıklayabilir mi, hem eldeki işi hem de aklıma anlamak daha kolay mı?
John Powell

4
@ JohnBarça: Bunun gibi basit bir durum CASE ifadeleriyle kolayca çözülebilir. Ancak bu, tamsayılardan daha fazla özellik ve / veya diğer veri türleriyle çok çabuk kötüleşir. Bir kenara: bu form toplama işlevini kullanır, hangisinin işe sum()yarayacağı daha iyi olur min()veya max()hiç olmaz . Ancak bunun, öznitelik başına yalnızca "ilk" değeri kullanan etkisinden oldukça farklı etkileri vardır . Sadece bir tane olduğu sürece önemli değil. Son olarak, performans da önemlidir. C harfiyle yazılmıştır ve görev için optimize edilmiştir. ELSEtextcorosstab()crosstab()
Erwin Brandstetter

Bu benim için çalışmıyor, postgresql için. Hatayı alıyorumERROR: 42803: aggregate function calls may not be nested
Audrey

1
@Audrey aynı SQL'i çalıştırmıyor musunuz?

2
Sadece bir kod bloğu ile açıklama
ekleyin

10

JSON toplama ile çözüm:

CREATE TEMP TABLE t (
  section   text
, status    text
, ct        integer  -- don't use "count" as column name.
);

INSERT INTO t VALUES 
  ('A', 'Active', 1), ('A', 'Inactive', 2)
, ('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7); 


SELECT section,
       (obj ->> 'Active')::int AS active,
       (obj ->> 'Inactive')::int AS inactive
FROM (SELECT section, json_object_agg(status,ct) AS obj
      FROM t
      GROUP BY section
     )X

Teşekkür ederim, bu bana ilgili bir sorunla yardımcı oldu.
JeffCharter

1

Maalesef bu tamamlanmadı çünkü burada test edemiyorum, ancak sizi doğru yönde çıkarabilir. Ben benzer bir sorgu yapar kullandığım bir şeyden çeviri:

select mt.section, mt1.count as Active, mt2.count as Inactive
from mytable mt
left join (select section, count from mytable where status='Active')mt1
on mt.section = mt1.section
left join (select section, count from mytable where status='Inactive')mt2
on mt.section = mt2.section
group by mt.section,
         mt1.count,
         mt2.count
order by mt.section asc;

Çalıştığım kod:

select m.typeID, m1.highBid, m2.lowAsk, m1.highBid - m2.lowAsk as diff, 100*(m1.highBid - m2.lowAsk)/m2.lowAsk as diffPercent
from mktTrades m
   left join (select typeID,MAX(price) as highBid from mktTrades where bid=1 group by typeID)m1
   on m.typeID = m1.typeID
   left join (select typeID,MIN(price) as lowAsk  from mktTrades where bid=0 group by typeID)m2
   on m1.typeID = m2.typeID
group by m.typeID, 
         m1.highBid, 
         m2.lowAsk
order by diffPercent desc;

bir typeID, en yüksek fiyat teklifi ve istenen en düşük fiyat ve ikisi arasındaki fark döndürür (olumlu bir fark, bir şeyin satılabileceğinden daha az bir fiyata satın alınabileceği anlamına gelir).


1
Bir yan tümcesini kaçırıyorsunuz, aksi takdirde bu doğrudur. Açıklama planları sistemimde çok farklı - çapraz fonksiyonun maliyeti 22.5, LEFT JOIN yaklaşımı ise 91.38 maliyetle yaklaşık 4 kat daha pahalı. Ayrıca, yaklaşık iki kat daha fazla fiziksel okuma üretir ve diğer birleştirme türlerine kıyasla oldukça pahalı olabilen karma birleştirmeler gerçekleştirir.
Jeremiah Peschka

Teşekkürler Jeremiah, bunu bilmek güzel. Diğer cevabı iptal ettim, ancak yorumunuz saklanmaya değer, bu yüzden bunu silmeyeceğim.
LanceH

-1

Crosstabişlevi tablefuncuzantı altında kullanılabilir . Bu uzantıyı veritabanı için bir kez oluşturmanız gerekir.

UZATMA OLUŞTUR tablefunc;

Çapraz sekmeyi kullanarak pivot tablo oluşturmak için aşağıdaki kodu kullanabilirsiniz:

create table test_Crosstab( section text,
<br/>status text,
<br/>count numeric)

<br/>insert into test_Crosstab values ( 'A','Active',1)
                <br/>,( 'A','Inactive',2)
                <br/>,( 'B','Active',4)
                <br/>,( 'B','Inactive',5)

select * from crosstab(
<br/>'select section
    <br/>,status
    <br/>,count
    <br/>from test_crosstab'
    <br/>)as ctab ("Section" text,"Active" numeric,"Inactive" numeric)

1
Bu cevap önceden var olan cevaplara bir şey katmaz.
Erwin Brandstetter
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.