How do I automatically update a timestamp in PostgreSQL - database

I want the code to be able to automatically update the time stamp when a new row is inserted as I can do in MySQL using CURRENT_TIMESTAMP.
How will I be able to achieve this in PostgreSQL?
CREATE TABLE users (
id serial not null,
firstname varchar(100),
middlename varchar(100),
lastname varchar(100),
email varchar(200),
timestamp timestamp
)

To populate the column during insert, use a DEFAULT value:
CREATE TABLE users (
id serial not null,
firstname varchar(100),
middlename varchar(100),
lastname varchar(100),
email varchar(200),
timestamp timestamp default current_timestamp
)
Note that the value for that column can explicitly be overwritten by supplying a value in the INSERT statement. If you want to prevent that you do need a trigger.
You also need a trigger if you need to update that column whenever the row is updated (as mentioned by E.J. Brennan)
Note that using reserved words for column names is usually not a good idea. You should find a different name than timestamp

You'll need to write an insert trigger, and possible an update trigger if you want it to change when the record is changed. This article explains it quite nicely:
http://www.revsys.com/blog/2006/aug/04/automatically-updating-a-timestamp-column-in-postgresql/
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified = now();
RETURN NEW;
END;
$$ language 'plpgsql';
Apply the trigger like this:
CREATE TRIGGER update_customer_modtime BEFORE UPDATE ON customer FOR EACH ROW EXECUTE PROCEDURE update_modified_column();

Updating timestamp, only if the values changed
Based on E.J's link and add a if statement from this link (https://stackoverflow.com/a/3084254/1526023)
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN
NEW.modified = now();
RETURN NEW;
ELSE
RETURN OLD;
END IF;
END;
$$ language 'plpgsql';

Using 'now()' as default value automatically generates time-stamp.

To automatically update the timestamp field in PostgresSQL whenever a new row is inserted, you can set the current_timestamp as its default value:
CREATE TABLE users (
id serial not null,
firstname varchar(100),
middlename varchar(100),
lastname varchar(100),
email varchar(200),
timestamp timestamp default current_timestamp
)
In addition to this, you might want to prevent anyone from updating this field in the future, and this can be done by creating an update trigger and applying it:
CREATE OR REPLACE FUNCTION stop_change_on_timestamp()
RETURNS trigger AS
$BODY$
BEGIN
-- always reset the timestamp to the old value ("actual creation time")
NEW.timestamp := OLD.timestamp;
RETURN NEW;
END;
$BODY$
CREATE TRIGGER prevent_timestamp_changes
BEFORE UPDATE
ON users
FOR EACH ROW
EXECUTE PROCEDURE stop_change_on_timestamp();

For update and Liquibase YAML:
databaseChangeLog:
- changeSet:
id: CREATE FUNCTION set_now_to_timestamp
author: Konstantin Chvilyov
changes:
- sql:
sql: CREATE OR REPLACE FUNCTION set_now_to_timestamp() RETURNS TRIGGER LANGUAGE plpgsql AS 'BEGIN NEW.timestamp = NOW(); RETURN NEW; END;'
- changeSet:
id: CREATE TRIGGER update_users_set_now_to_timestamp
author: Konstantin Chvilyov
changes:
- sql:
sql: CREATE TRIGGER update_users_set_now_to_timestamp BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE set_now_to_timestamp();

Related

Can I determine when a Azure SQL DB row was last updated? [duplicate]

