I am building a WCF service with a SQL Server, which will be consumed by a WPF app. I want my database tables to have columns like:
CreatedOn, CreatedBy, LastModifiedOn, LastModifiedBy
Is there a way to create these authomatically, or if not I can create them, but is it possible somehow their values to be populated by SQL server?
Thanks
Made a couple of assumptions here - that CreatedBy/ModifiedBy would be populated with a system variable such as SUSER_SNAME() and that modified values should reflect the same values as created, initially. Assuming this base table:
USE tempdb;
GO
CREATE TABLE dbo.foo(fooID INT PRIMARY KEY);
GO
Make these modifications:
ALTER TABLE dbo.foo ADD CreatedOn SMALLDATETIME
NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dbo.foo ADD CreatedBy NVARCHAR(32)
NOT NULL DEFAULT SUSER_SNAME();
ALTER TABLE dbo.foo ADD ModifiedOn SMALLDATETIME
NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dbo.foo ADD ModifiedBy NVARCHAR(32)
NOT NULL DEFAULT SUSER_SNAME();
GO
Now you just need a trigger to handle subsequent updates:
CREATE TRIGGER dbo.foo_audit
ON dbo.foo
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
UPDATE f
SET ModifiedOn = CURRENT_TIMESTAMP,
ModifiedBy = SUSER_SNAME()
FROM dbo.foo AS f
INNER JOIN inserted AS i
ON f.fooID = d.fooID;
END
END
GO
If you need the username to be passed in from the app, then WCF is going to have to help with that.
Related
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
I'm getting ready to release a stored procedure that gets info from other tables, does a pre-check, then inserts the good data into a (new) table. I'm not used to working with keys and new tables as much, and my insert into this new table I'm creating is having this error message having to do with the insert/key:
Msg 545, Level 16, State 1, Line 131
Explicit value must be specified for identity column in table 'T_1321_PNAnnotationCommitReport' either when IDENTITY_INSERT is set to ON or when a replication user is inserting into a NOT FOR REPLICATION identity column.
BEGIN
...
BEGIN
IF NOT EXISTS (SELECT * FROM sys.tables where name = N'T_1321_PNAnnotationCommitReport')
BEGIN
CREATE TABLE T_1321_PNAnnotationCommitReport (
[id] [INT] IDENTITY(1,1) PRIMARY KEY NOT NULL, --key
[progressnote_id] [INT] NOT NULL,
[form_id] [INT] NOT NULL,
[question_id] [INT],
[question_value] [VARCHAR](max),
[associatedconcept_id] [INT],
[crte_date] [DATETIME] DEFAULT CURRENT_TIMESTAMP,
[create_date] [DATETIME] --SCHED_RPT_DATE
);
print 'test';
END
END --if not exists main table
SET IDENTITY_INSERT T_1321_PNAnnotationCommitReport ON;
...
INSERT INTO dbo.T_1321_PNAnnotationCommitReport--(progressnote_id,form_id,question_id,question_value,associatedconcept_id,crte_date, create_date) **I tried with and without this commented out part and it's the same.
SELECT progressnote_id,
a.form_id,
question_id,
questionvalue,
fq.concept_id,
getdate(),
a.create_date
FROM (
SELECT form_id,
progressnote_id,
R.Q.value('#id', 'varchar(max)') AS questionid,
R.Q.value('#value', 'varchar(max)') AS questionvalue,
create_date
FROM
#tableNotes t
OUTER APPLY t.form_questions.nodes('/RESULT/QUESTIONS/QUESTION') AS R(Q)
WHERE ISNUMERIC(R.Q.value('#id', 'varchar(max)')) <> 0
) a
INNER JOIN [CKOLTP_DEV]..FORM_QUESTION fq ON
fq.form_id = a.form_id AND
fq.question_id = a.questionid
--select * from T_1321_PNAnnotationCommitReport
SET IDENTITY_INSERT T_1321_PNAnnotationCommitReport OFF;
END
Any ideas?
I looked at some comparable inserts we do at work, insert into select and error message, and insert key auto-incremented, and I think I'm doing what they do. Does anyone else see my mistake? Thanks a lot.
To repeat my comment under the question:
The error is literally telling you the problem. You turn change the IDENTITY_INSERT property to ON for the table T_1321_PNAnnotationCommitReport and then omit the column id in your INSERT. If you have enabled IDENTITY_INSERT you need to supply a value to that IDENTITY, just like the error says.
We can easily replicate this problem with the following batches:
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
SomeValue varchar(20));
GO
SET IDENTITY_INSERT dbo.MyTable ON;
--fails
INSERT INTO dbo.MyTable (SomeValue)
VALUES('abc');
GO
If you want the IDENTITY value to be autogenerated, then leave IDENTITY_INSERT set to OFF and omit the column from the INSERT (like above):
SET IDENTITY_INSERT dbo.MyTable OFF; --Shouldn't be needed normally, but we manually changed it before
--works, as IDENTITY_INSERT IS OFF
INSERT INTO dbo.MyTable (SomeValue)
VALUES('abc');
If you do specifically want to define the value for the IDENTITY, then you need to both set IDENTITY_INSERT to ON and provide a value in the INSERT statement:
SET IDENTITY_INSERT dbo.MyTable ON;
--works
INSERT INTO dbo.MyTable (ID,SomeValue)
VALUES(10,'def');
GO
SELECT *
FROM dbo.MyTable;
IDENTITY_INSERT doesn't mean "Get the RDBMS to 'insert' the value" it means that you want to want to tell the RDBMS what value to INSERT. This is covered in the opening sentence of the documentation SET IDENTITY_INSERT (Transact-SQL):
Allows explicit values to be inserted into the identity column of a table.
(Emphasis mine)
I have no idea how to create this history trigger.
Imagine that we would like to store a history of price changes for each item. To do so, we would first need to create an Item_price_history table that stores at a minimum a reference to the item, its old price,
its new price, and the date of the change. We could then define a trigger on the Item
table that updates the Item_price_history table whenever an item price is updated.
I have created two tables as below:
CREATE TABLE ITEM(
item_id DECIMAL(10) NOT NULL,
description VARCHAR(30),
price DECIMAL(10),
PRIMARY KEY (item_id));
CREATE TABLE Item_price_history (
history_id DECIMAL(10) NOT NULL,
item_id DECIMAL(10) NOT NULL,
old_price DECIMAL(10,2),
new_price DECIMAL(10,2),
date_of_change DATE,
PRIMARY KEY (HISTORY_ID),
FOREIGN KEY (ITEM_ID) REFERENCES item);
Here is the trigger:
CREATE TRIGGER trHistory ON dbo.ITEM
FOR UPDATE --only for update
AS
BEGIN
IF UPDATE(price) --if price is really updated and not the other columns
BEGIN
INSERT INTO dbo.Item_price_history(item_id, old_price, new_price, date_of_change)
SELECT d.item_id, d.price, i.price, GETDATE()
FROM Deleted d
JOIN Inserted i ON d.item_id = i.item_id
END
END
Try this.
CREATE TRIGGER Sample ON Item_price_history
FOR UPDATE
AS
BEGIN
DECLARE #nOldPRICE AS DECIMAL(10)
SET #nOldPRICE = (SELECT price FROM DELETED)
INSERT INTO Item_price_history (item_id, old_price, new_price, date_of_change)
SELECT item_id, #nOldPRICE, new_price, GETDATE() FROM INSERTED
END
For sure you can use a trigger. But why?
You can update the history table when you update the main table and keep all the business logic in one place.
I remember in old days, during a programming SQL2000 seminar that it was told:
"Avoid the use of triggers. In most cases they try to solve design problems."
Some info
I'm trying to insert into two tables at the same time via stored procedure but it writes to only one table and fail to the other.
ALTER PROCEDURE [dbo].[insert_emp_pics]
#EmpName nvarchar(100),
#Nationality nvarchar(30),
#PassportPic nvarchar(100),
#Pic nvarchar(100)
AS
Begin
set nocount on;
DECLARE #ID int,
#Emp_ID int
insert into Employee (EmpName,Nationality)
values (#EmpName,#Nationality)
select #ID = ##IDENTITY
insert into DatePics
(PassportPic,Pic)
values
(#PassportPic ,#Pic)
select #Emp_ID = ##IDENTITY
end
There is relation between two tables
first table [Employee] PK ID
second table [DatePics] FK Emp_ID
this is the error message after executing this statement.
Cannot insert the value NULL into column 'Emp_ID', table 'QTecTest.dbo.DatePics';
column does not allow nulls. INSERT fails.
You need to insert the new Emp_Id as a Foreign Key to DatePics (and assuming both tables have identity columns):
insert into Employee (EmpName,Nationality)
values (#EmpName,#Nationality);
set #EMP_ID = SCOPE_IDENTITY();
insert into DatePics (PassportPic,Pic, Emp_ID)
values (#PassportPic ,#Pic, #EmpID);
set #DatePicsID = SCOPE_IDENTITY();
Also, please use SCOPE_IDENTITY over ##IDENTITY - ##Identity is vulnerable to issues where a Trigger also creates an new (unrelated) identity.
A column declared as primary key cannot have NULL values.
In your stored procedure you are not supplying value to Emp_ID column and so Insert fails.
If you want to automatically insert values in that column make it as IDENTITY column also
I want to insert new column in existing table with 2nd position.
Now i have columns order like
Emp_id, Emp_Name, Address, phoneNo.
I want to add "Gender" in near Emp_Name.
Emp_id, Emp_Name, Gender, Address, phoneNo).
I can't delete this table and create new table.
You can not do this programmatically without creating a new table.
if you are allowed to create and delete tables then:
create a new table:
CREATE TABLE new_table_name
(
column_name1 data_type(size),
column_name2 data_type(size),
...
);
move the data from your main table to the new one then delete the old table.
DROP TABLE my_table
after all rename the new table to the name of which was on your old deleted table.
--alternative:
you can reorder columns in your host application if possible!
Hi you may try the following:
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Tmp_TableName
(
Emp_id INT NOT NULL,
Emp_Name VARCHAR(100) NULL,
Gender BIT NULL,
[Address] VARCHAR(100) NULL,
phoneNo int NULL
) ON [PRIMARY]
GO
IF EXISTS(SELECT * FROM dbo.Tmp_TableName)
EXEC('INSERT INTO dbo.Tmp_TableName (Emp_id, Emp_Name, [Address],phoneNo)
SELECT Emp_id, Emp_Name, [Address],phoneNo FROM dbo.TableName WITH (HOLDLOCK TABLOCKX)')
GO
DROP TABLE dbo.TableName
GO
EXECUTE sp_rename N'dbo.Tmp_TableName', N'TableName', 'OBJECT'
GO
COMMIT
Make sure to change TableName to your actual table name.
Giannis
You can do it but what for?
SQLFiddle demo
Alter table T ADD Gender varchar(10);
Alter table T ADD Address2 varchar(100),phoneNo2 varchar(100);
update T set Address2=Address,PhoneNo2=PhoneNo;
Alter table T DROP COLUMN Address,PhoneNo;
Alter table T ADD Address varchar(100),phoneNo varchar(100);
update T set Address=Address2,PhoneNo=PhoneNo2;
Alter table T DROP COLUMN Address2,PhoneNo2;