Commit c09b18f2 authored by Alvaro Herrera's avatar Alvaro Herrera

Support \crosstabview in psql

\crosstabview is a completely different way to display results from a
query: instead of a vertical display of rows, the data values are placed
in a grid where the column and row headers come from the data itself,
similar to a spreadsheet.

The sort order of the horizontal header can be specified by using
another column in the query, and the vertical header determines its
ordering from the order in which they appear in the query.

This only allows displaying a single value in each cell.  If more than
one value correspond to the same cell, an error is thrown.  Merging of
values can be done in the query itself, if necessary.  This may be
revisited in the future.

Author: Daniel Verité
Reviewed-by: Pavel Stehule, Dean Rasheed
parent 279d86af
......@@ -989,6 +989,106 @@ testdb=>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>\crosstabview [
<replaceable class="parameter">colV</replaceable>
<replaceable class="parameter">colH</replaceable>
[:<replaceable class="parameter">scolH</replaceable>]
[<replaceable class="parameter">colD</replaceable>]
] </literal></term>
<listitem>
<para>
Execute the current query buffer (like <literal>\g</literal>) and shows
the results inside a crosstab grid.
The query must return at least three columns.
The output column <replaceable class="parameter">colV</replaceable>
becomes a vertical header
and the output column <replaceable class="parameter">colH</replaceable>
becomes a horizontal header, optionally sorted by ranking data obtained
from <replaceable class="parameter">scolH</replaceable>.
<replaceable class="parameter">colD</replaceable>
is the output column to project into the grid. If this is not
specified and there are exactly three columns in the result set,
the column that isn't
<replaceable class="parameter">colV</replaceable> nor
<replaceable class="parameter">colH</replaceable>
is displayed; if there are more columns, an error is thrown.
</para>
<para>
All columns can be refered to by their position (starting at 1), or by
their name. Normal case folding and quoting rules apply on column
names. By default,
<replaceable class="parameter">colV</replaceable> corresponds to column 1
and <replaceable class="parameter">colH</replaceable> to column 2.
A query having only one output column cannot be viewed in crosstab, and
<replaceable class="parameter">colH</replaceable> must differ from
<replaceable class="parameter">colV</replaceable>.
</para>
<para>
The vertical header, displayed as the leftmost column,
contains the deduplicated values found in
column <replaceable class="parameter">colV</replaceable>, in the same
order as in the query results.
</para>
<para>
The horizontal header, displayed as the first row,
contains the deduplicated values found in
column <replaceable class="parameter">colH</replaceable>, in
the order of appearance in the query results.
If specified, the optional <replaceable class="parameter">scolH</replaceable>
argument refers to a column whose values should be integer numbers
by which <replaceable class="parameter">colH</replaceable> will be sorted
to be positioned in the horizontal header.
</para>
<para>
Inside the crosstab grid,
given a query output with <literal>N</literal> columns
(including <replaceable class="parameter">colV</replaceable> and
<replaceable class="parameter">colH</replaceable>),
for each distinct value <literal>x</literal> of
<replaceable class="parameter">colH</replaceable>
and each distinct value <literal>y</literal> of
<replaceable class="parameter">colV</replaceable>,
the contents of a cell located at the intersection
<literal>(x,y)</literal> is determined by these rules:
<itemizedlist>
<listitem>
<para>
if there is no corresponding row in the query results such that the
value for <replaceable class="parameter">colH</replaceable>
is <literal>x</literal> and the value
for <replaceable class="parameter">colV</replaceable>
is <literal>y</literal>, the cell is empty.
</para>
</listitem>
<listitem>
<para>
if there is exactly one row such that the value
for <replaceable class="parameter">colH</replaceable>
is <literal>x</literal> and the value
for <replaceable class="parameter">colV</replaceable>
is <literal>y</literal>, then the <literal>colD</literal> column
is displayed.
</para>
</listitem>
<listitem>
<para>
if there are several such rows, an error is thrown.
</para>
</listitem>
</itemizedlist>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>\d[S+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
......@@ -4070,6 +4170,47 @@ first | 4
second | four
</programlisting></para>
<para>
When suitable, query results can be shown in a crosstab representation
with the \crosstabview command:
<programlisting>
testdb=&gt; <userinput>SELECT first, second, first &gt; 2 AS gt2 FROM my_table;</userinput>
first | second | ge2
-------+--------+-----
1 | one | f
2 | two | f
3 | three | t
4 | four | t
(4 rows)
testdb=&gt; <userinput>\crosstabview first second</userinput>
first | one | two | three | four
-------+-----+-----+-------+------
1 | f | | |
2 | | f | |
3 | | | t |
4 | | | | t
(4 rows)
</programlisting>
This second example shows a multiplication table with rows sorted in reverse
numerical order and columns with an independant, ascending numerical order.
<programlisting>
testdb=&gt; <userinput>SELECT t1.first as "A", t2.first+100 AS "B", t1.first*(t2.first+100) as "AxB",</userinput>
testdb(&gt; <userinput>row_number() over(order by t2.first) AS ord</userinput>
testdb(&gt; <userinput>FROM my_table t1 CROSS JOIN my_table t2 ORDER BY 1 DESC</userinput>
testdb(&gt; <userinput>\crosstabview A B:ord AxB</userinput>
A | 101 | 102 | 103 | 104
---+-----+-----+-----+-----
4 | 404 | 408 | 412 | 416
3 | 303 | 306 | 309 | 312
2 | 202 | 204 | 206 | 208
1 | 101 | 102 | 103 | 104
(4 rows)
</programlisting>
</para>
</refsect1>
</refentry>
......@@ -23,7 +23,7 @@ LDFLAGS += -L$(top_builddir)/src/fe_utils -lpgfeutils -lpq
OBJS= command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
startup.o prompt.o variables.o large_obj.o describe.o \
tab-complete.o \
crosstabview.o tab-complete.o \
sql_help.o psqlscanslash.o \
$(WIN32RES)
......
......@@ -39,6 +39,7 @@
#include "common.h"
#include "copy.h"
#include "crosstabview.h"
#include "describe.h"
#include "help.h"
#include "input.h"
......@@ -364,6 +365,20 @@ exec_command(const char *cmd,
else if (strcmp(cmd, "copyright") == 0)
print_copyright();
/* \crosstabview -- execute a query and display results in crosstab */
else if (strcmp(cmd, "crosstabview") == 0)
{
pset.ctv_col_V = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
pset.ctv_col_H = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
pset.ctv_col_D = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
pset.crosstab_flag = true;
status = PSQL_CMD_SEND;
}
/* \d* commands */
else if (cmd[0] == 'd')
{
......
......@@ -23,6 +23,7 @@
#include "settings.h"
#include "command.h"
#include "copy.h"
#include "crosstabview.h"
#include "fe_utils/mbprint.h"
......@@ -1064,6 +1065,8 @@ PrintQueryResults(PGresult *results)
success = StoreQueryTuple(results);
else if (pset.gexec_flag)
success = ExecQueryTuples(results);
else if (pset.crosstab_flag)
success = PrintResultsInCrosstab(results);
else
success = PrintQueryTuples(results);
/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
......@@ -1213,7 +1216,8 @@ SendQuery(const char *query)
}
}
if (pset.fetch_count <= 0 || pset.gexec_flag || !is_select_command(query))
if (pset.fetch_count <= 0 || pset.gexec_flag ||
pset.crosstab_flag || !is_select_command(query))
{
/* Default fetch-it-all-and-print mode */
instr_time before,
......@@ -1356,6 +1360,24 @@ sendquery_cleanup:
/* reset \gexec trigger */
pset.gexec_flag = false;
/* reset \crosstabview trigger */
pset.crosstab_flag = false;
if (pset.ctv_col_V)
{
free(pset.ctv_col_V);
pset.ctv_col_V = NULL;
}
if (pset.ctv_col_H)
{
free(pset.ctv_col_H);
pset.ctv_col_H = NULL;
}
if (pset.ctv_col_D)
{
free(pset.ctv_col_D);
pset.ctv_col_D = NULL;
}
return OK;
}
......@@ -1501,7 +1523,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
break;
}
/* Note we do not deal with \gexec mode here */
/* Note we do not deal with \gexec or \crosstabview modes here */
ntuples = PQntuples(results);
......
This diff is collapsed.
/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2016, PostgreSQL Global Development Group
*
* src/bin/psql/crosstabview.h
*/
#ifndef CROSSTABVIEW_H
#define CROSSTABVIEW_H
/*
* Limit the number of output columns generated in memory by the crosstabview
* algorithm. A new output column is added for each distinct value found in the
* column that pivots (to form the horizontal header).
* The purpose of this limit is to fail early instead of over-allocating or spending
* too much time if the crosstab to generate happens to be unreasonably large
* (worst case: a NxN cartesian product with N=number of tuples).
* The value of 1600 corresponds to the maximum columns per table in storage,
* but it could be as much as INT_MAX theorically.
*/
#define CROSSTABVIEW_MAX_COLUMNS 1600
/* prototypes */
extern bool PrintResultsInCrosstab(PGresult *res);
#endif /* CROSSTABVIEW_H */
......@@ -177,6 +177,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\gexec execute query, then execute each value in its result\n"));
fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
fprintf(output, _(" \\q quit psql\n"));
fprintf(output, _(" \\crosstabview [COLUMNS] execute query and display results in crosstab\n"));
fprintf(output, _(" \\watch [SEC] execute query every SEC seconds\n"));
fprintf(output, "\n");
......
......@@ -93,6 +93,10 @@ typedef struct _psqlSettings
char *gfname; /* one-shot file output argument for \g */
char *gset_prefix; /* one-shot prefix argument for \gset */
bool gexec_flag; /* one-shot flag to execute query's results */
bool crosstab_flag; /* one-shot request to crosstab results */
char *ctv_col_V; /* \crosstabview 1st argument */
char *ctv_col_H; /* \crosstabview 2nd argument */
char *ctv_col_D; /* \crosstabview 3nd argument */
bool notty; /* stdin or stdout is not a tty (as determined
* on startup) */
......
......@@ -1274,7 +1274,8 @@ psql_completion(const char *text, int start, int end)
/* psql's backslash commands. */
static const char *const backslash_commands[] = {
"\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy", "\\copyright",
"\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy",
"\\copyright", "\\crosstabview",
"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD",
"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
......
......@@ -3295,30 +3295,9 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
for (i = 0; i < cont.ncolumns; i++)
{
char align;
Oid ftype = PQftype(result, i);
switch (ftype)
{
case INT2OID:
case INT4OID:
case INT8OID:
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
case OIDOID:
case XIDOID:
case CIDOID:
case CASHOID:
align = 'r';
break;
default:
align = 'l';
break;
}
printTableAddHeader(&cont, PQfname(result, i),
opt->translate_header, align);
opt->translate_header,
column_type_alignment(PQftype(result, i)));
}
/* set cells */
......@@ -3360,6 +3339,31 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
printTableCleanup(&cont);
}
char
column_type_alignment(Oid ftype)
{
char align;
switch (ftype)
{
case INT2OID:
case INT4OID:
case INT8OID:
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
case OIDOID:
case XIDOID:
case CIDOID:
case CASHOID:
align = 'r';
break;
default:
align = 'l';
break;
}
return align;
}
void
setDecimalLocale(void)
......
......@@ -206,6 +206,8 @@ extern void printTable(const printTableContent *cont,
extern void printQuery(const PGresult *result, const printQueryOpt *opt,
FILE *fout, bool is_pager, FILE *flog);
extern char column_type_alignment(Oid);
extern void setDecimalLocale(void);
extern const printTextFormat *get_line_style(const printTableOpt *opt);
extern void refresh_utf8format(const printTableOpt *opt);
......
......@@ -2476,6 +2476,7 @@ execute q;
+------------------+-------------------+
deallocate q;
\pset linestyle ascii
prepare q as select ' | = | lkjsafi\\/ /oeu rio)(!@&*#)*(!&@*) \ (&' as " | -- | 012345678 9abc def!*@#&!@(*&*~~_+-=\ \", '11' as "0123456789", 11 as int from generate_series(1,10) as n;
\pset format asciidoc
\pset expanded off
......@@ -2682,6 +2683,9 @@ execute q;
<l|int >l|11
|====
deallocate q;
\pset format aligned
\pset expanded off
\pset border 1
-- SHOW_CONTEXT
\set SHOW_CONTEXT never
do $$
......@@ -2710,3 +2714,188 @@ NOTICE: foo
CONTEXT: PL/pgSQL function inline_code_block line 3 at RAISE
ERROR: bar
CONTEXT: PL/pgSQL function inline_code_block line 4 at RAISE
--
-- \crosstabview
--
CREATE TABLE ctv_data (v, h, c, i, d) AS
VALUES
('v1','h2','foo', 3, '2015-04-01'::date),
('v2','h1','bar', 3, '2015-01-02'),
('v1','h0','baz', NULL, '2015-07-12'),
('v0','h4','qux', 4, '2015-07-15'),
('v0','h4','dbl', -3, '2014-12-15'),
('v0',NULL,'qux', 5, '2014-07-15'),
('v1','h2','quux',7, '2015-04-04');
-- running \crosstabview after query uses query in buffer
SELECT v, EXTRACT(year FROM d), count(*)
FROM ctv_data
GROUP BY 1, 2
ORDER BY 1, 2;
v | date_part | count
----+-----------+-------
v0 | 2014 | 2
v0 | 2015 | 1
v1 | 2015 | 3
v2 | 2015 | 1
(4 rows)
-- basic usage with 3 columns
\crosstabview
v | 2014 | 2015
----+------+------
v0 | 2 | 1
v1 | | 3
v2 | | 1
(3 rows)
-- ordered months in horizontal header, quoted column name
SELECT v, to_char(d, 'Mon') AS "month name", EXTRACT(month FROM d) AS num,
count(*) FROM ctv_data GROUP BY 1,2,3 ORDER BY 1
\crosstabview v "month name":num 4
v | Jan | Apr | Jul | Dec
----+-----+-----+-----+-----
v0 | | | 2 | 1
v1 | | 2 | 1 |
v2 | 1 | | |
(3 rows)
-- ordered months in vertical header, ordered years in horizontal header
SELECT EXTRACT(year FROM d) AS year, to_char(d,'Mon') AS "month name",
EXTRACT(month FROM d) AS month,
format('sum=%s avg=%s', sum(i), avg(i)::numeric(2,1))
FROM ctv_data
GROUP BY EXTRACT(year FROM d), to_char(d,'Mon'), EXTRACT(month FROM d)
ORDER BY month
\crosstabview "month name" year:year format
month name | 2014 | 2015
------------+-----------------+----------------
Jan | | sum=3 avg=3.0
Apr | | sum=10 avg=5.0
Jul | sum=5 avg=5.0 | sum=4 avg=4.0
Dec | sum=-3 avg=-3.0 |
(4 rows)
-- combine contents vertically into the same cell (V/H duplicates)
SELECT v, h, string_agg(c, E'\n') FROM ctv_data GROUP BY v, h ORDER BY 1,2,3
\crosstabview 1 2 3
v | h4 | | h0 | h2 | h1
----+-----+-----+-----+------+-----
v0 | qux+| qux | | |
| dbl | | | |
v1 | | | baz | foo +|
| | | | quux |
v2 | | | | | bar
(3 rows)
-- horizontal ASC order from window function
SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h) AS r
FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
\crosstabview v h:r c
v | h0 | h1 | h2 | h4 |
----+-----+-----+------+-----+-----
v0 | | | | qux+| qux
| | | | dbl |
v1 | baz | | foo +| |
| | | quux | |
v2 | | bar | | |
(3 rows)
-- horizontal DESC order from window function
SELECT v, h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h DESC) AS r
FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
\crosstabview v h:r c
v | | h4 | h2 | h1 | h0
----+-----+-----+------+-----+-----
v0 | qux | qux+| | |
| | dbl | | |
v1 | | | foo +| | baz
| | | quux | |
v2 | | | | bar |
(3 rows)
-- horizontal ASC order from window function, NULLs pushed rightmost
SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h NULLS LAST) AS r
FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
\crosstabview v h:r c
v | h0 | h1 | h2 | h4 |
----+-----+-----+------+-----+-----
v0 | | | | qux+| qux
| | | | dbl |
v1 | baz | | foo +| |
| | | quux | |
v2 | | bar | | |
(3 rows)
-- only null, no column name, 2 columns: error
SELECT null,null \crosstabview
The query must return at least two columns to be shown in crosstab
-- only null, no column name, 3 columns: works
SELECT null,null,null \crosstabview
?column? |
----------+--
|
(1 row)
-- null display
\pset null '#null#'
SELECT v,h, string_agg(i::text, E'\n') AS i FROM ctv_data
GROUP BY v, h ORDER BY h,v
\crosstabview v h i
v | h0 | h1 | h2 | h4 | #null#
----+--------+----+----+----+--------
v1 | #null# | | 3 +| |
| | | 7 | |
v2 | | 3 | | |
v0 | | | | 4 +| 5
| | | | -3 |
(3 rows)
\pset null ''
-- refer to columns by position
SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n')
FROM ctv_data GROUP BY v, h ORDER BY h,v
\crosstabview 2 1 4
h | v1 | v2 | v0
----+------+-----+-----
h0 | baz | |
h1 | | bar |
h2 | foo +| |
| quux | |
h4 | | | qux+
| | | dbl
| | | qux
(5 rows)
-- refer to columns by positions and names mixed
SELECT v,h, string_agg(i::text, E'\n') AS i, string_agg(c, E'\n') AS c
FROM ctv_data GROUP BY v, h ORDER BY h,v
\crosstabview 1 "h" 4
v | h0 | h1 | h2 | h4 |
----+-----+-----+------+-----+-----
v1 | baz | | foo +| |
| | | quux | |
v2 | | bar | | |
v0 | | | | qux+| qux
| | | | dbl |
(3 rows)
-- error: bad column name
SELECT v,h,c,i FROM ctv_data
\crosstabview v h j
Invalid column name: j
-- error: bad column number
SELECT v,h,i,c FROM ctv_data
\crosstabview 2 1 5
Invalid column number: 5
-- error: same H and V columns
SELECT v,h,i,c FROM ctv_data
\crosstabview 2 h 4
The same column cannot be used for both vertical and horizontal headers
-- error: too many columns
SELECT a,a,1 FROM generate_series(1,3000) AS a
\crosstabview
Maximum number of columns (1600) exceeded
-- error: only one column
SELECT 1 \crosstabview
The query must return at least two columns to be shown in crosstab
DROP TABLE ctv_data;
......@@ -326,6 +326,8 @@ execute q;
deallocate q;
\pset linestyle ascii
prepare q as select ' | = | lkjsafi\\/ /oeu rio)(!@&*#)*(!&@*) \ (&' as " | -- | 012345678 9abc def!*@#&!@(*&*~~_+-=\ \", '11' as "0123456789", 11 as int from generate_series(1,10) as n;
\pset format asciidoc
......@@ -351,6 +353,10 @@ execute q;
deallocate q;
\pset format aligned
\pset expanded off
\pset border 1
-- SHOW_CONTEXT
\set SHOW_CONTEXT never
......@@ -373,3 +379,102 @@ begin
raise notice 'foo';
raise exception 'bar';
end $$;
--
-- \crosstabview
--
CREATE TABLE ctv_data (v, h, c, i, d) AS
VALUES
('v1','h2','foo', 3, '2015-04-01'::date),
('v2','h1','bar', 3, '2015-01-02'),
('v1','h0','baz', NULL, '2015-07-12'),
('v0','h4','qux', 4, '2015-07-15'),
('v0','h4','dbl', -3, '2014-12-15'),
('v0',NULL,'qux', 5, '2014-07-15'),
('v1','h2','quux',7, '2015-04-04');
-- running \crosstabview after query uses query in buffer
SELECT v, EXTRACT(year FROM d), count(*)
FROM ctv_data
GROUP BY 1, 2
ORDER BY 1, 2;
-- basic usage with 3 columns
\crosstabview
-- ordered months in horizontal header, quoted column name
SELECT v, to_char(d, 'Mon') AS "month name", EXTRACT(month FROM d) AS num,
count(*) FROM ctv_data GROUP BY 1,2,3 ORDER BY 1
\crosstabview v "month name":num 4
-- ordered months in vertical header, ordered years in horizontal header
SELECT EXTRACT(year FROM d) AS year, to_char(d,'Mon') AS "month name",
EXTRACT(month FROM d) AS month,
format('sum=%s avg=%s', sum(i), avg(i)::numeric(2,1))
FROM ctv_data
GROUP BY EXTRACT(year FROM d), to_char(d,'Mon'), EXTRACT(month FROM d)
ORDER BY month
\crosstabview "month name" year:year format
-- combine contents vertically into the same cell (V/H duplicates)
SELECT v, h, string_agg(c, E'\n') FROM ctv_data GROUP BY v, h ORDER BY 1,2,3
\crosstabview 1 2 3
-- horizontal ASC order from window function
SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h) AS r
FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
\crosstabview v h:r c
-- horizontal DESC order from window function
SELECT v, h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h DESC) AS r
FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
\crosstabview v h:r c
-- horizontal ASC order from window function, NULLs pushed rightmost
SELECT v,h, string_agg(c, E'\n') AS c, row_number() OVER(ORDER BY h NULLS LAST) AS r
FROM ctv_data GROUP BY v, h ORDER BY 1,3,2
\crosstabview v h:r c
-- only null, no column name, 2 columns: error
SELECT null,null \crosstabview
-- only null, no column name, 3 columns: works
SELECT null,null,null \crosstabview
-- null display
\pset null '#null#'
SELECT v,h, string_agg(i::text, E'\n') AS i FROM ctv_data
GROUP BY v, h ORDER BY h,v
\crosstabview v h i
\pset null ''
-- refer to columns by position
SELECT v,h,string_agg(i::text, E'\n'), string_agg(c, E'\n')
FROM ctv_data GROUP BY v, h ORDER BY h,v
\crosstabview 2 1 4
-- refer to columns by positions and names mixed
SELECT v,h, string_agg(i::text, E'\n') AS i, string_agg(c, E'\n') AS c
FROM ctv_data GROUP BY v, h ORDER BY h,v
\crosstabview 1 "h" 4
-- error: bad column name
SELECT v,h,c,i FROM ctv_data
\crosstabview v h j
-- error: bad column number
SELECT v,h,i,c FROM ctv_data
\crosstabview 2 1 5
-- error: same H and V columns
SELECT v,h,i,c FROM ctv_data
\crosstabview 2 h 4
-- error: too many columns
SELECT a,a,1 FROM generate_series(1,3000) AS a
\crosstabview
-- error: only one column
SELECT 1 \crosstabview
DROP TABLE ctv_data;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment