I want to add indexes to some of the columns in a table on creation. Is there are way to add them to the CREATE TABLE definition or do I have to add them afterward with another query?
CREATE INDEX reply_user_id ON reply USING btree (user_id);
There doesn't seem to be any way of specifying an index in the CREATE TABLE syntax. PostgreSQL does however create an index for unique constraints and primary keys by default, as described in this note:
PostgreSQL automatically creates an index for each unique constraint and primary key constraint to enforce uniqueness.
Other than that, if you want a non-unique index, you will need to create it yourself in a separate CREATE INDEX query.
No.
However, you can create unique indexes in the create, but that's because they are classed as constraints. You can't create a "general" index.
Peter Krauss is looking for a canonical answer:
There are a MODERN SYNTAX (year 2020), so please explain and show examples, compatible with postgresql.org/docs/current/sql-createtable.html
You are searching for inline index definition, which is not available for PostgreSQL up to current version 12. Except UNIQUE/PRIMARY KEY constraint, that creates underlying index for you.
CREATE TABLE
[ CONSTRAINT constraint_name ]
{ CHECK ( expression ) [ NO INHERIT ] |
UNIQUE ( column_name [, ... ] ) index_parameters |
PRIMARY KEY ( column_name [, ... ] ) index_parameters |
The sample syntax of inline column definition(here SQL Server):
CREATE TABLE tab(
id INT PRIMARY KEY, -- constraint
c INT INDEX filtered (c) WHERE c > 10, -- filtered index
b VARCHAR(10) NOT NULL INDEX idx_tab_b, -- index on column
d VARCHAR(20) NOT NULL,
INDEX my_index NONCLUSTERED(d) -- index on column as separate entry
);
db<>fiddle demo
The rationale behind introducing them is quite interesting What are Inline Indexes? by Phil Factor
Related
We have a UNIQUE constraint on a table to prevent our city_name and state_id combinations from being duplicated. The problem we have found is that accents circumvent this.
Example:
"Montréal" "Quebec"
and
"Montreal" "Quebec"
We need a way to have the unique constraint run UNACCENT() and preferably wrap it in LOWER() as well for good measure. Is this possible?
You can create an immutable version of unaccent:
CREATE FUNCTION noaccent(text) RETURNS text
LANGUAGE sql IMMUTABLE STRICT AS
'SELECT unaccent(lower($1))';
and use that in a unique index on the column.
An alternative is to use a BEGORE INSERT OR UPDATE trigger that fills a new column with the unaccented value and put a unique constraint on that column.
You can create unique indexes on expressions, see the Postgres manual:
https://www.postgresql.org/docs/9.3/indexes-expressional.html
So in your case it could be something like
CREATE UNIQUE INDEX idx_foo ON my_table ( UNACCENT(LOWER(city_name)), state_id )
HeidiSQL generated the following creation code:
CREATE TABLE "books" (
"id" BIGINT NOT NULL,
"creation_date" TIMESTAMP NOT NULL,
"symbol" VARCHAR NOT NULL,
PRIMARY KEY ("id"),
UNIQUE INDEX "symbol" ("symbol")
)
;
COMMENT ON COLUMN "books"."id" IS E'';
COMMENT ON COLUMN "books"."creation_date" IS E'';
COMMENT ON COLUMN "books"."symbol" IS E'';
And when I try to submit, I get the following error:
Is it an HeidiSQL bug with PostgreSQL?
There is a recommendation note for how you should create an UNIQUE INDEX in PostgreSQL:
The preferred way to add a unique constraint to a table is ALTER TABLE ... ADD CONSTRAINT. The use of indexes to enforce unique constraints could be considered an implementation detail that should not be accessed directly. One should, however, be aware that there's no need to manually create indexes on unique columns; doing so would just duplicate the automatically-created index.
There are a couple of ways of creating a UNIQUE INDEX on PostgreSQL.
The first way is, as mentioned above, using ALTER TABLE on a previously created table:
ALTER TABLE books ADD UNIQUE ("symbol");
OR
ALTER TABLE books ADD CONSTRAINT UQ_SYMBOL UNIQUE ("symbol")
Note: this approach uses the auto-generation of indexes of PostgreSQL (that is, PostgreSQL will identify that that column is unique and add an index to it).
The second way is using CREATE INDEX:
CREATE UNIQUE INDEX "symbol" ON books("symbol");
Last but not least, you can simply omit, in the table creation, the INDEX keyword and let PostgreSQL do the magic (create the index) for you:
CREATE TABLE "books" (
"id" BIGINT NOT NULL,
"creation_date" TIMESTAMP NOT NULL,
"symbol" VARCHAR NOT NULL,
PRIMARY KEY ("id"),
UNIQUE ("symbol")
);
Note: The syntax UNIQUE "symbol"("symbol") could be a confusion made with method 2, as in that method one it is required to specify both the table and the column name (books("symbol")).
Therefore, that is not a PostgreSQL bug, but rather a HeidiSQL one.
Edit: I was able to reproduce the bug and opened an issue on github.
I'm using postgres 9.5.3, and I have a table like this:
CREATE TABLE packages (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL
);
I have defined a function, canonical_name, like this:
CREATE FUNCTION canonical_name(text) RETURNS text AS $$
SELECT replace(lower($1), '-', '_')
$$ LANGUAGE SQL;
I've added a unique index to this table that uses the function:
CREATE UNIQUE INDEX index_package_name
ON packages (canonical_name(name));
CREATE INDEX
# \d+ packages
Table "public.packages"
Column | Type | Modifiers | Storage | Stats target | Description
--------+-------------------+-------------------------------------------------------+----------+--------------+-------------
id | integer | not null default nextval('packages_id_seq'::regclass) | plain | |
name | character varying | not null | extended | |
Indexes:
"packages_pkey" PRIMARY KEY, btree (id)
"index_package_name" UNIQUE, btree (canonical_name(name::text))
And this unique index is working as I expect; it prevents insertion of duplicates:
INSERT INTO packages (name)
VALUES ('Foo-bar');
INSERT INTO packages (name)
VALUES ('foo_bar');
ERROR: duplicate key value violates unique constraint "index_package_name"
DETAIL: Key (canonical_name(name::text))=(foo_bar) already exists.
My problem is that I want to use this unique index to do an upsert, and I can't figure out how I need to specify the conflict target. The documentation seems to say I can specify an index expression:
where conflict_target can be one of:
( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
ON CONSTRAINT constraint_name
But all of these things below that I've tried produce errors as shown, instead of a working upsert.
I've tried matching the index expression as I specified it:
INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (canonical_name(name))
DO UPDATE SET name = EXCLUDED.name;
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
Matching the index expression as \d+ showed it:
INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (canonical_name(name::text))
DO UPDATE SET name = EXCLUDED.name;
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
Just naming the column that the unique index is on:
INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (name)
DO UPDATE SET name = EXCLUDED.name;
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
Using the index name instead:
INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (index_package_name)
DO UPDATE SET name = EXCLUDED.name;
ERROR: column "index_package_name" does not exist
LINE 3: ON CONFLICT (index_package_name)
So how do I specify that I want to use this index? Or is this a bug?
Important note: This behavior can only be observed on versions before 9.5.4. This is a bug that was fixed in 9.5.4. The rest of the answer describes the buggy behavior:
As you found out, you can only specify the expression for a unique constraint and not the one for a unique index.
This is somewhat confusing because under the hood a unique constraint is just a unique index (but that is considered an implementation detail).
To make matters worse for you, you cannot define a unique constraint over a unique index that contains expressions – I am not certain what the reason is, but suspect the SQL standard.
One way you can do this would be to add an artificial column, filled with the “canonical name” by a trigger and define the constraint on that column. I admit that that is not nice.
The correct solution, however, is to upgrade to the latest minor release for PostgreSQL 9.5.
I have a billing_infos table that has a column called order_id. It already has an index.
\d billing_infos
...
Indexes:
"billing_infos_pkey" PRIMARY KEY, btree (id)
"billing_infos_address_id_idx" btree (address_id)
"index_billing_infos_on_order_id" btree (order_id) //<--this one
However, I am not sure if this is a unique index. I have the task of making a unique index, but I'm not sure if I should change the one that's already there or create a new one. The order_id values should all be unique.
Should I create a new index or alter the existing one?
And how do I check to see if the existing indexes are unique?
It is probably least invasive to create a unique index concurrently. Note that using a CONSTRAINT is the recommended way to enforce uniqueness. A UNIQUE index is more useful if the columns being checked require a function to create the uniqueness. An example of the latter is using COALESCE() to prevent NULLs from bypassing the UNIQUE check.
Eg.
create unique index foo_col1_col2_uidx on foo (col1, coalesce(col2,-1));
In the example above, col2 is an integer column and is not defined as NOT NULL.
Example of creating unique index concurrently.
create unique index concurrently billing_infos_order_id_uidx on billing_infos (order_id);
The output in psql from \d for a UNIQUE index (I've named mine _uidx) and UNIQUE CONSTRAINT (_uc) looks like the following:
\d foo
Table "public.foo"
Column | Type | Modifiers
--------+-----------------------------+-----------
x | integer |
tstamp | timestamp without time zone |
col | text |
Indexes:
"foo_col_uidx" UNIQUE, btree (col) <<< unique index
"foo_tstamp_uc" UNIQUE CONSTRAINT, btree (tstamp) <<< unique constraint
"foo_idx" btree (x)
That is not a unique index.
Try creating a simple table which has a primary key, a column with a unique constraint, and a column with a normal index:
create table example (id integer primary key, alpha integer, beta integer, gamma integer);
alter table example add constraint alpha_unique unique (alpha);
create index beta_normal on example (beta);
create unique index gamma_unique on example (gamma);
If you use \d on it, the output is:
Table "public.example"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null
alpha | integer |
beta | integer |
gamma | integer |
Indexes:
"example_pkey" PRIMARY KEY, btree (id)
"alpha_unique" UNIQUE CONSTRAINT, btree (alpha)
"gamma_unique" UNIQUE, btree (gamma)
"beta_normal" btree (beta)
As you can see, when an index is unique, it says so. You can even see when the index implements a unique constraint.
So, what should you do? Firstly, don't add a unique index. Don't ever do that. If you want to impose a uniqueness constraint on a column, you do that by adding a unique constraint. A constraint, not an index. The clue is in the name.
Adding a unique index may well work, but it is the wrong thing to do as the PostgreSQL manual says:
The preferred way to add a unique constraint to a table is ALTER TABLE
... ADD CONSTRAINT. The use of indexes to enforce unique constraints
could be considered an implementation detail that should not be
accessed directly.
So, simply use the alter table ... add constraint syntax i use above to add the constraint.
Use PgAdmin tool to see the index structure.
When creating a table in PostgreSQL, default constraint names will assigned if not provided:
CREATE TABLE example (
a integer,
b integer,
UNIQUE (a, b)
);
But using ALTER TABLE to add a constraint it seems a name is mandatory:
ALTER TABLE example ADD CONSTRAINT my_explicit_constraint_name UNIQUE (a, b);
This has caused some naming inconsistencies on projects I've worked on, and prompts the following questions:
Is there a simple way to add a constraint to an extant table with the name it would have received if added during table creation?
If not, should default names be avoided altogether to prevent inconsistencies?
The standard names for indexes in PostgreSQL are:
{tablename}_{columnname(s)}_{suffix}
where the suffix is one of the following:
pkey for a Primary Key constraint
key for a Unique constraint
excl for an Exclusion constraint
idx for any other kind of index
fkey for a Foreign key
check for a Check constraint
Standard suffix for sequences is
seq for all sequences
Proof of your UNIQUE-constraint:
NOTICE: CREATE TABLE / UNIQUE will
create implicit index
"example_a_b_key" for table "example"
The manual is pretty clear about this ("tableconstraint: This form adds a new constraint to a table using the same syntax as CREATE TABLE.")
So you can simply run:
ALTER TABLE example ADD UNIQUE (a, b);