Question

I'm writing a request for postgresql database (9.2) to describe the structure of all schema tables. To make the result of the request more readable, I want to add an epmty record after rows, corresponded to the exact table.

I think the easiest way to reach that, is using WITH statement. So I experience some problems here.

Current result rowset part:

"table_name"    "column_name"   "format_type"   "description"   "is_pk" "is_nullable"   "foreign_table" "foreign_column"
"active_keys"   "TABLE" ""  "Login activation keys" ""  ""  ""  ""
""  "Key"   "character varying(900)"    "Activation key"    "PK"    ""  ""  ""
""  "LoginID"   "bigint"    "Activated login   ""   "Y" ""  ""
"addresses" "TABLE" ""  ""  ""  ""  ""  ""
""  "IDRec" "integer"   ""  ""  ""  ""  ""

Wanted result rowset part:

"table_name"    "column_name"   "format_type"   "description"   "is_pk" "is_nullable"   "foreign_table" "foreign_column"
"active_keys"   "TABLE" ""  "Login activation keys" ""  ""  ""  ""
""  "Key"   "character varying(900)"    "Activation key"    "PK"    ""  ""  ""
""  "LoginID"   "bigint"    "Activated login   ""   "Y" ""  ""

""  ""  ""  ""  ""  ""  ""  "" -- <-- empty record to determine next table description block

"addresses" "TABLE" ""  ""  ""  ""  ""  ""
""  "IDRec" "integer"   ""  ""  ""  ""  ""

Report SQL query (for my schema namespace 'stm'):

with  meta_constr_pk as (
    SELECT DISTINCT tc.constraint_name,
        tc.table_name,
        kcu.column_name,
        tc.constraint_type
    FROM information_schema.table_constraints tc
    JOIN information_schema.key_column_usage kcu ON tc.constraint_name::text = kcu.constraint_name::text
    WHERE tc.constraint_type::text = 'PRIMARY KEY' ::text
    ORDER BY tc.table_name ),
meta_constr_fk as (
    SELECT DISTINCT tc.table_name,
        kcu.column_name,
        ccu.table_name AS foreign_table_name,
        ccu.column_name AS foreign_column_name,
        tc.constraint_type
    FROM information_schema.table_constraints tc
    JOIN information_schema.key_column_usage kcu ON tc.constraint_name::text = kcu.constraint_name::text
    JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name::text = tc.constraint_name::text
    WHERE tc.constraint_type::text = 'FOREIGN KEY' ::text
    ORDER BY tc.table_name ),
main_select AS (
    select    case when a.attname = 'tableoid' then c.relname else '' end as table_name,
        replace(a.attname, 'tableoid', 'TABLE') as column_name,
        replace(format_type(a.atttypid, a.atttypmod), 'oid', '') as format_type,   
        coalesce( coalesce( col_description(c.oid, a.attnum),
        obj_description(c.oid)), '' ) as description,
        case when pk.constraint_type is not null then 'PK' else '' end  is_pk,
        case when col.is_nullable = 'YES' then 'Y' else '' end is_nullable,  
        coalesce( fk.foreign_table_name, '') foreign_table,
        coalesce(fk.foreign_column_name, '') foreign_column
    from pg_class c
    join pg_attribute a on (a.attrelid = c.oid)
    join pg_namespace n on (n.oid = c.relnamespace)
    left join information_schema.columns col on (col.table_name = c.relname and col.column_name = a.attname )   
    left join meta_constr_pk pk on (col.table_name = pk.table_name and col.column_name = pk.column_name )
    left join meta_constr_fk fk on (col.table_name = fk.table_name and col.column_name = fk.column_name )
    where
        n.nspname = 'stm' and c.relkind = 'r'::"char" 
        and ( a.attnum >= 0 or a.attnum = -7 ) -- "-7" for tableoid 
    order by c.relname, COALESCE( col.ordinal_position, 0 )
)
select *
from main_select m;

