What is the difference between referencing a column using "()" and "."? - sql

I was trying to create a join table using the following statement:
CREATE TABLE directors_and_films(
id serial PRIMARY KEY,
directors_id int REFERENCES directors.id
films_id int REFERENCES films.id,
);
This causes Postgres to respond with:
ERROR: schema "films" does not exist
When I change it to:
CREATE TABLE directors_films (
id serial PRIMARY KEY,
director_id integer REFERENCES directors (id),
film_id integer REFERENCES films (id)
);
The code executes fine.
My question is what is the difference between accessing a column using () as opposed to a period? What are the differences between these two in SQL generally?

Postgres does indeed support functional notation and attribute notation for column references. So this works for a table tbl with a column col:
SELECT col(tbl) FROM tbl;
The manual:
but this usage is deprecated since it's easy to get confused
See:
Store common query as column?
But that has no bearing on the case at hand. The short syntax for FK constraints in a CREATE TABLE statement requires parentheses around the referenced column. (The column constraint like in your example, can only reference a single column, obviously.) Attribute notation like you tried (directors.id) is a syntax error in this spot.
That's all there is to this. The manual:
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]

Related

How to reference a SQL table if primary key has more than one column?

I am learning postgresql and I have created 2 tables: goals and results. Each table has a primary key which is formed by 3 columns:
id
valid_date_from
vald_until_from
I did this so that each row must be unique not only by its id but also depending on when the goal or result is still valid. Here is my sql code:
CREATE TABLE goals (
goal_id INT,
goal_title VARCHAR(80),
goal_description VARCHAR(300),
goal_valid_from_date TIMESTAMP,
goal_valid_until_date TIMESTAMP,
goal_deleted_flag BOOLEAN,
PRIMARY KEY (goal_id, goal_valid_from_date, goal_valid_until_date)
);
CREATE TABLE results (
result_id INT,
goal_id INT,
result_description VARCHAR(300),
result_target FLOAT,
result_timestamp DATE,
result_valid_from_date TIMESTAMP,
result_valid_until_date TIMESTAMP,
result_deleted_flag BOOLEAN,
PRIMARY KEY (result_id, result_valid_from_date, result_valid_until_date)
);
My goal now is to create a foreign key so that I am able to connect the 2 tables based on the goal_id column only and not the valid_from/until_date columns otherwise they would never match.
I tried to achieve this by using the following lines of code:
ALTER TABLE results
ADD FOREIGN KEY(goal_id)
REFERENCES goals(goal_id) on delete set null;
However I get an error:
SQL Error [42830]: ERROR: there is no unique constraint matching given
keys for referenced table "goal"
Would you be able to propose a smart and elegant way to achieve my goal?
it is very clear in the documentation
FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
you need to put the list of column_name
... so that I am able to connect the 2 tables based on the goal_id column only ...
As the error message already suggests, you need to add a matching UNIQUE CONSTRAINT:
ALTER TABLE goals
ADD CONSTRAINT u_goals_goal_id
UNIQUE (goal_id)
However, this constraint is more restrictive than your actual primary key, which might not be what you want, if your current design is on purpose and not by accident (see the comments of #MikeOrganek and #ErwinBrandstetter).
If it is what you want then you should consider making this your primary key instead.
For your exclusion problem (no two goals may have overlapping time periods) take a look at the example in the manual, it literally describes your case.

Postgres BIGSERIAL does not share sequence when inserts are made with multiple remote fdw sources

I am trying to insert into a table in a Postgres database from two other Postgres databases using Foreign Data Wrappers. The objective is to have an autogenerate primary key, independent of the source, as there will be more than two in.
I first defined the tables like so:
Target database:
create table dummy (
dummy_pk bigserial primary key
-- other fields
);
Sources databases:
create foreign table dummy (
dummy_pk bigserial
-- other fields
) server ... ;
This solution worked fine as long as I inserted from only one source, when I tried to insert from the other one, without specifying dummy_pk, I got this message:
Duplicate key (dummy_pk)=(1)
Because postgres tries to insert an id of 1, I believe the sequence used for each source foreign table is different. I changed the source tables a bit in an attempt to let the target table's sequence do the job for the id:
create foreign table dummy (
dummy_pk bigint
-- other fields
) server ... ;
This time I got a diffrent error:
NULL value violates NOT NULL constaint on column « dummy_pk »
Therefore I believe the source server sends a query to the target where the dummy_pk is null, and the target does not replace it with the default value.
So, is there a way I can force the use of the target's sequence in a query executed on the source? Maybe I have to share that sequence, can I create a foreign sequence? I cannot remove the column on the foreign tables as I need a read access to them.
Thanks!
Remove dummy_pk from foreign tables so that destination table does not get NULL nor value and so fall backs to DEFAULT or NULL if no DEFAULT specified. If you attempt to pass DEFAULT to foreign table it will try to use DEFAULT value of foreign table instead.
create foreign table dummy (
/*dummy_pk bigserial,*/
column1 text,
column2 int2,
-- other fields
) server ... ;
Another way would be to grab sequence values from destination server using dblink, but I think this is better (if you can afford to have this column removed from foreign tables).

Postgres create table error

I am trying to create my very first table in postgres, but when I execute this SQL:
create table public.automated_group_msg (
automated_group_msg_idx integer NOT NULL DEFAULT nextval ('automated_group_msg_idx'::regclass),
group_idx integer NOT NULL,
template_idx integer NOT NULL,
CONSTRAINT automated_group_msg_pkey PRIMARY KEY (automated_group_msg_idx),
CONSTRAINT automated_group_msg_group_idx_fkey FOREIGN KEY (group_idx)
REFERENCES public.groups (group_idx) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT automated_msg_template_idx_fkey FOREIGN KEY (template_idx)
REFERENCES public.template (template_idx) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
WITH (
OIDS = FALSE
);
I get the following error:
ERROR: relation "automated_group_msg_idx" does not exist
Your error is (likely) because the sequence you're trying to use doesn't exist yet.
But you can create a sequence on the fly using this syntax:
create table public.automated_group_msg (
id serial primary key,
... -- other columns
)
Not directly related to your question, but naming columns with the table name in the name of the column is generally speaking an anti-pattern, especially for primary keys for which id is the industry standard. It also allows for app code refactoring using abstract classes whose id column is always id. It's crystal clear what automated_group_msg.id means and also crystal clear that automated_group_msg.automated_group_msg_id is a train wreck and contains redundant information. Attribute column names like customer.birth_date should also not be over-decorated as customer.customer_birth_date for the same reasons.
You just need to create the sequence before creating the table
CREATE SEQUENCE automated_group_msg_idx;

Informix to SQL attribute constraint

I have a constraint as follows in my script & I learnt that it's Informix:
create table blaBla
{
var_name_1 not null constraint n255_153,
var_name_1 not null constraint n655_699,
}
I can't find an equivalent to this in SQL. I tried just typing it the same but it doesn't work. What's the equivalent in other DBMS?
You have a number of problems with your SQL, which reads:
create table blaBla
{
var_name_1 not null constraint n255_153,
var_name_1 not null constraint n655_699,
}
Informix uses { and } to enclose comments (also -- to end of line, and /* … */). Both standard SQL and Informix require ( and ) around the body of the CREATE TABLE statement.
All SQL DBMS insist on a type in the column definition, and require different columns in the same table to have distinct names. (The constraint names in a table must be unique across all tables in the database, too.) The comma is a separator, not a terminator, so the final comma must be omitted, too.
Informix also insists on the constraint name after the constraint, but the standard and other SQL DBMS put the constraint name before the constraint. Thus, elsewhere, you'd consider using:
CREATE TABLE blaBla
(
var_name_1 INTEGER CONSTRAINT n255_153 NOT NULL,
var_name_2 CHAR(20) CONSTRAINT n655_699 NOT NULL
)
Speaking frankly, though, most people would not name a NOT NULL constraint at all and would simply write:
CREATE TABLE blaBla
(
var_name_1 INTEGER NOT NULL,
var_name_2 CHAR(20) NOT NULL
)
The system then generates an automatic constraint name similar to the ones you showed. The nXXX_YYY notation means that the table ID (tabid number in the systables system catalog table) was XXX when the constraint was created, and for that table, the constraint was number YYY. Either you have composite notation from several tables, or the table was modified (altered) and the new table number was 655 when the old was 255. That is, however, a quirk of the naming scheme used by Informix and totally unlegislated by standard SQL.

Correct way to create a table that references variables from another table

I have these relationships:
User(uid:integer,uname:varchar), key is uid
Recipe(rid:integer,content:text), key is rid
Rating(rid:integer, uid:integer, rating:integer) , key is (uid,rid).
I built the table in the following way:
CREATE TABLE User(
uid INTEGER PRIMARY KEY ,
uname VARCHAR NOT NULL
);
CREATE TABLE Recipes(
rid INTEGER PRIMARY KEY,
content VARCHAR NOT NULL
);
Now for the Rating table: I want it to be impossible to insert a uid\rid that does not exist in User\Recipe.
My question is: which of the following is the correct way to do it? Or please suggest the correct way if none of them are correct. Moreover, I would really appreciate if someone could explain to me what is the difference between the two.
First:
CREATE TABLE Rating(
rid INTEGER,
uid INTEGER,
rating INTEGER CHECK (0<=rating and rating<=5) NOT NULL,
PRIMARY KEY(rid,uid),
FOREIGN KEY (rid) REFERENCES Recipes,
FOREIGN KEY (uid) REFERENCES User
);
Second:
CREATE TABLE Rating(
rid INTEGER REFERENCES Recipes,
uid INTEGER REFERENCES User,
rating INTEGER CHECK (0<=rating and rating<=5) NOT NULL,
PRIMARY KEY(rid,uid)
);
EDIT:
I think User is problematic as a name for a table so ignore the name.
Technically both versions are the same in Postgres. The docs for CREATE TABLE say so quite clearly:
There are two ways to define constraints: table constraints and column constraints. A column constraint is defined as part of a column definition. A table constraint definition is not tied to a particular column, and it can encompass more than one column. Every column constraint can also be written as a table constraint; a column constraint is only a notational convenience for use when the constraint only affects one column.
So when you have to reference a compound key a table constraint is the only way to go.
But for every other case I prefer the shortest and most concise form where I don't need to give names to stuff I'm not really interested in. So my version would be like this:
CREATE TABLE usr(
uid SERIAL PRIMARY KEY ,
uname TEXT NOT NULL
);
CREATE TABLE recipes(
rid SERIAL PRIMARY KEY,
content TEXT NOT NULL
);
CREATE TABLE rating(
rid INTEGER REFERENCES recipes,
uid INTEGER REFERENCES usr,
rating INTEGER NOT NULL CHECK (rating between 0 and 5),
PRIMARY KEY(rid,uid)
);
This is a SQL Server based solution, but the concept applies to most any RDBMS.
Like so:
CREATE TABLE Rating (
rid int NOT NULL,
uid int NOT NULL,
CONSTRAINT PK_Rating PRIMARY KEY (rid, uid)
);
ALTER TABLE Rating ADD CONSTRAINT FK_Rating_Recipies FOREIGN KEY(rid)
REFERENCES Recipies (rid);
ALTER TABLE Rating ADD CONSTRAINT FK_Rating_User FOREIGN KEY(uid)
REFERENCES User (uid);
This ensures that the values inside of Rating are only valid values inside of both the Users table and the Recipes table. Please note, in the Rating table I didn't include the other fields you had, just add those.
Assume in the users table you have 3 users: Joe, Bob and Bill respective ID's 1,2,3. And in the recipes table you had cookies, chicken pot pie, and pumpkin pie respective ID's are 1,2,3. Then inserting into Rating table will only allow for these values, the minute you enter 4 for a RID or a UID SQL throws an error and does not commit the transaction.
Try it yourself, its a good learning experience.
In Postgresql a correct way to implement these tables are:
CREATE SEQUENCE uid_seq;
CREATE SEQUENCE rid_seq;
CREATE TABLE User(
uid INTEGER PRIMARY KEY DEFAULT nextval('uid_seq'),
uname VARCHAR NOT NULL
);
CREATE TABLE Recipes(
rid INTEGER PRIMARY KEY DEFAULT nextval('rid_seq'),
content VARCHAR NOT NULL
);
CREATE TABLE Rating(
rid INTEGER NOT NULL REFERENCES Recipes(rid),
uid INTEGER NOT NULL REFERENCES User(uid),
rating INTEGER CHECK (0<=rating and rating<=5) NOT NULL,
PRIMARY KEY(rid,uid)
);
There is no real difference between the two options that you have written.
A simple (i.e. single-column) foreign key may be declared in-line with the column declaration or not. It's merely a question of style. A third way should be to omit foreign key declarations from the CREATE TABLE entirely and later add them using ALTER TABLE statements; done in a transaction (presumable along with all the other tables, constraints, etc) the table would never exist without its required constraints. Choose whichever you think is easiest fora human coder to read and understand i.e. is easiest to maintain.
EDIT: I overlooked the REFERENCES clause in the second version when I wrote my original answer. The two versions are identical in terms of referential integrity, there are just two ways of syntax to do this.