how we can Create Primary Keys in a table - primary-key

How can I create a Primary Key in a table. I used the following command to create one, but the key doesn't appear to be enforced:
ALTER TABLE tablename ADD PRIMARY KEY (column1,column3);

Snowflake supports defining and maintaining constraints, but does not enforce them, except for NOT NULL constraints, which are always enforced.
https://docs.snowflake.com/en/sql-reference/constraints-overview.html#supported-constraint-types

Although the primary key is not enforced in Snowflake, you can use the MERGE statement to insert the data to enforce uniqueness of that key. This approach requires making sure that the source data set does not include duplicates, by using for example QUALIFY function.
Example:
CREATE TABLE IF NOT EXISTS test (
col1 VARCHAR(50) NOT NULL,
col2 VARCHAR(50),
col3 VARCHAR(50) NOT NULL,
constraint PK_test primary key (col1, col3) not enforced
);
MERGE INTO TEST AS tgt
USING (
SELECT COL1, COL2, COL3 FROM (
SELECT 'a' COL1, 'b' COL2, 'a' COL3
UNION
SELECT 'a' COL1, 'c' COL2, 'a' COL3
UNION
SELECT 'a' COL1, 'd' COL2, 'a' COL3)
QUALIFY row_number() over (partition by COL1, COL3 order by COL2) = 1
)AS src
ON (tgt.COL1=src.COL1 AND tgt.COL3=src.COL3)
WHEN NOT MATCHED
THEN INSERT (COL1, COL2, COL3)
VALUES (src.COL1, src.COL2, src.COL3);
Only the row a,b,a was inserted.

Related

UPSERT based on UNIQUE constraint with NULL values

I have a Postgres table with a unique constraint on multiple columns, one of which can be NULL. I only every want to allow one record with NULL in that column for each combination.
create table my_table (
col1 int generated by default as identity primary key,
col2 int not null,
col3 real,
col4 int,
constraint ux_my_table_unique unique (col2, col3)
);
I have an upsert query that I want to update col4 when it encounters a record with the same values in col2, col3:
insert into my_table (col2, col3, col4) values (p_col2, p_col3, p_col4)
on conflict (col2, col3) do update set col4=excluded.col4;
But the conflict is not firing when col3 is NULL. I have read about using triggers. What is the best solution to get the conflict to fire please?
Postgres 15
... adds the clause NULLS NOT DISTINCT. Your case works out of the box now:
ALTER TABLE my_table
DROP CONSTRAINT IF EXISTS ux_my_table_unique
, ADD CONSTRAINT ux_my_table_unique UNIQUE NULLS NOT DISTINCT (col2, col3);
INSERT INTO my_table (col2, col3, col4)
VALUES (p_col2, p_col3, p_col4)
ON CONFLICT (col2, col3) DO UPDATE
SET col4 = EXCLUDED.col4;
See:
Create unique constraint with null columns
Postgres 14 or older
NULL values are not considered equal to each other and thus never trigger a UNIQUE violation. That means, your current table definition does not do what you say it should do. There can already be multiple rows with (col2, col3) = (1, NULL). ON CONFLICT never fires for col3 IS NULL in your current setup.
You can enforce your UNIQUE constraint with two partial UNIQUE indexes as also outlined here:
Create unique constraint with null columns
Applied to your case:
CREATE UNIQUE INDEX my_table_col2_uni_idx ON my_table (col2)
WHERE col3 IS NULL;
CREATE UNIQUE INDEX my_table_col2_col3_uni_idx ON my_table (col2, col3)
WHERE col3 IS NOT NULL;
But ON CONFLICT ... DO UPDATE can only be based on a single UNIQUE index or constraint. Only the ON CONFLICT DO NOTHING variant works as "catch-all". See:
How to use RETURNING with ON CONFLICT in PostgreSQL?
It would seem like what you want is currently impossible, but there is a ...
Perfect solution
With the two partial UNIQUE indexes in place, you can use the right statement based on the input value of col3:
WITH input(col2, col3, col4) AS (
VALUES
(3, NULL::real, 5) -- ①
, (3, 4, 5)
)
, upsert1 AS (
INSERT INTO my_table AS t(col2, col3, col4)
SELECT * FROM input WHERE col3 IS NOT NULL
ON CONFLICT (col2, col3) WHERE col3 IS NOT NULL -- matching index_predicate!
DO UPDATE
SET col4 = EXCLUDED.col4
WHERE t.col4 IS DISTINCT FROM EXCLUDED.col4 -- ②
)
INSERT INTO my_table AS t(col2, col3, col4)
SELECT * FROM input WHERE col3 IS NULL
ON CONFLICT (col2) WHERE col3 IS NULL -- matching index_predicate!
DO UPDATE SET col4 = EXCLUDED.col4
WHERE t.col4 IS DISTINCT FROM EXCLUDED.col4; -- ②
db<>fiddle here
Works in every case.
Even works for multiple input rows with an arbitrary mix of NULL and NOT NULL values for col3.
And doesn't even cost much more than the plain statement because each row only enters into one of the two UPSERTs.
This is one of those "Eurika!" queries where everything just clicks, against all odds. :)
① Note the explicit cast to ::real in the CTE input. This related answer explains why:
Casting NULL type when updating multiple rows
② The final WHERE clause is optional, but highly recommended. It would be a waste to go through with the UPDATE if it doesn't actually change anything. See:
How do I (or can I) SELECT DISTINCT on multiple columns?
If you can find a value that can never legally exist in col3 (make sure with a check constraint), you could use a unique index:
CREATE UNIQUE INDEX ON my_table (
col2,
coalesce(col3, -1.0)
);
and use that in your INSERT:
INSERT INTO my_table (col2, col3, col4)
VALUES (p_col2, p_col3, p_col4)
ON CONFLICT (col2, coalesce(col3, -1.0))
DO UPDATE SET col4 = excluded.col4;

