Related
SELECT
ACCOUNT_NUMBER||','||
pkg_name.function_name(ACCOUNT_NUMBER, null) as "FLAG"
FROM table_name
where clause='value';
The expectation here is to display the columns below
ACCOUNT_NUMBER','FLAG
12345,Y
2323,N
Can someone pls help me to find out the correct query?
If I understood you correctly, result you got is OK (meaning: 12345,Y is OK). But, the whole column's heading is set to FLAG - you'd want to get ACCOUNT_NUMBER','FLAG.
In that case, enclose such a string into double quotes, e.g.
SQL> select dummy "ACCOUNT_NUMBER','FLAG" from dual;
ACCOUNT_NUMBER','FLAG --> here it is
------------------------------
X
SQL>
If you want to give alias to function's result and then concatenate that alias to account number, then you could use a CTE (or even a correlated subquery):
WITH
temp
AS
(SELECT account_number AS anum,
pkg_name.function_name (account_number, NULL) AS flag
FROM table_name
WHERE clause = 'value')
SELECT anum || ',' || flag as result
FROM temp
Here are a few examples:
Function:
SQL> CREATE OR REPLACE FUNCTION f_flag (par_deptno IN NUMBER)
2 RETURN VARCHAR2
3 IS
4 BEGIN
5 RETURN CASE WHEN par_deptno <= 20 THEN 'Y' ELSE 'N' END;
6 END;
7 /
Function created.
An ordinary concatenation:
SQL> SELECT deptno || ',' || f_flag (deptno) AS result
2 FROM emp
3 WHERE ROWNUM <= 5;
RESULT
--------------------------------------------------------------------------------
20,Y
30,N
30,N
20,Y
30,N
Using a CTE:
SQL> WITH
2 temp
3 AS
4 (SELECT deptno, f_flag (deptno) AS flag
5 FROM emp
6 WHERE ROWNUM <= 5)
7 SELECT deptno || ',' || flag AS result
8 FROM temp;
RESULT
--------------------------------------------------------------------------------
20,Y
30,N
30,N
20,Y
30,N
Using correlated subquery within a CTE:
SQL> WITH
2 temp
3 AS
4 (SELECT e.deptno, (SELECT f_flag (e.deptno) FROM DUAL) AS flag
5 FROM emp e
6 WHERE ROWNUM <= 5)
7 SELECT deptno || ',' || flag AS result
8 FROM temp;
RESULT
--------------------------------------------------------------------------------
20,Y
30,N
30,N
20,Y
30,N
SQL>
I still think that the simplest concatenation is the way to go.
I am using LISTAGG function for my query, however, it returned an ORA-01489: result of string concatenation is too long error. So I googled that error and found out I can use ON OVERFLOW TRUNCATE and I implemented that into my SQL but now it generates missing right parenthesis error and I can't seem to figure out why?
My query
SELECT DISTINCT cust_id, acct_no, state, language_indicator, billing_system, market_code,
EMAIL_ADDR, DATE_OF_CHANGE, TO_CHAR(DATE_LOADED, 'DD-MM-YYYY') DATE_LOADED,
(SELECT LISTAGG( SUBSTR(mtn, 7, 4),'<br>' ON OVERFLOW TRUNCATE '***' )
WITHIN GROUP (ORDER BY cust_id || acct_no) mtnlist
FROM process.feature WHERE date_loaded BETWEEN TO_DATE('02-08-2018','MM-dd-yyyy')
AND TO_DATE('02-09-2018', 'MM-dd-yyyy') AND cust_id = ffsr.cust_id
AND acct_no = ffsr.acct_no AND filename = 'FEATURE.VB2B.201802090040'
GROUP BY cust_id||acct_no) mtnlist
FROM process.feature ffsr WHERE date_loaded BETWEEN TO_DATE('02-08-2018','MM-dd-yyyy')
AND TO_DATE('02-09-2018','MM-dd-yyyy') AND cust_id BETWEEN 0542185146 AND 0942025571
AND src_ind = 'B' AND filename = 'FEATURE.VB2B.201802090040'
AND letter_type = 'FA' ORDER BY cust_id;
With a little bit of help by XML, you might get it work. Example is based on HR schema.
SQL> select
2 listagg(s.department_name, ',') within group (order by null) result
3 from departments s, departments d;
from departments s, departments d
*
ERROR at line 3:
ORA-01489: result of string concatenation is too long
SQL>
SQL> select
2 rtrim(xmlagg(xmlelement (e, s.department_name || ',')).extract
3 ('//text()').getclobval(), ',') result
4 from departments s, departments d;
RESULT
--------------------------------------------------------------------------------
Administration,Administration,Administration,Administration,Administration,Admin
SQL>
This demo sourced from livesql.oracle.com
-- Create table with 93 strings of different lengths, plus one NULL string. Notice the only ASCII character not used is '!', so I will use it as a delimiter in LISTAGG.
create table strings as
with letters as (
select level num,
chr(ascii('!')+level) let
from dual
connect by level <= 126 - ascii('!')
union all
select 1, null from dual
)
select rpad(let,num,let) str from letters;
-- Note the use of LENGTHB to get the length in bytes, not characters.
select str,
sum(lengthb(str)+1) over(order by str rows unbounded preceding) - 1 cumul_lengthb,
sum(lengthb(str)+1) over() - 1 total_lengthb,
count(*) over() num_values
from strings
where str is not null;
-- This statement implements the ON OVERFLOW TRUNCATE WITH COUNT option of LISTAGG in 12.2. If there is no overflow, the result is the same as a normal LISTAGG.
select listagg(str, '!') within group(order by str) ||
case when max(total_lengthb) > 4000 then
'! ... (' || (max(num_values) - count(*)) || ')'
end str_list
from (
select str,
sum(lengthb(str)+1) over(order by str) - 1 cumul_lengthb,
sum(lengthb(str)+1) over() - 1 total_lengthb,
count(*) over() num_values
from strings
where str is not null
)
where total_lengthb <= 4000
or cumul_lengthb <= 4000 - length('! ... (' || num_values || ')');
I have an ORA-01489: result of string concatenation is too long error executing this query on a Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production, PL/SQL Release 11.2.0.4.0 - Production, CORE 11.2.0.4.0 Production, TNS for Linux: Version 11.2.0.4.0 - Production, NLSRTL Version 11.2.0.4.0 - Production:
SELECT "USER_PRIMARY_UNIT","LOGIN","FIRST_NAME","LAST_NAME","UNIT_ROLE"
FROM (
SELECT user_primary_unit,login, first_name, last_name,
LTRIM(MAX(SYS_CONNECT_BY_PATH(rights,' / '))
KEEP (DENSE_RANK LAST ORDER BY curr),' / ') AS UNIT_ROLE
FROM
(SELECT login,
first_name,
last_name,
user_primary_unit,
rights,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) AS curr,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) -1 AS prev
FROM (select member0_.login, member0_.first_name first_name, unit2.unit_name user_primary_unit, member0_.last_name last_name,
CONCAT(CONCAT(unit.unit_name, ' - '), role3_.role_name) rights
from
IOT_DEVICES.t_member member0_
inner join IOT_DEVICES.t_user member0_1_ on member0_.member_id=member0_1_.user_id
inner join IOT_DEVICES.t_playable_role playedrole1_ on member0_.member_id=playedrole1_.user_id
inner join IOT_DEVICES.t_unit_role unitrole2_ on playedrole1_.unit_role_id=unitrole2_.unit_role_id
inner join IOT_DEVICES.t_role role3_ on unitrole2_.role_id=role3_.role_id
inner join IOT_DEVICES.t_unit unit on unitrole2_.unit_id=unit.unit_id
inner join IOT_DEVICES.t_unit unit2 on unit2.unit_id=member0_1_.primary_unit_id
where current_date between playedrole1_.start_date and playedrole1_.end_date
order by unit.unit_name
))
GROUP BY login, first_name, last_name, user_primary_unit
CONNECT BY prev = PRIOR curr AND login = PRIOR login
START WITH curr = 1
)
ORDER BY user_PRIMARY_UNIT, FIRST_NAME, LAST_NAME;
The problem with this query is with the use of CONCAT operator (||). Concat operator returns char1 concatenated with char2. The string returned is in the same character set as char1. So here concat operator is trying to return varchar2, which has limit of 4000 characters and getting exceeded. This problem may also come when we try to CONCAT a VARCHAR2 with CLOB. So here I want simply convert its first string to CLOB and avoid this error. After converting first string to CLOB, CONCAT operator will return string of CLOB type
So I add the TO_CLOB to convert the types but then I have the next error:
ORA-00932: inconsistent datatypes: expected - got CLOB
SELECT "USER_PRIMARY_UNIT","LOGIN","FIRST_NAME","LAST_NAME","UNIT_ROLE"
FROM (
SELECT user_primary_unit,login, first_name, last_name,
LTRIM(MAX(SYS_CONNECT_BY_PATH(rights,' / '))
KEEP (DENSE_RANK LAST ORDER BY curr),' / ') AS UNIT_ROLE
FROM
(SELECT login,
first_name,
last_name,
user_primary_unit,
rights,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) AS curr,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) -1 AS prev
FROM (select member0_.login, member0_.first_name first_name, unit2.unit_name user_primary_unit, member0_.last_name last_name,
TO_CLOB(CONCAT(CONCAT(unit.unit_name, ' - '), role3_.role_name)) rights
from
IOT_DEVICES.t_member member0_
inner join IOT_DEVICES.t_user member0_1_ on member0_.member_id=member0_1_.user_id
inner join IOT_DEVICES.t_playable_role playedrole1_ on member0_.member_id=playedrole1_.user_id
inner join IOT_DEVICES.t_unit_role unitrole2_ on playedrole1_.unit_role_id=unitrole2_.unit_role_id
inner join IOT_DEVICES.t_role role3_ on unitrole2_.role_id=role3_.role_id
inner join IOT_DEVICES.t_unit unit on unitrole2_.unit_id=unit.unit_id
inner join IOT_DEVICES.t_unit unit2 on unit2.unit_id=member0_1_.primary_unit_id
where current_date between playedrole1_.start_date and playedrole1_.end_date
order by unit.unit_name
))
GROUP BY login, first_name, last_name, user_primary_unit
CONNECT BY prev = PRIOR curr AND login = PRIOR login
START WITH curr = 1
)
ORDER BY user_PRIMARY_UNIT, FIRST_NAME, LAST_NAME;
I also tried to use the package Hierarchy defined Here but then I got a ORA-00932: inconsistent datatypes: expected - got CLOB
https://community.oracle.com/thread/965324?start=0&tstart=0
SELECT "USER_PRIMARY_UNIT","LOGIN","FIRST_NAME","LAST_NAME","UNIT_ROLE"
FROM (
SELECT user_primary_unit,login, first_name, last_name,
LTRIM(MAX(hierarchy.branch(level,rights,' / '))
KEEP (DENSE_RANK LAST ORDER BY curr),' / ') AS UNIT_ROLE
FROM
(SELECT login,
first_name,
last_name,
user_primary_unit,
rights,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) AS curr,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) -1 AS prev
FROM (select member0_.login, member0_.first_name first_name, unit2.unit_name user_primary_unit, member0_.last_name last_name,
TO_CLOB(CONCAT(CONCAT(unit.unit_name, ' - '), role3_.role_name)) rights
from
IOT_DEVICES.t_member member0_
inner join IOT_DEVICES.t_user member0_1_ on member0_.member_id=member0_1_.user_id
inner join IOT_DEVICES.t_playable_role playedrole1_ on member0_.member_id=playedrole1_.user_id
inner join IOT_DEVICES.t_unit_role unitrole2_ on playedrole1_.unit_role_id=unitrole2_.unit_role_id
inner join IOT_DEVICES.t_role role3_ on unitrole2_.role_id=role3_.role_id
inner join IOT_DEVICES.t_unit unit on unitrole2_.unit_id=unit.unit_id
inner join IOT_DEVICES.t_unit unit2 on unit2.unit_id=member0_1_.primary_unit_id
where current_date between playedrole1_.start_date and playedrole1_.end_date
order by unit.unit_name
))
GROUP BY login, first_name, last_name, user_primary_unit
CONNECT BY prev = PRIOR curr AND login = PRIOR login
START WITH curr = 1
)
ORDER BY user_PRIMARY_UNIT, FIRST_NAME, LAST_NAME;
Then I tried as well with sys.stragg , but i got a ORA-00978: nested group function without GROUP BY
SELECT "USER_PRIMARY_UNIT","LOGIN","FIRST_NAME","LAST_NAME","UNIT_ROLE"
FROM (
SELECT user_primary_unit,login, first_name, last_name,
LTRIM(MAX(SYS_CONNECT_BY_PATH(rights,' / '))
KEEP (DENSE_RANK LAST ORDER BY curr),' / ') AS UNIT_ROLE
FROM
(SELECT login,
first_name,
last_name,
user_primary_unit,
rights,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) AS curr,
ROW_NUMBER() OVER (PARTITION BY login ORDER BY rights) -1 AS prev
FROM (select member0_.login, member0_.first_name first_name, unit2.unit_name user_primary_unit, member0_.last_name last_name,
sys.stragg(sys.stragg(unit.unit_name || ' - ' || role3_.role_name)) rights
from
IOT_DEVICES.t_member member0_
inner join IOT_DEVICES.t_user member0_1_ on member0_.member_id=member0_1_.user_id
inner join IOT_DEVICES.t_playable_role playedrole1_ on member0_.member_id=playedrole1_.user_id
inner join IOT_DEVICES.t_unit_role unitrole2_ on playedrole1_.unit_role_id=unitrole2_.unit_role_id
inner join IOT_DEVICES.t_role role3_ on unitrole2_.role_id=role3_.role_id
inner join IOT_DEVICES.t_unit unit on unitrole2_.unit_id=unit.unit_id
inner join IOT_DEVICES.t_unit unit2 on unit2.unit_id=member0_1_.primary_unit_id
where current_date between playedrole1_.start_date and playedrole1_.end_date
order by unit.unit_name
))
GROUP BY login, first_name, last_name, user_primary_unit
CONNECT BY prev = PRIOR curr AND login = PRIOR login
START WITH curr = 1
)
ORDER BY user_PRIMARY_UNIT, FIRST_NAME, LAST_NAME;
You can build hierarchy CLOB path with subquery factoring syntax This may run really slow. Consider having two path columns - one for varchar2 result and one for CLOB. Build varchar2 till size allows, and keep NULL in CLOB path, and switch to CLOB when out of varchar2 capacity. This is a different question though.
with
base as (
select
level as id,
case when level > 1 then level - 1 end as parent_id,
dbms_random.string('X', 2000) as val
from dual
connect by level <= 50
),
hier(id, parent_id, val, path) as (
select
b.id,
b.parent_id,
b.val,
to_clob(concat('/', b.val)) as path
from base b
where b.parent_id is null
union all
select
b.id,
b.parent_id,
b.val,
concat(h.path, to_clob(' / '||b.val) )
from base b
join hier h on h.id = b.parent_id
)
select rownum, length(h.path)
from hier h;
ROWNUM LENGTH(H.PATH)
1 2001
2 4004
3 6007
4 8010
5 10013
6 12016
7 14019
8 16022
9 18025
10 20028
11 22031
12 24034
13 26037
14 28040
15 30043
16 32046
17 34049
18 36052
19 38055
20 40058
21 42061
22 44064
23 46067
24 48070
25 50073
26 52076
27 54079
28 56082
29 58085
30 60088
31 62091
32 64094
33 66097
34 68100
35 70103
36 72106
37 74109
38 76112
39 78115
40 80118
41 82121
42 84124
43 86127
44 88130
45 90133
46 92136
47 94139
48 96142
49 98145
50 100148
You might found this page, because you're trying to aggregate strings longer than 4000 characters and remembered the different techniques.
If so, I created a small example, based on #B Samedi's answer to help you out, when you're not able to use user defined aggregates
with dummy_text as (
select 'teststring ' || rownum str from dual connect by rownum < 2
)
, indexed_strings as (
select str, row_number() over (order by 'x') rn, ',' separator from dummy_text
)
, hier (str, lvl) as (
select to_clob(i.str), rn from indexed_strings i where rn = (select max(rn) from indexed_strings)
union all
select concat(to_clob(concat(i.str, i.separator)), h.str), h.lvl - 1 from indexed_strings i join hier h on h.lvl - 1 = i.rn
)
select str from hier where lvl = 1
I have data in a table which looks like:
I want to split its data and make it look like the following through a sql query in Oracle (without using pivot):
How can it be done?? is there any other way of doing so without using pivot?
You need to use a pivot query here to get the output you want:
SELECT Name,
MIN(CASE WHEN ID_Type = 'PAN' THEN ID_No ELSE NULL END) AS PAN,
MIN(CASE WHEN ID_Type = 'DL' THEN ID_No ELSE NULL END) AS DL,
MIN(CASE WHEN ID_Type = 'Passport' THEN ID_No ELSE NULL END) AS Passport
FROM yourTable
GROUP BY Name
You could also try using Oracle's built in PIVOT() function if you are running version 11g or later.
Since you mention without using PIVOT function, you can try to
use SQL within group for moving rows onto one line and listagg to display multiple column values in a single column.
In Oracle 11g, we can use the listagg built-in function :
select
deptno,
listagg (ename, ',')
WITHIN GROUP
(ORDER BY ename) enames
FROM
emp
GROUP BY
deptno
Which should give you the below result:
DEPTNO ENAMES
------ --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
You can find all the solution(s) to this problem here:
http://www.dba-oracle.com/t_converting_rows_columns.htm
For Oracle 11g and above, you could use PIVOT.
For pre-11g release, you could use MAX and CASE.
A common misconception, about PIVOT better in terms of performance than the old way of MAX and DECODE. But, under the hood PIVOT is same MAX + CASE. You can check it in 12c where Oracle added EXPAND_SQL_TEXT procedure to DBMS_UTILITY package.
For example,
SQL> variable c clob
SQL> begin
2 dbms_utility.expand_sql_text(Q'[with choice_tbl as (
3 select 'Jones' person,1 choice_nbr,'Yellow' color from dual union all
4 select 'Jones',2,'Green' from dual union all
5 select 'Jones',3,'Blue' from dual union all
6 select 'Smith',1,'Orange' from dual
7 )
8 select *
9 from choice_tbl
10 pivot(
11 max(color)
12 for choice_nbr in (1 choice_nbr1,2 choice_nbr2,3 choice_nbr3)
13 )]',:c);
14 end;
15 /
PL/SQL procedure successfully completed.
Now let's see what Oracle actually does internally:
SQL> set long 100000
SQL> print c
C
--------------------------------------------------------------------------------
SELECT "A1"."PERSON" "PERSON",
"A1"."CHOICE_NBR1" "CHOICE_NBR1",
"A1"."CHOICE_NBR2" "CHOICE_NBR2",
"A1"."CHOICE_NBR3" "CHOICE_NBR3"
FROM (
SELECT "A2"."PERSON" "PERSON",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=1) THEN "A2"."COLOR" END ) "CHOICE_NBR1",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=2) THEN "A2"."COLOR" END ) "CHOICE_NBR2",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=3) THEN "A2"."COLOR" END ) "CHOICE_NBR3"
FROM (
(SELECT 'Jones' "PERSON",1 "CHOICE_NBR",'Yellow' "COLOR" FROM "SYS"."DUAL" "A7") UNION ALL
(SELECT 'Jones' "'JONES'",2 "2",'Green' "'GREEN'" FROM "SYS"."DUAL" "A6") UNION ALL
(SELECT 'Jones' "'JONES'",3 "3",'Blue' "'BLUE'" FROM "SYS"."DUAL" "A5") UNION ALL
(SELECT 'Smith' "'SMITH'",1 "1",'Orange' "'ORANGE'" FROM "SYS"."DUAL" "A4")
) "A2"
GROUP BY "A2"."PERSON"
) "A1"
SQL>
Oracle internally interprets the PIVOT as MAX + CASE.
You're able to create a non-pivot query by understanding what the pivot query will do:
select *
from yourTable
pivot
(
max (id_no)
for (id_type) in ('PAN' as pan, 'DL' as dl, 'Passport' as passport)
)
What the pivot does is GROUP BY all columns not specified inside the PIVOT clause (actually, just the name column), selecting new columns in a subquery fashion based on the aggregations before the FOR clause for each value specified inside the IN clause and discarding those columns specified inside the PIVOT clause.
When I say "subquery fashion" I'm refering to one way to achieve the result got with PIVOT. Actually, I don't know how this works behind the scenes. This subquery fashion would be like this:
select <aggregation>
from <yourTable>
where 1=1
and <FORclauseColumns> = <INclauseValue>
and <subqueryTableColumns> = <PIVOTgroupedColumns>
Now you identified how you can create a query without the PIVOT clause:
select
name,
(select max(id_no) from yourTable where name = t.name and id_type = 'PAN') as pan,
(select max(id_no) from yourTable where name = t.name and id_type = 'DL') as dl,
(select max(id_no) from yourTable where name = t.name and id_type = 'Passport') as passport
from yourTable t
group by name
You can use CTE's to break the data down and then join them back together to get what you want:
WITH NAMES AS (SELECT DISTINCT NAME
FROM YOURTABLE),
PAN AS (SELECT NAME, ID_NO AS PAN
FROM YOURTABLE
WHERE ID_TYPE = 'PAN'),
DL AS (SELECT NAME, ID_NO AS DL
FROM YOURTABLE
WHERE ID_TYPE = 'DL'),
PASSPORT AS (SELECT NAME, ID_NO AS "Passport"
FROM YOURTABLE
WHERE ID_TYPE = 'Passport')
SELECT n.NAME, p.PAN, d.DL, t."Passport"
FROM NAMES n
LEFT OUTER JOIN PAN p
ON p.NAME = n.NAME
LEFT OUTER JOIN DL d
ON d.NAME = p.NAME
LEFT OUTER JOIN PASSPORT t
ON t.NAME = p.NAME'
Replace YOURTABLE with the actual name of your table of interest.
Best of luck.
I have a PostgreSQL function (or table) which gives me the following output:
Sl.no username Designation salary etc..
1 A XYZ 10000 ...
2 B RTS 50000 ...
3 C QWE 20000 ...
4 D HGD 34343 ...
Now I want the Output as below:
Sl.no 1 2 3 4 ...
Username A B C D ...
Designation XYZ RTS QWE HGD ...
Salary 10000 50000 20000 34343 ...
How to do this?
SELECT
unnest(array['Sl.no', 'username', 'Designation','salary']) AS "Columns",
unnest(array[Sl.no, username, value3Count,salary]) AS "Values"
FROM view_name
ORDER BY "Columns"
Reference : convertingColumnsToRows
Basing my answer on a table of the form:
CREATE TABLE tbl (
sl_no int
, username text
, designation text
, salary int
);
Each row results in a new column to return. With a dynamic return type like this, it's hardly possible to make this completely dynamic with a single call to the database. Demonstrating solutions with two steps:
Generate query
Execute generated query
Generally, this is limited by the maximum number of columns a table can hold. So not an option for tables with more than 1600 rows (or fewer). Details:
What is the maximum number of columns in a PostgreSQL select query
Postgres 9.4+
Dynamic solution with crosstab()
Use the first one you can. Beats the rest.
SELECT 'SELECT *
FROM crosstab(
$ct$SELECT u.attnum, t.rn, u.val
FROM (SELECT row_number() OVER () AS rn, * FROM '
|| attrelid::regclass || ') t
, unnest(ARRAY[' || string_agg(quote_ident(attname)
|| '::text', ',') || '])
WITH ORDINALITY u(val, attnum)
ORDER BY 1, 2$ct$
) t (attnum bigint, '
|| (SELECT string_agg('r'|| rn ||' text', ', ')
FROM (SELECT row_number() OVER () AS rn FROM tbl) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attnum > 0
AND NOT attisdropped
GROUP BY attrelid;
Operating with attnum instead of actual column names. Simpler and faster. Join the result to pg_attribute once more or integrate column names like in the pg 9.3 example.
Generates a query of the form:
SELECT *
FROM crosstab(
$ct$
SELECT u.attnum, t.rn, u.val
FROM (SELECT row_number() OVER () AS rn, * FROM tbl) t
, unnest(ARRAY[sl_no::text,username::text,designation::text,salary::text]) WITH ORDINALITY u(val, attnum)
ORDER BY 1, 2$ct$
) t (attnum bigint, r1 text, r2 text, r3 text, r4 text);
This uses a whole range of advanced features. Just too much to explain.
Simple solution with unnest()
One unnest() can now take multiple arrays to unnest in parallel.
SELECT 'SELECT * FROM unnest(
''{sl_no, username, designation, salary}''::text[]
, ' || string_agg(quote_literal(ARRAY[sl_no::text, username::text, designation::text, salary::text])
|| '::text[]', E'\n, ')
|| E') \n AS t(col,' || string_agg('row' || sl_no, ',') || ')' AS sql
FROM tbl;
Result:
SELECT * FROM unnest(
'{sl_no, username, designation, salary}'::text[]
,'{10,Joe,Music,1234}'::text[]
,'{11,Bob,Movie,2345}'::text[]
,'{12,Dave,Theatre,2356}'::text[])
AS t(col,row1,row2,row3,row4);
db<>fiddle here
Old sqlfiddle
Postgres 9.3 or older
Dynamic solution with crosstab()
Completely dynamic, works for any table. Provide the table name in two places:
SELECT 'SELECT *
FROM crosstab(
''SELECT unnest(''' || quote_literal(array_agg(attname))
|| '''::text[]) AS col
, row_number() OVER ()
, unnest(ARRAY[' || string_agg(quote_ident(attname)
|| '::text', ',') || ']) AS val
FROM ' || attrelid::regclass || '
ORDER BY generate_series(1,' || count(*) || '), 2''
) t (col text, '
|| (SELECT string_agg('r'|| rn ||' text', ',')
FROM (SELECT row_number() OVER () AS rn FROM tbl) t)
|| ')' AS sql
FROM pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attnum > 0
AND NOT attisdropped
GROUP BY attrelid;
Could be wrapped into a function with a single parameter ...
Generates a query of the form:
SELECT *
FROM crosstab(
'SELECT unnest(''{sl_no,username,designation,salary}''::text[]) AS col
, row_number() OVER ()
, unnest(ARRAY[sl_no::text,username::text,designation::text,salary::text]) AS val
FROM tbl
ORDER BY generate_series(1,4), 2'
) t (col text, r1 text,r2 text,r3 text,r4 text);
Produces the desired result:
col r1 r2 r3 r4
-----------------------------------
sl_no 1 2 3 4
username A B C D
designation XYZ RTS QWE HGD
salary 10000 50000 20000 34343
Simple solution with unnest()
SELECT 'SELECT unnest(''{sl_no, username, designation, salary}''::text[] AS col)
, ' || string_agg('unnest('
|| quote_literal(ARRAY[sl_no::text, username::text, designation::text, salary::text])
|| '::text[]) AS row' || sl_no, E'\n , ') AS sql
FROM tbl;
Slow for tables with more than a couple of columns.
Generates a query of the form:
SELECT unnest('{sl_no, username, designation, salary}'::text[]) AS col
, unnest('{10,Joe,Music,1234}'::text[]) AS row1
, unnest('{11,Bob,Movie,2345}'::text[]) AS row2
, unnest('{12,Dave,Theatre,2356}'::text[]) AS row3
, unnest('{4,D,HGD,34343}'::text[]) AS row4
Same result.
If (like me) you were needing this information from a bash script, note there is a simple command-line switch for psql to tell it to output table columns as rows:
psql mydbname -x -A -F= -c "SELECT * FROM foo WHERE id=123"
The -x option is the key to getting psql to output columns as rows.
I have a simpler approach than Erwin pointed above, that worked for me with Postgres (and I think that it should work with all major relational databases whose support SQL standard)
You can use simply UNION instead of crosstab:
SELECT text 'a' AS "text" UNION SELECT 'b';
text
------
a
b
(2 rows)
Of course that depends on the case in which you are going to apply this. Considering that you know beforehand what fields you need, you can take this approach even for querying different tables. I.e.:
SELECT 'My first metric' as name, count(*) as total from first_table UNION
SELECT 'My second metric' as name, count(*) as total from second_table
name | Total
------------------|--------
My first metric | 10
My second metric | 20
(2 rows)
It's a more maintainable approach, IMHO. Look at this page for more information: https://www.postgresql.org/docs/current/typeconv-union-case.html
There is no proper way to do this in plain SQL or PL/pgSQL.
It will be way better to do this in the application, that gets the data from the DB.