How do you perform the equivalent of Oracle's DESCRIBE TABLE
in PostgreSQL with psql command?

- 22,221
- 10
- 124
- 129

- 22,772
- 3
- 20
- 21
24 Answers

- 89,526
- 13
- 117
- 169

- 87,773
- 37
- 126
- 127
-
11I had originally accepted devinmoore's answer but I really like this one better. Not only does it describe the table but it also shows the metadata such as column descriptions and if there are any OIDs. – Mr. Muskrat Sep 20 '08 at 21:08
-
38The `+` is really clutch, as PostgresSQL 9 only gives the in-depth description for views when you do `\d+ table_name`, rather than the simple `\d table_name` – nessur May 04 '11 at 22:08
-
13\d doesn't work when you invoke it in PosgreSQL 9.1 through pgAdmin, Vinko's answer below is applicable to more cases – hello_earth Jul 18 '12 at 13:38
-
14`psql -E` is handy to get the sql that implements `\d+` and similar (for use outside of the psql prompt) – bsb Aug 19 '13 at 05:34
-
1Note: This only works from the command-line `psql` client. Under the hood, it issues a query to get the information from the server as shown in the answer below. – Matt Ryall Oct 03 '13 at 02:50
-
27Error: "did not find any relation named". This means you need to wrap your table's name in double quotes. Apparently, postgres will lower case your table name without them and therefore not find your table. Hope this helps anyone else who comes here and has this problem. :) – amurrell Mar 31 '15 at 00:57
-
4Whoever came up with a CLI interface that makes one letter commands that start with the same letter as *d*rop or *d*elete should not be designing databases. – Andy Ray Apr 25 '15 at 01:21
-
1This is a hard one to remember, keep confuding \dt (list tables in a database) with \d+ tablename (describe a table). – Daniel Viglione Jan 03 '17 at 20:39
-
1I always come here, and never remember the sytax :D... this is the 3rd time this month :D – thapakazi Dec 28 '17 at 10:43
In addition to the PostgreSQL way (\d 'something' or \dt 'table' or \ds 'sequence' and so on)
The SQL standard way, as shown here:
select column_name, data_type, character_maximum_length, column_default, is_nullable
from INFORMATION_SCHEMA.COLUMNS where table_name = '<name of table>';
It's supported by many db engines.

- 8,989
- 7
- 48
- 78

- 330,807
- 53
- 334
- 373
-
25select column_name,data_type,character_maximum_length from INFORMATION_SCHEMA.COLUMNS where table_name = 'table'; – SO Stinks Sep 23 '10 at 03:05
-
8This is more useful than \d when you're stuck with an pre-8.4 psql and a post-8.4 server - the \d command is incompatible. – beldaz Oct 05 '10 at 19:21
-
31Also this command runs against RedShift, where `\d+` does not. **This is the best answer IMO** – New Alexandria Apr 03 '13 at 14:27
-
8
-
2\d, \d+ do not work from Navicat. This little query is good enough! This should have been the answer! – Kumar Vaibhav Nov 30 '14 at 05:41
-
2
-
1
-
4This only lists columns with minimal information. \d+ gives full DDL for the table including: defaults, nullability, nextval, precision, primary key, foreign keys, indexes, check constraints, and FK's from other tables. – bradw2k Jan 19 '17 at 18:08
-
-
@Dan The 'columns' table doesn't hold info about things like primary key, foreign keys, indexes, check constraints. In one project I left join from 'columns' to 'key_column_usage' and 'table_constraints' to pick up PK and FK constraints. All the info is in that schema for sure. – bradw2k Dec 28 '17 at 20:17
-
This is actually the most precise answer, as Oracle's DESCRIBE will list only the column information and so does this command. All the other stuff reported by \d (indexes, constraints) are not part of a simple DESCRIBE, and often I don't want to see them - pity there's no \d- command to make psql be a little less verbose... – volkerk Aug 28 '19 at 18:06
-
If you want to obtain it from query instead of psql, you can query the catalog schema. Here's a complex query that does that:
SELECT
f.attnum AS number,
f.attname AS name,
f.attnum,
f.attnotnull AS notnull,
pg_catalog.format_type(f.atttypid,f.atttypmod) AS type,
CASE
WHEN p.contype = 'p' THEN 't'
ELSE 'f'
END AS primarykey,
CASE
WHEN p.contype = 'u' THEN 't'
ELSE 'f'
END AS uniquekey,
CASE
WHEN p.contype = 'f' THEN g.relname
END AS foreignkey,
CASE
WHEN p.contype = 'f' THEN p.confkey
END AS foreignkey_fieldnum,
CASE
WHEN p.contype = 'f' THEN g.relname
END AS foreignkey,
CASE
WHEN p.contype = 'f' THEN p.conkey
END AS foreignkey_connnum,
CASE
WHEN f.atthasdef = 't' THEN d.adsrc
END AS default
FROM pg_attribute f
JOIN pg_class c ON c.oid = f.attrelid
JOIN pg_type t ON t.oid = f.atttypid
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
WHERE c.relkind = 'r'::char
AND n.nspname = '%s' -- Replace with Schema name
AND c.relname = '%s' -- Replace with table name
AND f.attnum > 0 ORDER BY number
;
It's pretty complex but it does show you the power and flexibility of the PostgreSQL system catalog and should get you on your way to pg_catalog mastery ;-). Be sure to change out the %s's in the query. The first is Schema and the second is the table name.