How to select more columns than insert column

I have the following query:
INSERT INTO TableA (Col1, Col2, Col3)
OUTPUT #SomeData, INSERTED.ID, ID INTO TableB(SomeColumn, TableAID, ID)
SELECT Col1, Col2, Col3, ID
FROM TableC;
When I run it, I get this error:
The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns.
That error makes sense, but I don't know how to fix it. I want to select 4 columns from TableC, but I want to only insert three of them (Col1, Col2, Col3) into TableA. I am selecting the column ID because I want to insert it into the ID column of TableB. Is there any way to do that?
CREATE TABLE TableA
(
ID bigint identity
constraint PK_TableA_ID
primary key
Col1 int,
Col2 int,
Col3 int
)
CREATE TABLE TableB
(
ID [int] NOT NULL,
SomeColumn [int] NOT NULL,
TableAID [bigint] NOT NULL
);
CREATE TABLE TableC
(
ID [int] IDENTITY (1,1) NOT NULL,
Col1 [int],
Col2 [int],
Col3 [int],
);
You can try merge and write something like this:
MERGE TableA AS target
USING TableC AS source
ON 1 = 0
WHEN NOT MATCHED BY target
THEN INSERT (Col1, Col2, Col3) VALUES (source.Col1, source.Col2, source.Col3)
OUTPUT #SomeData, INSERTED.ID, source.ID INTO TableB (SomeColumn,TableAID, ID);

Composite Key join in SQL Error

I an trying to join two tables with two keys. The join code is like:
select
col1, col2, col3, col4
from
data a
join
data1 b on a.col1 = b.col1 and a.col3 = b.col3
I am getting error:
Query Error: Error: ER_NON_UNIQ_ERROR: Column 'col1' in field list is ambiguous
CREATE TABLE data
(
Id Serial,
col1 VARCHAR(70) NOT NULL,
col3 varchar(70),
col2 integer,
PRIMARY KEY(Id)
);
CREATE TABLE data1
(
Id Serial,
col1 VARCHAR(70) NOT NULL,
col3 varchar(70),
col4 integer,
PRIMARY KEY(Id)
);
SQL fiddle with sample data
It's ambiguous because postgres wants to know which table you want col1 from.
Add an alias to the fields in the select clause. E.g. select a.col1...

Copy parent and child rows into same table?

