I have been trying to create a trigger that will find the nulls in the DateAdded column and replacing them with the current date or with GETDATE(), and I seem to be writing a lot of lines when it seems like it would be simple, is an after update the right way to go?
USE MyGuitarShop;
GO
IF OBJECT_ID('Products_INSERT') IS NOT NULL
DROP TRIGGER Products_INSERT;
GO
CREATE TRIGGER Products_INSERT
ON Products
AFTER INSERT, UPDATE
AS
IF EXISTS (SELECT DateAdded FROM Inserted WHERE DateAdded = Null)
UPDATE Products SET DateAdded = getDate() WHERE dateAdded IS NULL
GO...................
INSERT into Products(DateAdded, ProductCode,ProductName, Description, ListPrice)
VALUES (NULL, 'halter', 'Hofner Icon', '', 699.99);...........this works but whenever I try and update the statement and I go back into the edit screen, everything is added but the new row I added is null under the dateadded, how do I get it to auto update to the date after the insert? as far as I can see this should have worked (I did change the beginning to after insert, update
Alternatively, you could make the DateAdded column NOT NULL and add a default constraint of GETDATE(). That way, whenever a row is created and DateAdded is not given a value, it will be set to the current date:
ALTER TABLE dbo.Products
ALTER COLUMN DateAdded datetime NOT NULL;
ALTER TABLE dbo.Products ADD DEFAULT (GETDATE()) FOR DateAdded;
this should work as your UPDATE statement
UPDATE Products SET DateAdded = getDate() WHERE dateAdded IS NULL
Related
Given the following (simplified) schema:
CREATE TABLE period (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
name TEXT
);
CREATE TABLE course (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
name TEXT
);
CREATE TABLE registration (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
period_id UUID NOT NULL REFERENCES period(id),
course_id UUID NOT NULL REFERENCES course(id),
inserted_at timestamptz NOT NULL DEFAULT now()
);
I now want to add a new column client_ref, which identifies a registration unique within a period, but consists of only a 4-character string. I want to use pg_hashids - which requires a unique integer input - to base the column value on.
I was thinking of setting up a trigger on the registration table that runs on inserting a new row. I came up with the following:
CREATE OR REPLACE FUNCTION set_client_ref()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
next_row_number integer;
BEGIN
WITH rank AS (
SELECT
period.id AS period_id,
row_number() OVER (PARTITION BY period.id ORDER BY registration.inserted_at)
FROM
registration
JOIN period ON registration.period_id = period.id ORDER BY
period.id,
row_number
)
SELECT
COALESCE(rank.row_number, 0) + 1 INTO next_row_number
FROM
period
LEFT JOIN rank ON (rank.period_id = period.id)
WHERE
period.id = NEW.period_id
ORDER BY
rank.row_number DESC
LIMIT 1;
NEW.client_ref = id_encode (next_row_number);
RETURN NEW;
END
$function$
;
The trigger is set-up like: CREATE TRIGGER set_client_ref BEFORE INSERT ON registration FOR EACH ROW EXECUTE FUNCTION set_client_ref();
This works as expected when inserting a single row to registration, but if I insert multiple within one statement, they end up having the same client_ref. I can reason about why this happens (the rows don't know about each other's existence, so they assume they're all just next in line when retrieving their row_order), but I am not sure what a way is to prevent this. I tried setting up the trigger as an AFTER trigger, but it resulted in the same (duplicated) behaviour.
What would be a better way to get the lowest possible, unique integer for the rows to be inserted (to base the hash function on) that also works when inserting multiple rows?
I have json data in table that I use to insert new data into final table as follows
CREATE TABLE musicbrainz.acoustid_track (
id int NOT NULL,
created timestamp with time zone DEFAULT current_timestamp,
gid uuid NOT NULL,
new_id varchar(30)
);
CREATE TABLE musicbrainz.acoustid_track_json (
data jsonb
);
......
tables loaded
......
The json column data is visible in the query and you can refer to it in the WHERE clause, e.g.:
insert into musicbrainz.acoustid_track
select id, created, gid, new_id
from musicbrainz.acoustid_track_json
cross join jsonb_populate_record(null::musicbrainz.acoustid_track, data);
and this works except acoustid_track_json can contains new records or replacement records, and this is detemrined by if they have an updated field
e.g
New record
{"id":67028798,"gid":"18575a2d-bc9c-48c0-b5d7-f815b97421ed","created":"2020-02-03T00:02:11.315629+00:00"}
Updated record
{"id":66277512,"gid":"a31e1ecc-af48-4b8f-ba65-de5187a5c9a7","new_id":65603612,"created":"2019-11-17T12:37:49.81505+00:00","updated":"2020-02-03T13:12:58.043985+00:00"}
but I cant seem to modify INSERT to refer to updated field, possibly because no updated field in the final table, how do I do this.
The json column data is visible in the query and you can refer to it in the WHERE clause, e.g.:
insert into musicbrainz.acoustid_track
select id, created, gid, new_id
from musicbrainz.acoustid_track_json
cross join jsonb_populate_record(null::musicbrainz.acoustid_track, data)
where data->'updated' is null;
Im trying to write a stored procedure in which I can upsert a row even if one of the values in the key is null. I've read the documentation and found that Postgres doesn't work with comparing equality of null values.
I've also read other forum posts and noticed that what I want to achieve can be done through a partial index. I'm able to successfully get a constraint violation, however my "on conflict" never gets hit when i pass in a value that has a null birthday.
I want to be able to pass in a null birthday and update an ID for a person even if their birthday is null.
(
id bigint not null,
name text,
birthday date
);
I create an index and partial index so that it allows birthday to be null
CREATE UNIQUE INDEX name_birthday_key on people (name, birthday);
CREATE UNIQUE INDEX birthday_null_key on people (name) where birthday is null;
create or replace procedure store_person(_identifier bigint, _name character varying, _birthday date)
language plpgsql
as
$$
begin
insert into people (
id, name, birthday
)
values (
_identifier, _name, _birthday
)
on conflict (name, birthday)
do update
set
id = _identifier
where people.birthday = _date and people.name = _name;
end
$$;
if I run:
call public.store_person(1, 'Bob', '1955-01-09')
call public.store_person(2, 'Bob', '1955-01-09')
i successfully see that the only row in the DB is Bob with an ID of 2.
however, if i run
call public.store_person(3, 'Joe', null)
call public.store_person(4, 'Joe', null)
the only row i get is ID 3. the second insert for ID 4 never updates the existing row. I do get a violation error but the "on conflict" update never is hit.
can someone point me in the right direction of how to do this?
The CONFLICT doesn't match because NULLis not equal to NULL. This is not a PostgreSQL thing, it's defined in SQL standard.
Use something like COALESCE(birthday, '0001-01-01') when inserting your data, that will match; and remove the partial index.
Your code has an error, in DO UPDATE...WHERE: there's nothing named _date, should be _birthday.
I have a demo table
CREATE TABLE items (
id SERIAL primary key,
user_id integer,
name character varying,
created timestamp with time zone default now()
);
And I want a single query to run and first insert data, then return primary key using returning id and then update the same table with the returned id.
INSERT INTO items (name) values ('pen') RETURNING id as idd
update items set user_id=(select idd) where id=(select idd)
but the above command doesn't work and throws syntax error.
Any help will be appriciated.
You can do that right within the INSERT statement:
INSERT INTO items
(name, user_id)
values
('pen', currval(pg_get_serial_sequence('items','id')));
Online example
You can try this way also :
create temp table insert_item as
with insert_item_cte as (
INSERT INTO items (name)
values ('pen') returning id
)
select id from insert_item_cte;
update items set user_id = items.id
from insert_item ii
where ii.id = items.id;
Online Demo
Say I have a table with an identity field. I want to insert a record in it if it doesn't already exist. In the below example, I check if the value stored in #Field1 already exists in the table. If not, I insert a new record:
Definition of the table:
MyTable (MyTableId int Identity not null, Field1 int not null, Field2 int not null)
This is how I check if the value already exists and insert it if necessary
merge MyTable as t
using (#Field1, #Field2) as s (Field1,Field2)
on (t.Field1=s.Field1)
when not matched then
insert (Field1,Field2) values (s.Field1,s.Field2);
Getting the identity value when the record didn't already exist in the table can be done by adding:
output Inserted.MyTableId
but what if the record was already in the table (ie if there was a match)?
The only way I found is to query the table after executing the Merge statement:
select MyTableId from MyTable where Field1=#Field1
Is there a way to get the identity value directly from the Merge?
In the case when the record already exists, you can store the matched id into a variable like this:
DECLARE #MatchedId INTEGER;
MERGE MyTable as t
....
....
WHEN MATCHED THEN
UPDATE SET #MatchedId = t.MyTableId;
UPDATE:
Here's a full example. This demonstrates one way:
DECLARE #UpdateVariable bit
DECLARE #ChangeResult TABLE (ChangeType VARCHAR(10), Id INTEGER)
DECLARE #Data TABLE (Id integer IDENTITY(1,1), Val VARCHAR(10))
INSERT #Data ([Val]) VALUES ('A');
MERGE #data AS TARGET
USING (SELECT 'A' AS Val UNION ALL SELECT 'B' AS Val) AS SOURCE ON TARGET.Val = SOURCE.Val
WHEN NOT MATCHED THEN
INSERT ([Val])
VALUES (SOURCE.Val)
WHEN MATCHED THEN
UPDATE SET #UpdateVariable = 1
OUTPUT $action, inserted.Id INTO #ChangeResult;
SELECT * FROM #data
SELECT * FROM #ChangeResult
Points to note are:
$action will give you what type of action was performed for a row (INSERT, UPDATE, DELETE)
#ChangeResult table will hold the info as to what types of changes were made
for the WHEN MATCHED case, I am basically setting a dummy variable. This doesn't serve any purpose here other than to ensure the UPDATE path gets hit to generate the UPDATE row in the output. i.e. that #UpdateVariable is not used for anything else. If you actually wanted to update the existing row, then you'd put a proper UPDATE in here, but in the case where you don't want to actually UPDATE the existing row, then this "dummy" update seems to be required.
Here is an alternative and slightly simpler approach (in my opinion):
DECLARE #Id [int];
MERGE INTO [MyTable] AS [t]
USING (VALUES
(#FieldA, #FieldB)
)
AS [x] (FieldA, FieldB)
ON [t].[FieldA] = [x].[FieldA]
AND [t].[FieldB] = [x].[FieldB]
WHEN NOT MATCHED BY TARGET THEN
INSERT (FieldA, FieldB)
VALUES (FieldA, FieldB)
WHEN MATCHED THEN
UPDATE SET #Id = [t].[Id]
IF #Id IS NULL
BEGIN
SET #Id = CAST(SCOPE_IDENTITY() as [int]);
END
SELECT #Id;
If the merge statement resulted in a match then #Id will be set to the identity of the matching row. In the event of no match, the new row will have been inserted with its new identity ready to be selected from SCOPE_IDENTITY().
Here other alternative:
DECLARE #FakeVar BIT
MERGE MyTable AS T
USING (VALUES(#Field1, #Field2)) AS S (Field1, Field2)
ON (T.Field1 = S.Field1)
WHEN NOT MATCHED THEN
INSERT (Field1, Field2)
VALUES (S.Field1, T.Field2)
WHEN MATCHED THEN
UPDATE SET #FakeVar = NULL -- do nothing, only force "an update" to ensure output on updates
OUTPUT INSERTED.MyTableId ;
If you check the OUPUT doc
INSERTED
Is a column prefix that specifies the value added by the insert or update operation.
you only need to do "something" on the update set clause
When a Merge has existing insert and an update code, the update most likely requires a primary key id. That value is generally passed to the stored procedure as a separate variable. Since that variable is null on a insert and has a value on an update, it is no issue to get the last insert with Ident_Current targeting the insert table.
Example to Merge into a Project table
Merge info.Project as Target
...
on #projectId = TARGET.ProjectId
When not matched then
... -- Insert
When matched then
... -- Update
;
if (#projectId is null)
set #projectId = IDENT_CURRENT('info.Project')