Also any query simplification tips would be accepted with gratitude!

Was it helpful?

Solution

You could cross join the query with a ad-hoc table like this:

CROSS JOIN (VALUES(0),(1)) AS l(line)

And then make it so that the l.line=1 connects only with the row connecting table name and that name doesn't get displayed if l.line=1 :

WITH  meta_constr_pk AS (
    SELECT DISTINCT tc.constraint_name,
        tc.table_name,
        kcu.column_name,
        tc.constraint_type
    FROM information_schema.table_constraints tc
    JOIN information_schema.key_column_usage kcu ON tc.constraint_name::text = kcu.constraint_name::text
    WHERE tc.constraint_type::text = 'PRIMARY KEY' ::text
    ORDER BY tc.table_name ),
meta_constr_fk AS (
    SELECT DISTINCT tc.table_name,
        kcu.column_name,
        ccu.table_name AS foreign_table_name,
        ccu.column_name AS foreign_column_name,
        tc.constraint_type
    FROM information_schema.table_constraints tc
    JOIN information_schema.key_column_usage kcu ON tc.constraint_name::text = kcu.constraint_name::text
    JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name::text = tc.constraint_name::text
    WHERE tc.constraint_type::text = 'FOREIGN KEY' ::text
    ORDER BY tc.table_name ),
main_select AS (
    SELECT
  CASE WHEN a.attname = 'tableoid' AND l.line=0 THEN c.relname ELSE '' END AS TABLE_NAME,
        CASE WHEN l.line=0 THEN replace(a.attname, 'tableoid', 'TABLE') ELSE '' END AS COLUMN_NAME,
        replace(format_type(a.atttypid, a.atttypmod), 'oid', '') AS format_type,
        coalesce( coalesce( col_description(c.oid, a.attnum),
        obj_description(c.oid)), '' ) AS description,
        CASE WHEN pk.constraint_type IS NOT NULL THEN 'PK' ELSE '' END  is_pk,
        CASE WHEN col.is_nullable = 'YES' THEN 'Y' ELSE '' END is_nullable,
        coalesce( fk.foreign_table_name, '') foreign_table,
        coalesce(fk.foreign_column_name, '') foreign_column
    FROM pg_class c
    CROSS JOIN (VALUES(0),(1)) AS l(line)
    JOIN pg_attribute a ON (a.attrelid = c.oid) AND (l.line=0 OR (l.line=1 AND a.attname = 'tableoid'))
    JOIN pg_namespace n ON (n.oid = c.relnamespace) --AND l.line=0
    LEFT JOIN information_schema.columns col ON (col.TABLE_NAME = c.relname AND col.COLUMN_NAME = a.attname ) AND l.line=0
    LEFT JOIN meta_constr_pk pk ON (col.TABLE_NAME = pk.TABLE_NAME AND col.COLUMN_NAME = pk.COLUMN_NAME ) AND l.line=0
    LEFT JOIN meta_constr_fk fk ON (col.TABLE_NAME = fk.TABLE_NAME AND col.COLUMN_NAME = fk.COLUMN_NAME ) AND l.line=0
    WHERE
      c.relkind = 'r'::"char"
      AND
      n.nspname = 'public'
      AND
      ( a.attnum >= 0 OR a.attnum = -7) -- "-7" for tableoid
    ORDER BY c.relname, l.line, COALESCE( col.ordinal_position, 0 )
)
SELECT *
FROM main_select m;

fiddle (it displays the l.line for easier understanding)

The only problem with this approach is that you also get one empty row at the end of the table, this can be averted with the use of window function row_number().

OTHER TIPS

To make the result of the request more readable, I want to add an epmty record after rows, corresponded to the exact table.

This is a presentation-level issue. It has to do with how the output looks, not what the output is.

That means it's better handled by your reporting tool, not by SQL. SQL provides the data; reporting tools supply the layout.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top