I have a situation similar to this SO question where I want to copy parent rows and their child rows from and into the same table.
The suggested answer using OUTPUT and MERGE is clean but only considers just one record.
Is there a way to modify it to handle multiple parent rows?
EDIT
I tried to modify the SQL statement and came up with this:
DECLARE #fromId int, #toId int;
SET #fromId = 1;
SET #toId = 2;
DECLARE #mapping TABLE (old_id int, new_id int);
INSERT INTO parent_table (col1, col2, col3)
SELECT #toId, col2, col3
FROM parent_table
WHERE col1 = #fromId;
MERGE child_table tgt
USING (
SELECT t.parent_id, t.col2, t.col3
FROM child_table t
inner join parent_table p on p.id = t.parent_id
WHERE p.col1 = #toId
) src
ON 0 = 1
WHEN NOT MATCHED THEN
INSERT (parent_id, col2, col3) VALUES (src.parent_id, src.col2, src.col3)
OUTPUT src.parent_id, INSERTED.parent_id INTO #mapping (old_id, new_id);
The first INSERT of the parent rows works. However, the second insert does insert any child rows. What am I missing?
You could make use of a UNION operator. We just have to focus on maintaining the column numbers and data types :
select <required-column names, add null if value for a column not required> from parent_table
UNION
select <required-column with null if value for that column not required> from child_table where id in(select id from parent_table);
For e.g. suppose destination has five columns, parent has three columns, child has two columns.
Then, depending on where parent ids should go, we could write :
insert into destination(col1,col2, col3, col4, col5)
select col1, null, col2, col3, null from parent_table where key in(<key values>)
UNION
select col1, col2, null, null,null from child_table where key in(<key values>);
If all records are to be copied :
insert into destination(col1,col2, col3, col4, col5)
select col1, null, col2, col3, null from parent_table
UNION
select col1, col2, null, null,null from child_table where key_column in(select key_column from parent_table);

SQL Check constraint on column referencing other columns

I want to limit a column that it can only have a value when another column has a value.
example: (this doesn't work)
create table testConstraint (
col1 int not null identity(1, 1) primary key,
col2 int,
col3 int check (col2 is not null),
col4 int)
This is not possible because he cannot reference another column.
Error:
Column CHECK constraint for column 'col3' references another column,
table 'testConstraint'.
Another try was: (also doesn't work)
create table testConstraint (
col1 int not null identity(1, 1) primary key,
col2 int,
col3 int,
col4 int)
GO
alter table testConstraint add constraint ck_columnNotNull check (case when col2 is null then col3 is null end)
GO
Anyone have an idea how this would be possible with a constraint?
You can write a trigger.
Also, you can try this
(1)
ALTER TABLE TestConstraint ADD CONSTRAINT
CK_TestConstraint CHECK (NOT ( (col3 is not null) and (col2 is null) ))
GO
or this
(2)
ALTER TABLE TestConstraint ADD CONSTRAINT
CK_TestConstraint CHECK
(
((col3 is not null) and (col2 is not null)) or
((col3 is null) and (col2 is null))
)
GO
depending on what exactly you need.
I just tested it and it works OK, I think.
insert into
TestConstraint
(col2, col3, col4)
values
(null, 1, 2)
-- ERROR
insert into
TestConstraint
(col2, col3, col4)
values
(1, 1, 2)
-- OK
ALTER TABLE testConstraint
ADD CONSTRAINT ck_columnNotNull
CHECK ( 1 = CASE
WHEN col2 IS NULL AND col3 IS NULL THEN 1
WHEN col2 IS NOT NULL AND col3 IS NOT NULL THEN 1
ELSE 0
END)
Only simple logic is required, plus it needs (as per your second attempt) to be a table check constraint, so you can't declare it inline with the declaration of col3:
create table testConstraint (
col1 int not null identity(1, 1) primary key,
col2 int,
col3 int,
col4 int)
GO
alter table testConstraint add constraint ck_columnNotNull check (
col3 is null
or col2 is not null
)
GO
If col3 is null, then we don't care what the value of col2 is. Conversely, if it's not NULL, then we do want to enforce the col2 isn't null. That's what the two sides of the or effectively give us.