I need to create a new DATETIME column in SQL Server that will always contain the date of when the record was created, and then it needs to automatically update whenever the record is modified. I've heard people say I need a trigger, which is fine, but I don't know how to write it. Could somebody help with the syntax for a trigger to accomplish this?
In MySQL terms, it should do exactly the same as this MySQL statement:
ADD `modstamp` timestamp NULL
DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
Here are a few requirements:
I can't alter my UPDATE statements to set the field when the row is modified, because I don't control the application logic that writes to the records.
Ideally, I would not need to know the names of any other columns in the table (such as the primary key)
It should be short and efficient, because it will happen very often.
SQL Server doesn't have a way to define a default value for UPDATE.
So you need to add a column with default value for inserting:
ADD modstamp DATETIME2 NULL DEFAULT GETDATE()
And add a trigger on that table:
CREATE TRIGGER tgr_modstamp
ON **TABLENAME**
AFTER UPDATE AS
UPDATE **TABLENAME**
SET ModStamp = GETDATE()
WHERE **ID** IN (SELECT DISTINCT **ID** FROM Inserted)
And yes, you need to specify a identity column for each trigger.
CAUTION: take care when inserting columns on tables where you don't know the code of the application. If your app have INSERT VALUES command without column definition, it will raise errors even with default value on new columns.
This is possible since SQL Server 2016 by using PERIOD FOR SYSTEM_TIME.
This is something that was introduced for temporal tables but you don't have to use temporal tables to use this.
An example is below
CREATE TABLE dbo.YourTable
(
FooId INT PRIMARY KEY CLUSTERED,
FooName VARCHAR(50) NOT NULL,
modstamp DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
MaxDateTime2 DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
PERIOD FOR SYSTEM_TIME (modstamp,MaxDateTime2)
)
INSERT INTO dbo.YourTable (FooId, FooName)
VALUES (1,'abc');
SELECT *
FROM dbo.YourTable;
WAITFOR DELAY '00:00:05'
UPDATE dbo.YourTable
SET FooName = 'xyz'
WHERE FooId = 1;
SELECT *
FROM dbo.YourTable;
DROP TABLE dbo.YourTable;
It has some limitations.
The time stored will be updated by the system and always be UTC.
There is a need to declare a second column (MaxDateTime2 above) that is completely superfluous for this use case. But it can be marked as hidden making it easier to ignore.
Okay, I always like to keep track of not only when something happened but who did it!
Lets create a test table in [tempdb] named [dwarfs]. At a prior job, a financial institution, we keep track of inserted (create) date and updated (modify) date.
-- just playing
use tempdb;
go
-- drop table
if object_id('dwarfs') > 0
drop table dwarfs
go
-- create table
create table dwarfs
(
asigned_id int identity(1,1),
full_name varchar(16),
ins_date datetime,
ins_name sysname,
upd_date datetime,
upd_name sysname,
);
go
-- insert/update dates
alter table dwarfs
add constraint [df_ins_date] default (getdate()) for ins_date;
alter table dwarfs
add constraint [df_upd_date] default (getdate()) for upd_date;
-- insert/update names
alter table dwarfs
add constraint [df_ins_name] default (coalesce(suser_sname(),'?')) for ins_name;
alter table dwarfs
add constraint [df_upd_name] default (coalesce(suser_sname(),'?')) for upd_name;
go
For updates, but the inserted and deleted tables exist. I choose to join on the inserted for the update.
-- create the update trigger
create trigger trg_changed_info on dbo.dwarfs
for update
as
begin
-- nothing to do?
if (##rowcount = 0)
return;
update d
set
upd_date = getdate(),
upd_name = (coalesce(suser_sname(),'?'))
from
dwarfs d join inserted i
on
d.asigned_id = i.asigned_id;
end
go
Last but not least, lets test the code. Anyone can type a untested TSQL statement in. However, I always stress testing to my team!
-- remove data
truncate table dwarfs;
go
-- add data
insert into dwarfs (full_name) values
('bilbo baggins'),
('gandalf the grey');
go
-- show the data
select * from dwarfs;
-- update data
update dwarfs
set full_name = 'gandalf'
where asigned_id = 2;
-- show the data
select * from dwarfs;
The output. I only waited 10 seconds between the insert and the delete. Nice thing is that who and when are both captured.
Create trigger tr_somename
On table_name
For update
As
Begin
Set nocount on;
Update t
Set t.field_name = getdate()
From table_name t inner join inserted I
On t.pk_column = I.pk_column
End
ALTER TRIGGER [trg_table_name_Modified]
ON [table_name]
AFTER UPDATE
AS
Begin
UPDATE table_name
SET modified_dt_tm = GETDATE() -- or use SYSDATETIME() for 2008 and newer
FROM Inserted i
WHERE i.ID = table_name.id
end

Using Snowflake UDF to query change tracking table meta data

Currently exploring different ways to identify deltas. I've got something using a change capture stream, but wanted to get something working on a table with change tracking enabled as an alternative option.
This is what I attempted to do:
create or replace table T1 (
id number(8) not null,
c1 varchar(255) default null
);
create or replace table T1_MERGED_OUTPUT (
id number(8) not null,
c1 varchar(255) default null
);
create or replace table DELTA_UPDATE_TIMES (
id number(8) not null,
table_name varchar(255) not null,
last_update_datetime TIMESTAMP_TZ default current_timestamp()
);
alter table t1 set change_tracking = true;
T1 is the source table with change tracking enabled. T1_MERGED_OUTPUT will be loaded with the incremental updates. DELTA_UPDATE_TIMES holds the times of when each table was updated. To get the latest update timestamps for each table, I've defined a UDF:
CREATE OR REPLACE FUNCTION TABLE_LAST_UPDATE_DATETIME(tbl VARCHAR)
RETURNS TIMESTAMP_TZ
AS
$$
SELECT MAX(LASTEST_UPDATE_DATETIME) FROM DELTA_UPDATE_TIMES WHERE TABLE_NAME = tbl
$$;
Then I tried to create another UDF to get the changes between the last update time and the current time like this:
CREATE OR REPLACE FUNCTION DATA_LAST_UPDATE_DATETIME(start_time TIMESTAMP_TZ)
RETURNS TABLE(ID NUMBER, C1 VARCHAR, ACTION_NAME VARCHAR, IS_UPDATE BOOLEAN)
AS
$$
select ID, C1, METADATA$ACTION AS ACTION_NAME, METADATA$ISUPDATE AS IS_UPDATE
from t1
changes(information => default)
at(timestamp => start_time)
end(timestamp => current_timestamp())
$$;
But I'm getting this error: SQL compilation error: error line 2 at position 29 invalid identifier 'METADATA$ACTION'
When I run that query outside of the function, it works correct and the action and is_update is returned.
Any ideas what the issue is here and whether there is a way round it?

how to fire a sql trigger whenever the password change

I have a table containing id, user_Name and password. I want to create the trigger which will fire when ever the password changes.
Suppose the table is:
create table reg
(
id int identity(1,1) primary key,
userName varchar(100),
pass varchar(100)
)
and I want to save userName, password, changeDate in to below table
create table regBackUp
(
id int identity(1,1),
regId foreign key references reg(id),
oldPass varchar(100),
changeDate date
)
Well, you need to create after update trigger on reg table.
In that trigger you need to write to table regBackUp records selected from table named deleted. It is special table available in that triggers and it will hold values of reg table just before update.
See MSDN for reference about syntax of create trigger expression.
Use this. TRIGGER
You can get the old values from DELETED table
CREATE TRIGGER trgTest ON regFOR UPDATE
AS
BEGIN
IF UPDATE(pass)
BEGIN
DECLARE #id AS INT
DECLARE #pass AS varchar(100)
SELECT #id = ID, #pass = pass FROM DELETED
INSERT INTO regBackUp (regId, oldPass, changeDate)
VALUES (#id, #pass, GETDATE())
END
END
Use After Update Trigger with Update() function to find out whether column is updated or not. From docs.
indicates whether an INSERT or UPDATE attempt was made on a specified
column of a table or view. UPDATE() is used anywhere inside the body
of a Transact-SQL INSERT or UPDATE trigger to test whether the trigger
should execute certain actions.
Create a After Update trigger like this.
CREATE TRIGGER reg_pass_trg
ON reg
after UPDATE
AS
BEGIN
IF UPDATE(pass) --Works only when pass is mentioned in update statement
INSERT INTO regBackUp
(regId,oldPass,changeDate)
SELECT ID,pass,Getdate()
FROM deleted
END
INSERT INTO namepassback
(username,
pass,
[date])
SELECT username,
pass,
Getdate()
FROM namepass
WHERE id = 1
use above query that will work same without creating trigger

How to create copy trigger function in PostgreSQL?

I have a table
CREATE TABLE order (
id SERIAL PRIMARY KEY NOT NULL,
total_price DECIMAL(12, 2),
date TIMESTAMP WITH TIME ZONE DEFAULT now(),
product_amount INT
);
And another table for order history
CREATE TABLE order_history (
id SERIAL PRIMARY KEY NOT NULL,
order_id INT,
total_price DECIMAL(12, 2),
date TIMESTAMP WITH TIME ZONE DEFAULT now(),
product_amount INT
);
I need a trigger function for ORDER table which can trigger on event when I insert some data into ORDER table or update data. Moreover this procedure must persist data into table ORDER_HISTORY.
Are you looking for something like this?
CREATE OR REPLACE FUNCTION save_order_in_history()
RETURNS trigger
AS $$
BEGIN
INSERT INTO order_history(order_id, total_price, product_amount)
VALUES (NEW.id, NEW.total_price, NEW.product_amount);
RETURN NULL;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER order_trigger AFTER INSERT OR UPDATE
ON "order" FOR EACH ROW EXECUTE PROCEDURE save_order_in_history();
Here is SQLFiddle demo
Further reading:
CREATE TRIGGER
Trigger Procedures

Create Unique ID in Stored Procedure to Match Legacy Data

I'm creating CRUD procedures that duplicate a legacy program that generates a unique ID based on a 'Next ID' field in a separate table. Rather than duplicate the use of a separate table I have written a stored procedure that reads the number of rows in the table.
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE #Rows varchar(12)
SET #Rows = (CONVERT(varchar(12), (SELECT Count(UniqueID) FROM [TLA_Items]) + 1))
SET #NewUniqueID = #ItemID + #SiteReference + #Rows
INSERT INTO [TLA_Items] ([ItemID], [UniqueID])
VALUES (#ItemID, #NewUniqueID)
SELECT #NewUniqueID
END
I've simplified the code above but what's not shown is that the TLA_Items table also has an IDENTITY column and that it needs to work with SQL Server 2008.
The UniqueID field has to match the pattern of the legacy program: ItemID + SiteReference + (integer representing number of previous records)
However when testing this I've found a flaw in my logic. If rows are deleted then it's possible to create a unique Id which matches an existing row. This doesn't happen in the legacy system as rows are rarely deleted and the separate table stores the next number in the sequence.
Other than store the next ID value in a separate table, is there a better technique, to create a unique ID that matches the legacy pattern?
You could have your procedure store only the prefix (#ItemID + #SiteReference) into UniqueID and use a FOR INSERT trigger to append the IDENTITY value as the rows component immediately after the row is inserted, something like this:
CREATE TRIGGER TLA_Items_Adjust
ON dbo.TLA_Items
FOR INSERT
AS
BEGIN
UPDATE t
SET t.UniqueID = i.UniqueID + CAST(t.IdentityColumn AS varchar(10))
FROM dbo.TLA_Items AS t
INNER JOIN inserted AS i
ON t.IdentityColumn = i.IdentityColumn
;
END
To read and return the newly generated UniqueID value as the OUTPUT parameter as well as a row, you could use a table variable and the OUTPUT clause in the INSERT statement, like this:
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE #GeneratedUniqueID TABLE (UniqueID varchar(68));
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
OUTPUT inserted.UniqueID INTO #GeneratedUniqueID (UniqueID)
VALUES (#ItemID, #ItemID + #SiteReference);
SELECT #NewUniqueID = UniqueID FROM #GeneratedUniqueID;
SELECT #NewUniqueID;
END
Although instead of using OUTPUT you could probably just read the value from the row matching the SCOPE_IDENTITY() result:
CREATE PROCEDURE [TLA_CreateItem]
#SiteReference varchar(50)
,#ItemID varchar(4)
,#NewUniqueID varchar(68) OUTPUT
AS
BEGIN
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
VALUES (#ItemID, #ItemID + #SiteReference);
SELECT #NewUniqueID = UniqueID
FROM dbo.TLA_Items
WHERE IdentityColumn = SCOPE_IDENTITY();
SELECT #NewUniqueID;
END
Here is another option, but please bear in mind that it would affect existing UniqueID values.
If you can afford a slight change to the table schema, you could add a column called something like UniqueIDPrefix:
ALTER TABLE dbo.TLA_Items
ADD UniqueIDPrefix varchar(56) NOT NULL;
and redefine the UniqueID column to be a computed column:
ALTER TABLE dbo.TLA_Items
DROP COLUMN UniqueID;
GO
ALTER TABLE dbo.TLA_Items
ADD UniqueID AS UniqueIDPrefix + CAST(IdentiyColumn AS varchar(12));
In your stored procedure, you would then need to populate UniqueIDPrefix instead of UniqueID (with just the result of #ItemID + #SiteReference)
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueIDPrefix])
VALUES (#ItemID, #ItemID + #SiteReference);
and read the value of UniqueID using either OUTPUT or SCOPE_IDENTITY(), as in my other answer.
It sounds like you are on SQL 2008, but if you were on 2012, you could use a sequence to store an incrementing value.
How about never delete? You could add a flag to the table for logical deletes.

Resources