Preventing specifying values in a column - sql-server

Is there a way to prevent specifying explicit values in a column? The identity attribute prevents specifying explicit values (as long as IDENTITY_INSERT is off), which is the type of behavior I want.
create table testguid (
ID uniqueidentifier default newsequentialid() not null primary key
,somedate datetime
)
[constraint or trigger here?]
insert into testguid (somedate) values (getdate()) -- this is ok
insert into testguid (ID, somedate) values (newid(), getdate()) -- this is not ok
I want the database to insert values, but I want to prevent any way of specifying them.

You could use an INSTEAD OF TRIGGER to basically re-write the INSERT statement, leaving out the ID column.
CREATE TRIGGER dbo.testguid_beforeinsert
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.testguid(somedate --, other columns
) SELECT GETDATE() --, other columns
FROM inserted;
END
GO
Probably want something similar for UPDATE, too.

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

Inserting to table having issue - Explicit value must be specified for identity column in table

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)

Insert values in second table using triggers

I have one table called [FridgeTemperture], when any record inserted it should add one value in the new table MpSensors. But records are not being inserted in the new table when a record is inserted.
Error
Explicit value must be specified for identity column in table
'MpSensors' either identity_insert is set to ON or when a replication
user is inserting into a not for replication identity column.
CREATE TRIGGER [dbo].[FridgeTemperature_INSERT]
ON [dbo].[FridgeTemperture]
AFTER INSERT
AS
BEGIN
SET IDENTITY_INSERT MpSensors ON;
SET NOCOUNT ON;
DECLARE #fridge_temp varchar(10)
INSERT INTO MpSensors(fridge_temp)
VALUES(#fridge_temp)
SET IDENTITY_INSERT MpSensors OFF;
END
GO
table schema
CREATE TABLE [dbo].[MpSensors](
[id] [int] IDENTITY(1,1) NOT NULL,
[fridge_temp] [varchar](10) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[FridgeTemperture](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ShopId] [nvarchar](4) NULL,
[Fridgetemp] [decimal](4, 2) NOT NULL,
[UpdatedDate] [datetime2](7) NOT NULL
GO
You don't need the set identity_insert on if you are not attempting to insert values to the identity column. Also, your current insert statement, if you loose the set identity_insert, will simply inside a single null row for any insert statement completed successfully on the FridgeTemperture table.
When using triggers, you have access to the records effected by the statement that fired the trigger via the auto-generated tables called inserted and deleted.
I think you are after something like this:
CREATE TRIGGER [dbo].[FridgeTemperature_INSERT]
ON [dbo].[FridgeTemperture]
AFTER INSERT
AS
BEGIN
INSERT INTO MpSensors(fridge_temp)
SELECT CAST(Fridgetemp as varchar(10))
FROM inserted
END
Though I can't really see any benefit of storing the same value in two different places, and in two different data types.
Update
Following our conversation in the comments, you can simply use an update statement in the trigger instead of an insert statement:
UPDATE MpSensors
SET fridge_temp = (
SELECT TOP 1 CAST(Fridgetemp as varchar(10))
FROM inserted
ORDER BY Id DESC
)
This should give you the latest record in case you have an insert statement that inserts more than a single record into the FridgeTemperture table in a single statement.
create TRIGGER [dbo].[FridgeTemperature_INSERT]
ON [dbo].[FridgeTemperture]
AFTER INSERT
AS
BEGIN
UPDATE MpSensors
SET fridge_temp = CAST(Fridgetemp as varchar(10))
FROM inserted
END
You need to use Select statement with CAST as [fridge_temp] is varchar in MpSensors table in Trigger. Try like this:
CREATE trigger <table_name>
ON <table_name>
AFTER Insert
AS
BEGIN
INSERT INTO <table_name>(column_name)
Select CAST(column_name as varchar(10))
FROM inserted
END
The inserted table stores copies of the affected rows during INSERT and UPDATE statements. During an insert or update transaction, new rows are added to both the inserted table and the trigger table. The rows in the inserted table are copies of the new rows in the trigger table.

Reinsert primary key in the same record

I need to insert records into a production table. The problem is that one of the fields needs to be the same value as the primary key.
In the example below, the Insert query is dropping '99' into [AlsoMyID]. But that's just a placeholder. It needs to be whatever value is going into [MyID].
How do I write the Insert query so that the system will add the same PK value to both [MyID] and [AlsoMyID]?
Drop table #mylittletable
Create table #Mylittletable (
[MyID] int IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[AlsoMyID] int,
[ActualData] varchar(1))
Select * from #Mylittletable
Insert into #Mylittletable values (99,'x')
Select * from #Mylittletable
If you're interested in the background, the developer is using AlsoMyID as a linking field so any number of records can be linked together using the original primary key value. That said, I have no control over the table structure.
Firstly, you cannot specify the value for identity column unless you use set identity_insert on. so according to your requirement, you need to insert the same value to AlsoMyID as MyID.
You can work it out as flowing:
insert into Mylittletable
select ##IDENTITY+1,'1'
With this trigger on the table you can insert anything on the alsoMyID-column and that will be overwritten with what get's set in the myID-column.
create trigger tr_Mylittletable ON Mylittletable
AFTER INSERT AS
BEGIN
declare #ID int = (select MyID from inserted)
update Mylittletable set AlsoMyID = #ID where MyID = #ID
END
NOTE: This only works when making inserts of one line at a time!

Set A Field the same as ID (IDENTITY) in the insert

I have a Code (int) in my table, the ID is set to identity. How can I set a default value for my code to be filled by the same value az ID? I mean Identity.
You could use an after insert trigger:
create table TestTable (id int identity, col1 int)
go
create trigger TestTrigger on TestTable after insert
as begin
update TestTable
set col1 = id
where col1 is null
and id in (select id from inserted)
end
go
Test code:
insert TestTable default values
insert TestTable (col1) values (666)
insert TestTable default values
select * from TestTable
In general, I try to stay clear of triggers. In the long run using a stored procedure for insert is much more maintainable:
create procedure dbo.InsertTestRow(
#col1 int)
as
insert TestTable (col1) values (#col1)
if #col1 is null
begin
update TestTable
set col1 = id
where id = SCOPE_IDENTITY()
end
If it always has the same value - why don't you just drop that field. Otherwise it can be maintained with triggers (BEFORE INSERT one).
I'm looking for something in the
default value! If it is null it should
be filled with the same value as id
but if it is provided with some value,
it should keep that value
You could solve the issue by using coalesce in your queries instead.
create table T (ID int identity, ID2 int)
insert into T values (default)
insert into T values (null)
insert into T values (78)
select
ID,
coalesce(ID2, ID) as ID2
from T
Result
ID ID2
-- ---
1 1
2 2
3 78
Assuming your table's ID is an Identity column, you could consider using a constraint:
ALTER TABLE MyTable
ADD CONSTRAINT MyTableCodeDefault
DEFAULT IDENT_CURRENT('MyTable') FOR Code
This works for these use cases:
INSERT INTO MyTable DEFAULT VALUES
INSERT INTO MyTable ({columns NOT including 'Code'})
VALUES ({value list matching insert columns})
INSERT INTO MyTable (Code) VALUES (666)
INSERT INTO MyTable (Code) SELECT 8 UNION SELECT 13 UNION SELECT 21
But it does not work for bulk inserts:
INSERT INTO MyTable ({columns NOT including 'Code'})
SELECT {value list matching insert columns}
UNION
SELECT {value list matching insert columns}
UNION
SELECT {value list matching insert columns}
This restriction may seem onerous, but in my practical experience, it's rarely a problem. Most of the use cases I've encountered that need a default value involve user/UI 'convenience': don't force the user to pick a value if they don't want to.
OTOH, rarely do I encounter bulk insert situations where it's impractical to specify the value for the columns you're targeting.
You could use computed column, like this:
if object_id('TempTable') is not null drop table TempTable
create table TempTable (Id int identity(1,1), Code as Id)
insert into TempTable
default values
insert into TempTable
default values
insert into TempTable
default values
select * from TempTable
Of course if you have other columns, then you dont need default values:
if object_id('TempTable') is not null drop table TempTable
create table TempTable (Id int identity(1,1), Code as Id, SomethingElse int)
insert into TempTable (SomethingElse)
select 10 union all
select 11 union all
select 12
select * from TempTable
But, like zerkms said - why do you need two columns that are same?
If the field is an Identity field in SQL Server, the database engine will take care of its value. What we normally do is to read the record back (after inserting) to get to the generated Id.
EDIT: It sounds like you are trying to "override" the identity? If so, before you insert, run:
SET IDENTITY_INSERT [tableName] ON
You'll have to be careful not to insert a value that already exists. This can get tricky, though. So maybe consider removing the identity property altogether, and managing the default values yourself?

Resources