- 8,414
- 4
- 39
- 69

- 4,551
- 4
- 33
- 29
-
3This query is better shown [here](http://en.wikibooks.org/wiki/Converting_MySQL_to_PostgreSQL#Language_Constructs) note that they suggest "\d table" too – Flavien Volken Oct 12 '11 at 14:05
-
4One advantage of this solution is that `format_type()` will include any modifiers attached to the type, e.g. `numeric(6,2)`; whereas `information_schema.columns` will only report the base type of `numeric`. – Eli Collins Dec 06 '14 at 02:26
-
3How do I split the data type from the size? say | character varying(50) | to 2 columns: | character varying | 50 | – ivanceras May 20 '15 at 10:44
-
You can do that with a psql slash command:
\d myTable describe table
It also works for other objects:
\d myView describe view
\d myIndex describe index
\d mySequence describe sequence
Source: faqs.org

- 16,251
- 4
- 53
- 90

- 2,726
- 3
- 19
- 14
This should be the solution:
SELECT * FROM information_schema.columns
WHERE table_schema = 'your_schema'
AND table_name = 'your_table'

- 1,748
- 6
- 18
- 24

- 1,613
- 2
- 15
- 30
-
2This is a more complete answer. If you are not getting any rows back, try this. One must always provide schema name when in doubt. – Vijay Kumar Aug 18 '22 at 20:35
The psql equivalent of DESCRIBE TABLE
is \d table
.
See the psql portion of the PostgreSQL manual for more details.

- 4,229
- 5
- 37
- 33

- 22,772
- 3
- 20
- 21
-
3Also, psql database selction is `\c databasename` rather than `use databasename` (for those coming from MySQL like myself :-). Without `\c databasename` first, `\d tablename` produces `No relations found.` message and nothing more. – Ville Dec 03 '15 at 05:10
-
Well, you can just `\d databasename.tablename` too. With proper quoting if necessary, i.e. `\d "DatabaseName"."TableName"`, if your names are not all lowercase. – ddevienne Sep 22 '21 at 06:59
You may do a \d *search pattern *
with asterisks to find tables that match the search pattern you're interested in.
-
This was what I was looking for - how to describe a __subset__ of tables. Of note, I also found that if your tables have uppercase, the syntax is `\d *"
"*`. That is, the double quotes must be _inside_ the asterisks. Though, if you just want the __list__ of tables then you want to use `\dt` – Randall Apr 07 '17 at 17:46 -
In addition to the command line \d+ <table_name>
you already found, you could also use the information-schema to look up the column data, using info_schema.columns
SELECT *
FROM info_schema.columns
WHERE table_schema = 'your_schema'
AND table_name = 'your_table'

- 7,772
- 4
- 32
- 38

- 2,816
- 1
- 19
- 32
-
9`FROM info_schema.columns` didn't work for me I had to use `from information_schema.columns`, not sure if that's a typo in your answer or some implementation issue at my end. – user27874 Jan 17 '17 at 16:48
Use the following SQL statement
SELECT DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'tbl_name'
AND COLUMN_NAME = 'col_name'
If you replace tbl_name and col_name, it displays data type of the particular coloumn that you looking for.

- 2,350
- 4
- 35
- 60

- 489
- 6
- 11
-
3That's what [this answer](http://stackoverflow.com/a/109337/19068) from 2008 says. – Quentin May 05 '16 at 11:45
-
@Quentin-There is difference in both of them..the above 2008 Solution describes column_name, data_type, character_maximum_length for the whole table. Where as mine - the mentioned solution - only shows the data type of the schema column. Run both and check. They both are different. All the solutions here are different ways to solve a problem. User can use this for different reasons – Mr.Tananki May 06 '16 at 01:18
You can use this :
SELECT attname
FROM pg_attribute,pg_class
WHERE attrelid=pg_class.oid
AND relname='TableName'
AND attstattarget <>0;

- 2,350
- 4
- 35
- 60

- 198
- 2
- 8
In MySQL , DESCRIBE table_name
In PostgreSQL , \d table_name
Or , you can use this long command:
SELECT
a.attname AS Field,
t.typname || '(' || a.atttypmod || ')' AS Type,
CASE WHEN a.attnotnull = 't' THEN 'YES' ELSE 'NO' END AS Null,
CASE WHEN r.contype = 'p' THEN 'PRI' ELSE '' END AS Key,
(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid), '\'(.*)\'')
FROM
pg_catalog.pg_attrdef d
WHERE
d.adrelid = a.attrelid
AND d.adnum = a.attnum
AND a.atthasdef) AS Default,
'' as Extras
FROM
pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
JOIN pg_type t ON a.atttypid = t.oid
LEFT JOIN pg_catalog.pg_constraint r ON c.oid = r.conrelid
AND r.conname = a.attname
WHERE
c.relname = 'tablename'
AND a.attnum > 0
ORDER BY a.attnum

