I want to know the columns (names) which belong to each unique constraint.
I can easily find the constraints and the tables names in syscat.tabconst.
However I can't find the columns names.
I'm guessing you're looking for Unique Constraints from an index. Here is a query I've used in the past to look these up for a specific table. You should be able to adapt to what you need based on what you are looking for:
SELECT
T.TABSCHEMA AS TABLE_SCHEMA
,T.TABNAME AS TABLE_NAME
,CASE T.TYPE
WHEN 'F' THEN 'Foreign Key'
WHEN 'I' THEN 'Functional Dependency'
WHEN 'K' THEN 'Check'
WHEN 'P' THEN 'Primary Key'
WHEN 'U' THEN 'Unique'
END AS Type
,I.INDSCHEMA AS INDEX_SCHEMA
,I.INDNAME AS INDEX_NAME
,U.COLNAME AS COLUMN_NAME
,U.COLSEQ AS COLUMN_ORDINAL
,CASE U.COLORDER
WHEN 'A' THEN 'Ascending'
WHEN 'D' THEN 'Descending'
WHEN 'I' THEN 'Included (unordered)'
END AS COLUMN_SORRING
FROM SYSCAT.TABCONST T
JOIN SYSCAT.CONSTDEP C
ON T.CONSTNAME = C.CONSTNAME
JOIN SYSCAT.INDEXES I
ON C.BSCHEMA = I.INDSCHEMA
AND C.BNAME = I.INDNAME
JOIN SYSCAT.INDEXCOLUSE U
ON I.INDSCHEMA = U.INDSCHEMA
AND I.INDNAME = U.INDNAME
WHERE T.TABSCHEMA = #schema
AND T.TABNAME = #table
AND C.BTYPE = 'I' --Indexes Only
ORDER BY
T.TABSCHEMA
,T.TABNAME
,I.INDSCHEMA
,I.INDNAME
,U.COLSEQ
Here are the Info Center articles for the tables involved:
SYSCAT.TABCONST
SYSCAT.CONSTDEP
SYSCAT.INDEXES
SYSCAT.INDEXCOLUSE
Maybe this helps. SYSCAT.COLCHECKS
Or this SYSCAT.CHECKS
Source
Related
I'm trying to solve a slow query in PostgreSQL. I have a table "element" and a table "relation"
The table relation enables to put any items of the table "element" in relation with other items of the same table "element".
Another table "subtype" describes the type of the element. I list here only the most important columns for clarity.
Table: element(id, id_subtype, identification_number)
Table: relation(id, type_source, id_source, type_destination, id_destination)
Table: subtype(id, name, code)
I want to list all entries of the table "element" with the following columns:
Id, identification_number
a concatenated string of all its relations to other elements
a concatenated string of all its relations to other elements of the subtype with code = "zone"
a concatenated string of all its relations to other elements of the subtype with code = "secteur"
I have this query so far
SELECT
e.id, e.name,
string_agg(distinct(elem_identification_number), ', ') as rel_element_string,
string_agg(distinct(elem_zone_identification_number), ', ') as rel_zone_element_string,
string_agg(distinct(elem_sector_identification_number), ', ') as rel_sector_element_string
FROM(
SELECT e.id,
CASE
WHEN elem.id is null THEN null
ELSE concat(s.name, ' ', elem.identification_number)
END AS elem_identification_number,
CASE
WHEN s_zone.id is null THEN null
ELSE elem_zone.identification_number
END AS elem_zone_identification_number,
CASE
WHEN s_sector.id is null THEN null
ELSE elem_sector.identification_number
END AS elem_sector_identification_number
FROM element e
LEFT JOIN relation re ON re.id_source = e.id AND re.type_source = 'element' AND re.type_destination = 'element'
LEFT JOIN element elem ON re.id_destination = elem.id
LEFT JOIN subtype s ON elem.id_subtype = s.id
LEFT JOIN relation re_zone ON re_zone.id_source = e.id AND re_zone.type_source = 'element' AND re_zone.type_destination = 'element' AND re_zone.is_deleted = false
LEFT JOIN element elem_zone ON re_zone.id_destination = elem_zone.id
LEFT JOIN subtype s_zone ON elem_zone.id_subtype = s_zone.id AND s_zone.code = 'zone'
LEFT JOIN relation re_sector ON re_sector.id_source = e.id AND re_sector.type_source = 'element' AND re_sector.type_destination = 'element' AND re_sector.is_deleted = false
LEFT JOIN element elem_sector ON re_sector.id_destination = elem_sector.id
LEFT JOIN subtype s_sector ON elem_sector.id_subtype = s_sector.id AND s_sector.code = 'secteur'
WHERE e.is_deleted = false AND e.id_subtype = 18
UNION ALL
/* Same query but with reveresed id_source - id_destination */
) as e
GROUP BY id, e.identification_number, ...
ORDER BY id DESC";
The query plan of the full request (with all columns) looks like this with the "explain"
https://explain.depesz.com/s/Lk9h
I also have 2 indexes on table "relation"
CREATE INDEX idx_relation
ON public.relation USING btree
(id_chantier ASC NULLS LAST, type_source COLLATE pg_catalog."default" ASC NULLS LAST, id_source ASC NULLS LAST)
CREATE INDEX idx_relation_dest
ON public.relation USING btree
(id_chantier ASC NULLS LAST, type_destination COLLATE pg_catalog."default" ASC NULLS LAST, id_destination ASC NULLS LAST)
Any idea how I can improve the query?
Thank you!
You have a combinatorial explosion here. For example, if each of your string_aggs produces a list of a 100 things for each e, you first have a dataset of 100^3, or a million things, per e before the distinct compacts it back down again.
The way to avoid that is to not write one 10-way join, but rather write 3 correlated subqueries where each subquery has a 3-way join plus a reference to the outer table. Something like:
select e.*,
(select string_agg(...) from relation, element, subtype ...) rel_element_string,
(select string_agg(...) from relation, element, subtype ...) rel_zone_element_string,
(select string_agg(...) from relation, element, subtype ...) rel_sector_element_string
from elements e
WHERE e.is_deleted = false AND e.id_subtype = 18
I have this query to get the list of indexes on a table:
SELECT
ns.nspname as schema_name,
tab.relname as table_name,
cls.relname as index_name,
am.amname as index_type,
idx.indisprimary as is_primary,
idx.indisunique as is_unique
FROM
pg_index idx
INNER JOIN pg_class cls ON cls.oid=idx.indexrelid
INNER JOIN pg_class tab ON tab.oid=idx.indrelid
INNER JOIN pg_am am ON am.oid=cls.relam
INNER JOIN pg_namespace ns on ns.oid=tab.relnamespace
WHERE ns.nspname = #Schema AND tab.relname = #Name
It seems to be working right. But now I need a query for the list of columns and I'm having trouble understanding how the system views work.
Specifically what I'm looking for are:
[index name or id for matching to the first query]
Order in index
Column name
ascending or descending
sorted column or included column
Ideally I would like to get the above items for all indexes of a given table at one time.
Note that I'm looking for more than just the column names.
Use the system catalog information function pg_get_indexdef(index_oid) to get complete information (including the list of index expressions) - in a query against pg_index to get all indexes for a given table:
SELECT pg_get_indexdef(indexrelid) || ';' AS idx
FROM pg_index
WHERE indrelid = 'public.tbl'::regclass; -- optionally schema-qualified
Related:
Django/PostgreSQL varchar to UUID
Copy indexes from one table to another
If you rely on an unqualified table name (without schema) you depend on the current search_path setting and might get results for a table of the same name in a different schema.
Alternatively, you can join to pg_attribute by hand to get individual columns like demonstrated in these related answers:
How to get column attributes query from table name using PostgreSQL?
Find tables with multiple indexes on same column
Key ingredient is to join like this:
FROM pg_index idx
LEFT JOIN pg_attribute a ON a.attrelid = idx.indrelid
AND a.attnum = ANY(idx.indkey)
AND a.attnum > 0
The manual about pg_index.indkey:
This is an array of indnatts values that indicate which table columns
this index indexes. For example a value of 1 3 would mean that the
first and the third table columns make up the index entries. Key
columns come before non-key (included) columns. A zero in this array
indicates that the corresponding index attribute is an expression over
the table columns, rather than a simple column reference.
Adding AND a.attnum > 0 is not technically necessary as there is no a.attnum = 0. But it makes the query clearer and it won't hurt. The manual:
Ordinary columns are numbered from 1 up. System columns, such as oid, have (arbitrary) negative numbers.
Be aware, that there the "list of column names" can actually contain expressions, too. And since Postgres 11 there are also "included" columns (no expressions there). pg_get_indexdef() deals with all possible complications out of the box.
You can puzzle it together from the system catalogs, as Erwin Brandstetter detailed.
Here is a query that will return the information you want:
SELECT i.indexrelid::regclass AS indexname,
k.i AS index_order,
i.indnkeyatts,
coalesce(a.attname,
(('{' || pg_get_expr(
i.indexprs,
i.indrelid
)
|| '}')::text[]
)[k.i]
) AS index_column,
i.indoption[k.i - 1] = 0 AS ascending,
k.i <= i.indnkeyatts AS is_key
FROM pg_index i
CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS k(attnum, i)
LEFT JOIN pg_attribute AS a
ON i.indrelid = a.attrelid AND k.attnum = a.attnum
WHERE i.indrelid = 'schemaname.tablename'::regclass;
This query will only work from PostgreSQL v11 on (but there are no covering Indexes before v11).
Also, the query will fail if the indexed expression contains a comma; I don't know how to fix that.
Version 10.4
SELECT idx.indexrelid::regclass AS indexname,
k.i AS index_order,
--i.indnkeyatts,
coalesce(att.attname,
(('{' || pg_get_expr(
idx.indexprs,
idx.indrelid
)
|| '}')::text[]
)[k.i]
) AS index_column,
pg_index_column_has_property(idx.indexrelid,k.i::int,'asc') AS ascending,
k.i != -1 AS is_key
FROM pg_index idx
CROSS JOIN LATERAL unnest(idx.indkey) WITH ORDINALITY AS k(attnum, i)
LEFT JOIN pg_attribute AS att
ON idx.indrelid = att.attrelid AND k.attnum = att.attnum
I have a query like this:
SELECT
table1.*,
sum(table2.amount) as totalamount
FROM table1
join table2 on table1.key = table2.key
GROUP BY table1.*;
I got the error: column "table1.key" must appear in the GROUP BY clause or be used in an aggregate function.
Are there any way to group "all" field?
There is no shortcut syntax for grouping by all columns, but it's probably not necessary in the described case. If the key column is a primary key, it's enough when you use it:
GROUP BY table1.key;
You have to specify all the column names in group by that are selected and are not part of aggregate function ( SUM/COUNT etc)
select c1,c2,c4,sum(c3) FROM totalamount
group by c1,c2,c4;
A shortcut to avoid writing the columns again in group by would be to specify them as numbers.
select c1,c2,c4,sum(c3) FROM t
group by 1,2,3;
I found another way to solve, not perfect but maybe it's useful:
SELECT string_agg(column_name::character varying, ',') as columns
FROM information_schema.columns
WHERE table_schema = 'your_schema'
AND table_name = 'your_table
Then apply this select result to main query like this:
$columns = $result[0]["columns"];
SELECT
table1.*,
sum(table2.amount) as totalamount
FROM table1
join table2 on table1.key = table2.key
GROUP BY $columns;
I have written a complex JPA 2 Criteria API query (my provider is EclipseLink), where I find myself re-using the same subquery over and over again. Unless the DB (Oracle) does something clever, I think that the subquery will be executed each time it is found in the query. I am looking for a way to execute the subquery only once.
We have field-level access, which means that a user has visibility to a DB Column if certain conditions are met. In the example below, the user has the following access:
COLUMN_1 is visible if the result belongs to category 1
COLUMN_2 is visible if the result belongs to category 2
COLUMN_3 is visible if the result belongs to category 1 or category 2
This is a pseudo-query:
SELECT T.PK
FROM MY_TABLE T
WHERE
(
T.COLUMN_1 = 'A'
AND
T.PK IN (SELECT PKs of category 1)
)
AND
(
T.COLUMN_2 = 'B'
AND
T.PK IN (SELECT PKs of category 2)
)
AND
(
T.COLUMN_3 = 'C'
AND
(
T.PK IN (SELECT PKs of category 1)
OR
T.PK IN (SELECT PKs of category 2)
)
)
If I would write it by hand in SQL, I would write it by OUTER JOINing the two queries, like this:
SELECT T.PK
FROM MY_TABLE T
LEFT OUTER JOIN (SELECT PKs of category 1) IS_CAT_1 ON T.PK = IS_CAT_1.PK
LEFT OUTER JOIN (SELECT PKs of category 2) IS_CAT_2 ON T.PK = IS_CAT_2.PK
WHERE
(
T.COLUMN_1 = 'A'
AND
IS_CAT_1.RESULT = true
)
AND
(
T.COLUMN_2 = 'B'
AND
IS_CAT_2.RESULT = true
)
AND
(
T.COLUMN_3 = 'C'
AND
(
IS_CAT_1.RESULT = true
OR
IS_CAT_2.RESULT = true
)
)
Can I join a query as a table with the Criteria API? Creating a View would by my very last choice (the DB is not maintained by me).
Note: I have seen that EclipseLink provides such vendor-specific support in JPQL (link), but I haven't seen this available for Criteria.
In the end, we created a DB View, mapped it as a new JPA entity and joined it to the other entities.
I have a sequence called seque_post.
I need to find out in what table it's being used.
Is there a way to write a query that will give the table name?
I wrote this query to find the sequence:
select *
from pg_class
where relname like 'seque_post'
there is a filed there reltoastrelid which according to the manual gives:
OID of the TOAST table associated with this table, 0 if none. The TOAST table stores large attributes "out of line" in a secondary table.
but i'm not sure how to continue from here.. suggestions?
To find the table a sequence is "related" to, you can use something like this:
select seq_ns.nspname as sequence_schema,
seq.relname as sequence_name,
tab_ns.nspname as table_schema,
tab.relname as related_table
from pg_class seq
join pg_namespace seq_ns on seq.relnamespace = seq_ns.oid
JOIN pg_depend d ON d.objid = seq.oid AND d.deptype = 'a'
JOIN pg_class tab ON d.objid = seq.oid AND d.refobjid = tab.oid
JOIN pg_namespace tab_ns on tab.relnamespace = tab_ns.oid
where seq.relkind = 'S'
and seq.relname = '[your sequence name]'
and seq_ns.nspname = 'public';
Just to complete the picture:
The other way round (looking up a sequence for a column) is easier, because Postgres has a function to find the sequence for a column:
select pg_get_serial_sequence('public.some_table', 'some_column');
The key catalog here is the rather versatile pg_depend, which can connect basically any two items of any sort.
The ::regclass cast is a magic trick for converting to and from oids, which allows you to look up something like this, with no joins (but possible ambiguities):
select
D.refobjid::regclass, -- the target table name
D.* -- see docs for meaning of other columns
from
pg_depend as D
where
-- source is a relation (in this case, a sequence)
D.classid = 'pg_catalog.pg_class'::regclass
-- target is also a relation (in this case, a table)
and D.refclassid = 'pg_catalog.pg_class'::regclass
-- source is the sequence you're looking for, fully qualified name
and D.objid = 'public.seque_post'::regclass
Try this using pg_depend instead of pg_class:
SELECT d.refobjid::regclass, a.attname
FROM pg_depend d
JOIN pg_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid
WHERE d.objid = 'public."seque_post"'::regclass;
If you add the join to pg_attribute then you even have the column name that uses the sequence.
The ::regclass cast can be used to magically convert object identifiers to relation names.
Hope that helps.