Est-ce que quelqu'un sait comment créer des requêtes d'analyse croisée dans PostgreSQL?
Par exemple, j'ai le tableau suivant:
Section Status Count
A Active 1
A Inactive 2
B Active 4
B Inactive 5
Je souhaite que la requête renvoie le tableau croisé suivant:
Section Active Inactive
A 1 2
B 4 5
Est-ce possible?
Installez le module supplémentaire tablefunc
une fois par base de données, qui fournit la fonction crosstab()
. Depuis Postgres 9.1, vous pouvez utiliser CREATE EXTENSION
pour cela:
_CREATE EXTENSION IF NOT EXISTS tablefunc;
_
_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
_
crosstab(text)
avec 1 paramètre :
_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);
_
Résultats:
Section | Actif | Inactif --------- + -------- + ---------- A | 1 | 2 B | 4 | 5 C | sept | - !!
C
: la valeur _7
_ est renseigné pour la première colonne. Parfois, ce comportement est souhaitable, mais pas pour ce cas d'utilisation.crosstab(text, text)
avec 2 (paramètres d’entrée): :
_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);
_
Résultats:
Section | Actif | Inactif --------- + -------- + ---------- A | 1 | 2 B | 4 | 5 C | | sept - !!
Notez le résultat correct pour C
.
Le deuxième paramètre peut être toute requête renvoyant une ligne par attribut correspondant à l'ordre de la définition de la colonne à la fin. Vous voudrez souvent interroger des attributs distincts de la table sous-jacente comme ceci:
_'SELECT DISTINCT attribute FROM tbl ORDER BY 1'
_
C'est dans le manuel.
Étant donné que vous devez malgré tout épeler toutes les colonnes de la liste de définition de colonne (à l'exception des variantes crosstabN()
prédéfinies), il est généralement plus efficace de fournir une liste courte dans une expression VALUES
comme illustré:
_$$VALUES ('Active'::text), ('Inactive')$$)
_
Ou (pas dans le manuel):
_$$SELECT unnest('{Active,Inactive}'::text[])$$ -- short syntax for long lists
_
J'ai utilisé cotation en dollars pour faciliter la cotation.
Vous pouvez même éditer des colonnes avec différents types de données avec crosstab(text, text)
- tant que la représentation textuelle du La colonne value est une entrée valide pour le type de cible. De cette façon, vous pourriez avoir des attributs de type et de sortie différents text
, date
, numeric
etc. pour les attributs respectifs. Il existe un exemple de code à la fin de chapitre crosstab(text, text)
dans le manuel .
db <> violon ici
pivot sur plusieurs colonnes à l'aide de Tablefunc - montrant également les "colonnes supplémentaires" mentionnées
\crosstabview
_ en psqlPostgres 9.6 a ajouté cette méta-commande à son terminal interactif par défaut psql . Vous pouvez exécuter la requête que vous utiliseriez comme premier paramètre crosstab()
et le transmettre à _\crosstabview
_ (immédiatement ou à l'étape suivante). Comme:
_db=> SELECT section, status, ct FROM tbl \crosstabview
_
Résultat similaire à celui ci-dessus, mais il s’agit d’une fonction de représentation côté client exclusivement. Les lignes en entrée sont traitées légèrement différemment. Par conséquent, _ORDER BY
_ n'est pas requis. Détails pour _\crosstabview
_ dans le manuel. Il y a plus d'exemples de code au bas de cette page.
Réponse associée sur dba.SE de Daniel Vérité (l'auteur de la fonction psql):
Le réponse précédemment acceptée est obsolète.
La variante de la fonction crosstab(text, integer)
est obsolète. Le deuxième paramètre integer
est ignoré. Je cite le actuel manuel :
crosstab(text sql, int N)
...Version obsolète de
crosstab(text)
. Le paramètreN
est maintenant ignoré, car le nombre de colonnes de valeur est toujours déterminé par la requête appelante.
Inutile casting et renommer.
Il échoue si une ligne n'a pas tous les attributs. Voir la variante sûre avec les deux paramètres d'entrée ci-dessus pour gérer correctement les attributs manquants.
_ORDER BY
_ est requis sous la forme à un paramètre de crosstab()
. Le manuel:
En pratique, la requête SQL doit toujours spécifier _
ORDER BY 1,2
_ pour s'assurer que les lignes d'entrée sont correctement ordonnées.
Vous pouvez utiliser la fonction crosstab()
du module supplémentaire tablefunc - que vous devez installer une fois par base de données. Depuis PostgreSQL 9.1, vous pouvez utiliser CREATE EXTENSION
pour cela:
_CREATE EXTENSION tablefunc;
_
Dans votre cas, je pense que cela ressemblerait à ceci:
_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);
_
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
Solution avec agrégation JSON:
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
Désolé, ce n'est pas complet parce que je ne peux pas le tester ici, mais cela pourrait vous faire avancer dans la bonne direction. Je traduis quelque chose que j'utilise qui fait une requête similaire:
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;
Le code sur lequel je travaille est:
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;
qui renverra un typeID, l'offre la plus élevée et le prix le plus bas demandé, ainsi que la différence entre les deux (une différence positive signifierait que quelque chose pourrait être acheté à un prix inférieur à ce qu'il peut être vendu).