- 309
- 4
- 4
This variation of the query (as explained in other answers) worked for me.
SELECT
COLUMN_NAME
FROM
information_schema.COLUMNS
WHERE
TABLE_NAME = 'city';
It's described here in details: http://www.postgresqltutorial.com/postgresql-describe-table/

- 143
- 1
- 6
To improve on the other answer's SQL query (which is great!), here is a revised query. It also includes constraint names, inheritance information, and a data types broken into it's constituent parts (type, length, precision, scale). It also filters out columns that have been dropped (which still exist in the database).
SELECT
n.nspname as schema,
c.relname as table,
f.attname as column,
f.attnum as column_id,
f.attnotnull as not_null,
f.attislocal not_inherited,
f.attinhcount inheritance_count,
pg_catalog.format_type(f.atttypid,f.atttypmod) AS data_type_full,
t.typname AS data_type_name,
CASE
WHEN f.atttypmod >= 0 AND t.typname <> 'numeric'THEN (f.atttypmod - 4) --first 4 bytes are for storing actual length of data
END AS data_type_length,
CASE
WHEN t.typname = 'numeric' THEN (((f.atttypmod - 4) >> 16) & 65535)
END AS numeric_precision,
CASE
WHEN t.typname = 'numeric' THEN ((f.atttypmod - 4)& 65535 )
END AS numeric_scale,
CASE
WHEN p.contype = 'p' THEN 't'
ELSE 'f'
END AS is_primary_key,
CASE
WHEN p.contype = 'p' THEN p.conname
END AS primary_key_name,
CASE
WHEN p.contype = 'u' THEN 't'
ELSE 'f'
END AS is_unique_key,
CASE
WHEN p.contype = 'u' THEN p.conname
END AS unique_key_name,
CASE
WHEN p.contype = 'f' THEN 't'
ELSE 'f'
END AS is_foreign_key,
CASE
WHEN p.contype = 'f' THEN p.conname
END AS foreignkey_name,
CASE
WHEN p.contype = 'f' THEN p.confkey
END AS foreign_key_columnid,
CASE
WHEN p.contype = 'f' THEN g.relname
END AS foreign_key_table,
CASE
WHEN p.contype = 'f' THEN p.conkey
END AS foreign_key_local_column_id,
CASE
WHEN f.atthasdef = 't' THEN d.adsrc
END AS default_value
FROM pg_attribute f
JOIN pg_class c ON c.oid = f.attrelid
JOIN pg_type t ON t.oid = f.atttypid
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
WHERE c.relkind = 'r'::char
AND f.attisdropped = false
AND n.nspname = '%s' -- Replace with Schema name
AND c.relname = '%s' -- Replace with table name
AND f.attnum > 0
ORDER BY f.attnum
;

