问题
I have a table called as matrix which contains two columns namely cola and colb as
shown below:
Table: matrix
create table matrix
(
cola varchar(10),
colb varchar(10)
);
Insertion of rows:
insert into matrix values('A1','B1'),('A2','B2'),('A3','B3'),('A4','B4'),
('A5','B5'),('A6','B6'),('A7','B7'),('A8','B8'),
('A9','B9'),('A10','B10'),('A11','B11'),('A12','B12'),
('A13','B13'),('A14','B14'),('A15','B15'),('A16','B16'),
('A17','B17'),('A18','B18'),('A19','B19'),('A20','B20'),
('A21','B21'),('A22','B22'),('A23','B23'),('A24','B24'),
('A25','B25'),('A26','B26'),('A27','B27'),('A28','B28'),
('A29','B29'),('A30','B30');
Note: I want to show the result in the form of matrix and count which columns belongs to each others and assign the values in the matrix for each column. I have just added 30 records for just example but there may be thousands of records also. So I need to prepare a dynamic pivot table for that. The expected result as shown below.
Expected Result:
A1 A2 A3 A4 A5 A6 ................ A30
------------------------------------------------------------------
B1 | 1 0 0 0 0 0 0
|
B2 | 0 1 0 0 0 0 0
|
B3 | 0 0 1 0 0 0 0
|
B4 | 0 0 0 1 0 0 0
|
B5 | 0 0 0 0 1 0 0
|
B6 | 0 0 0 0 0 1 0
. |
. |
. |
. |
B30| 0 0 0 0 0 0 1
回答1:
You can do this with crosstab() from the additional module tablefunc:
SELECT b
, COALESCE(a1, 0) AS "A1"
, COALESCE(a2, 0) AS "A2"
, COALESCE(a3, 0) AS "A3"
, ... -- all the way up to "A30"
FROM crosstab(
'SELECT colb, cola, 1 AS val FROM matrix
ORDER BY 1,2'
, $$SELECT 'A'::text || g FROM generate_series(1,30) g$$
) AS t (b text
, a1 int, a2 int, a3 int, a4 int, a5 int, a6 int
, a7 int, a8 int, a9 int, a10 int, a11 int, a12 int
, a13 int, a14 int, a15 int, a16 int, a17 int, a18 int
, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int
, a25 int, a26 int, a27 int, a28 int, a29 int, a30 int);
If NULL instead of 0 works, too, it can be just SELECT * in the outer query.
Detailed explanation:
- PostgreSQL Crosstab Query
The special "difficulty" here: no actual "value". So add 1 AS val as last column.
Unknown number of categories
A completely dynamic query (with unknown result type) is not possible in a single query. You need two queries. First build a statement like the above dynamically, then execute it. Details:
Selecting multiple max() values using a single SQL statement
PostgreSQL convert columns to rows? Transpose?
Dynamically generate columns for crosstab in PostgreSQL
Dynamic alternative to pivot with CASE and GROUP BY
Too many categories
If you exceed the maximum number of columns (1600), a classic crosstab is impossible, because the result cannot be represented with individual columns. (Also, human eyes would hardly be able to read a table with that many columns)
Arrays or document types like hstore or jsonb are the alternative. Here is a solution with arrays:
SELECT colb, array_agg(cola) AS colas
FROM (
SELECT colb, right(colb, -1)::int AS sortb
, CASE WHEN m.cola IS NULL THEN 0 ELSE 1 END AS cola
FROM (SELECT DISTINCT colb FROM matrix) b
CROSS JOIN (SELECT DISTINCT cola FROM matrix) a
LEFT JOIN matrix m USING (colb, cola)
ORDER BY sortb, right(cola, -1)::int
) sub
GROUP BY 1, sortb
ORDER BY sortb;
Build the complete grid of values with:
(SELECT DISTINCT colb FROM matrix) b CROSS JOIN (SELECT DISTINCT cola FROM matrix) aLEFT JOINexisting combinations, order by the numeric part of the name and aggregate into arrays.right(colb, -1)::inttrims the leading character from 'A3' and casts the digits to integer so we get a proper sort order.
Basic matrix
If you just want a table of 0 an 1 where x = y, this can be had cheaper:
SELECT x, array_agg((x = y)::int) AS y_arr
FROM generate_series(1,10) x
, generate_series(1,10) y
GROUP BY 1
ORDER BY 1;
SQL Fiddle building on the one you provided in the comments.
Note that sqlfiddle.com currently has a bug that kills the display of array values. So I cast to text there to work around it.
来源:https://stackoverflow.com/questions/28337765/postgresql-9-3-dynamic-pivot-table