- 156
- 1
- 6
You can also check using below query
Select * from schema_name.table_name limit 0;
Expmple : My table has 2 columns name and pwd. Giving screenshot below.
*Using PG admin3

- 2,248
- 4
- 33
- 54

- 1,201
- 1
- 11
- 10
-
because selecting and expecting the pgadmin to pickup the slack of getting the meta data is not "best practice" – Mickey Perlstein Nov 25 '18 at 13:42
-
The `LIMIT` clause is evaluated _after_ both `FROM` and `SELECT`; therefore, this query would take a **long** time to finish if the table in question is large. – Vainstein K Dec 20 '20 at 04:54
In postgres \d is used to describe the table structure.
e.g. \d schema_name.table_name
this command will provide you the basic info of table such as, columns, type and modifiers.
If you want more info about table use
\d+ schema_name.table_name
this will give you extra info such as, storage, stats target and description

- 254
- 3
- 3
When your table name starts with a capital letter you should put your table name in the quotation.
Example: \d "Users"

- 231
- 3
- 4
The best way to describe a table such as a column, type, modifiers of columns, etc.
\d+ tablename or \d tablename

- 383
- 4
- 17
Use this command
\d table name
like
\d queuerecords
Table "public.queuerecords"
Column | Type | Modifiers
-----------+-----------------------------+-----------
id | uuid | not null
endtime | timestamp without time zone |
payload | text |
queueid | text |
starttime | timestamp without time zone |
status | text |

- 535
- 5
- 13
When your table is not part of the default schema, you should write:
\d+ schema_name.table_name
Otherwise, you would get the error saying that "the relation doesn not exist."

- 534
- 3
- 18
1) PostgreSQL DESCRIBE TABLE using psql
In psql command line tool, \d table_name or \d+ table_name to find the information on columns of a table
2) PostgreSQL DESCRIBE TABLE using information_schema
SELECT statement to query the column_names,datatype,character maximum length of the columns table in the information_schema database;
SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH from INFORMATION_SCHEMA.COLUMNS where table_name = 'tablename';
For more information https://www.postgresqltutorial.com/postgresql-describe-table/

- 1
- 1

- 475
- 5
- 9
I'll add the pg_dump command even thou the psql command was requested. because it generate an output more common to previous MySQl users.
# sudo -u postgres pg_dump --table=my_table_name --schema-only mydb

- 212
- 1
- 3
- 17
The command below can describe multiple tables simply
\dt <table> <table>
The command below can describe multiple tables in detail:
\d <table> <table>
The command below can describe multiple tables in more detail:
\d+ <table> <table>

- 22,221
- 10
- 124
- 129
/dt is the commad which lists you all the tables present in a database. using
/d command and /d+ we can get the details of a table. The sysntax will be like
* /d table_name (or) \d+ table_name

- 21
- 3
I worked out the following script for get table schema.
'CREATE TABLE ' || 'yourschema.yourtable' || E'\n(\n' ||
array_to_string(
array_agg(
' ' || column_expr
)
, E',\n'
) || E'\n);\n'
from
(
SELECT ' ' || column_name || ' ' || data_type ||
coalesce('(' || character_maximum_length || ')', '') ||
case when is_nullable = 'YES' then ' NULL' else ' NOT NULL' end as column_expr
FROM information_schema.columns
WHERE table_schema || '.' || table_name = 'yourschema.yourtable'
ORDER BY ordinal_position
) column_list;

- 136
- 1
- 4
-
`||` appears to be something like a concatenation operator (joining strings together) – May 29 '18 at 17:53