Only allow current date/time on SQL Server insertion - sql-server

I need a way to enforce a single value only within an inserted field, more precisely a DateTime field, that should always be set to the current date/time at insertion.
I am working on a university exercise and they want all the constraints to be done within the DB, ordinarily i would just put on a DEFAULT GetDate() and always use DEFAULT on inserts, but the exercise requirements prevent this.
Now for an integer i can do this(i've omitted the other fields, since they are irrelevant to the issue at hand) :
CREATE TABLE tester(
d INTEGER not null DEFAULT 3,
CONSTRAINT chkd CHECK(d = 3)
);
However what i want is the following :
CREATE TABLE tester(
d DATETIME not null DEFAULT GETDATE(),
CONSTRAINT chkd CHECK(d = ????????)
);
Re-iterating GetDate() in the check() will trigger an error on inserts, because the microseconds will cause a mismatch.
So i guess the first question is, is this possible? and if so(i hope so) how?

Don't track the date/time in the tester table. Instead, have a separate table with a column that references the ID of the tester table as a foreign key constraint. The new table will have one other column, a DateTime column. On insertion into the tester table, a trigger can be fired that will insert a row into the new table containing the ID of the newly-created tester row as well as the current date/time.

Based upon Ryan's comment got to this answer which is working
CREATE TRIGGER trigger_date ON [dbo].[tester]
FOR INSERT
AS
BEGIN
UPDATE tester SET d = GETDATE() WHERE id IN (SELECT id FROM INSERTED);
END
GO

Related

EF - pass null value to SQL column so default value gets inserted

SQL Server table T2 has 2 columns:
Id INT NOT NULL
CreateDate DateTime NOT NULL, default = (getdate())
This statement inserts the CreateDate value correctly because it uses (getdate()) as default.
Insert T2 (Id)
Values (1)
So far so good. The problem is when I use Entity Framework to insert a row and still wish to use the default (getdate()) value.
Because the CreateDate is defined as NOT NULL, I cannot leave it blank or leave out of the Insert statement when using EF. But I want SQL to generate the timestamp on the server/database side.
Is there a way to handle this?
Thanks to squillman's reference to another SO post, I was able to find the answer.
Go to EDMX diagram, and you can set the StoreGeneratedPattern property to achieve what I am trying to do.
There are three Database Generated Options
Computed : The database generates a value when a row is inserted or updated.
Identity : The database generates a value when a row is inserted.
None : The database does not generate values.
EDIT: Although the picture shows Identity, I had to change it to Computed. The reason is that Identity option only works if the row is Inserted only. If the row is ever updated (other columns updated), then it caused an error. The Computed option seems to work fine with Insert (runs the default script) and Updates (to other columns, default script does not run again).

default date constraint has null records

We are having occasionally EMPTY records in our table/column below when there are multiple records inserted at one shot. While technically this is allowed since the column is nullable, the default constraint should apply for every row inserted.
ALTER TABLE [dbo].[JOB] ADD [DATE_CREATED] [nvarchar](35) NULL CONSTRAINT [DF_JOB_DATE_CREATED] DEFAULT (sysdatetime())
The one possible reason I could think of is "The default will only apply if you don't insert explicitly to that column". But I couldn't find anywhere code does that but I'm still working on that. Any other possible reasons?
We are on SQL Server 2012. The purpose of the column is to capture created date and time for processing. We can't have this column Non-nullable as this is a reporting column which shouldn't have a business impact.
Thank you for your advise.
Make the column NOT NULL. At the very least, do that so you can capture what application/query is explicitly inserting NULLs - which really just shouldn't be allowed.
Short of that, create a trigger:
CREATE TRIGGER trg_JOB_CreateDate
ON dbo.JOB
AFTER INSERT
AS
BEGIN
UPDATE j
SET DateInserted = GETDATE() -- consider using GETUTCDATE()
FROM JOB j
INNER JOIN inserted i
ON i.PrimaryKeyName = JOB.PrimaryKeyName
END
However, this could result in some additional transactional overhead, and won't stop someone from updating the column to = NULL. But again, if having that be null breaks something, then you really should just have the column be NOT NULL.

SQL Server time stamp column insertion or updation possible explicitly?

Is there any way to provide an explicit value for time stamp column in a table in SQL server? I am aware it is not datetime column but I want to know whether there is any way to insert or update it explicitly.
You cannot insert/update to timestamp column explicitly. They are generated automatically, when you perform insert/update to the table.
Because the timestamps appear to be representations of timestamps created by the database when you inserted or updated the column, in effect you would have to change the original timestamp created by the database in order to define them explicitly.
From your second comment I appreciate that you might have data coming in which is already timestamped and you just want those represented on your table in the same way as inserting data with "set identity_insert on" .
The answer would be to select the existing table into another table then add the incoming data. If you run the code below I think you'll see what I mean.
create table abc
(
col1 int, timestamp
)
go
insert into abc(col1) values (1)
go
select col1,convert(varbinary,timestamp) timestamp# into def from abc
go
select * from abc
select * from def
As far as I know the timestamp represents a row version number (which is why they change when you update a value in the row because you are creating another version of the row). There might be a date in the transaction log which states when this version of the row came into being. I don't consider it possible to directly convert timestamp to datetime.
Well..the only other idea I have is to add another column and then select the timestamp values into that! The weirdest thing, in doing this it takes the last character back one! See what you think.
drop table abc
go
create table abc
(
col1 int, timestamp
)
go
insert into abc(col1) values (1)
go
alter table abc add timestamp# varbinary(18)
go
update abc set timestamp# = convert(varbinary,timestamp)
Generaly speaking, when creating a table I would include a column which defaults to datetime, this way you have a datetime when each row is created.
Like this:
drop table def
go
create table def
(
col1 int,
idt datetime default getdate()
)
If you insert a value into col1 and do not include the idt in your column list in the insert statement the idt column will default to the datetime you inserted the value.
Like this:
insert into def (col1) values (1)

How to emulate a BEFORE INSERT trigger in T-SQL / SQL Server for super/subtype (Inheritance) entities? [duplicate]

This question already has answers here:
How can I do a BEFORE UPDATED trigger with sql server?
(9 answers)
Closed 2 years ago.
This is on Azure.
I have a supertype entity and several subtype entities, the latter of which needs to obtain their foreign keys from the primary key of the super type entity on each insert. In Oracle, I use a BEFORE INSERT trigger to accomplish this. How would one accomplish this in SQL Server / T-SQL?
DDL
CREATE TABLE super (
super_id int IDENTITY(1,1)
,subtype_discriminator char(4) CHECK (subtype_discriminator IN ('SUB1', 'SUB2')
,CONSTRAINT super_id_pk PRIMARY KEY (super_id)
);
CREATE TABLE sub1 (
sub_id int IDENTITY(1,1)
,super_id int NOT NULL
,CONSTRAINT sub_id_pk PRIMARY KEY (sub_id)
,CONSTRAINT sub_super_id_fk FOREIGN KEY (super_id) REFERENCES super (super_id)
);
I wish for an insert into sub1 to fire a trigger that actually inserts a value into super and uses the super_id generated to put into sub1.
In Oracle, this would be accomplished by the following:
CREATE TRIGGER sub_trg
BEFORE INSERT ON sub1
FOR EACH ROW
DECLARE
v_super_id int; //Ignore the fact that I could have used super_id_seq.CURRVAL
BEGIN
INSERT INTO super (super_id, subtype_discriminator)
VALUES (super_id_seq.NEXTVAL, 'SUB1')
RETURNING super_id INTO v_super_id;
:NEW.super_id := v_super_id;
END;
Please advise on how I would simulate this in T-SQL, given that T-SQL lacks the BEFORE INSERT capability?
Sometimes a BEFORE trigger can be replaced with an AFTER one, but this doesn't appear to be the case in your situation, for you clearly need to provide a value before the insert takes place. So, for that purpose, the closest functionality would seem to be the INSTEAD OF trigger one, as #marc_s has suggested in his comment.
Note, however, that, as the names of these two trigger types suggest, there's a fundamental difference between a BEFORE trigger and an INSTEAD OF one. While in both cases the trigger is executed at the time when the action determined by the statement that's invoked the trigger hasn't taken place, in case of the INSTEAD OF trigger the action is never supposed to take place at all. The real action that you need to be done must be done by the trigger itself. This is very unlike the BEFORE trigger functionality, where the statement is always due to execute, unless, of course, you explicitly roll it back.
But there's one other issue to address actually. As your Oracle script reveals, the trigger you need to convert uses another feature unsupported by SQL Server, which is that of FOR EACH ROW. There are no per-row triggers in SQL Server either, only per-statement ones. That means that you need to always keep in mind that the inserted data are a row set, not just a single row. That adds more complexity, although that'll probably conclude the list of things you need to account for.
So, it's really two things to solve then:
replace the BEFORE functionality;
replace the FOR EACH ROW functionality.
My attempt at solving these is below:
CREATE TRIGGER sub_trg
ON sub1
INSTEAD OF INSERT
AS
BEGIN
DECLARE #new_super TABLE (
super_id int
);
INSERT INTO super (subtype_discriminator)
OUTPUT INSERTED.super_id INTO #new_super (super_id)
SELECT 'SUB1' FROM INSERTED;
INSERT INTO sub (super_id)
SELECT super_id FROM #new_super;
END;
This is how the above works:
The same number of rows as being inserted into sub1 is first added to super. The generated super_id values are stored in a temporary storage (a table variable called #new_super).
The newly inserted super_ids are now inserted into sub1.
Nothing too difficult really, but the above will only work if you have no other columns in sub1 than those you've specified in your question. If there are other columns, the above trigger will need to be a bit more complex.
The problem is to assign the new super_ids to every inserted row individually. One way to implement the mapping could be like below:
CREATE TRIGGER sub_trg
ON sub1
INSTEAD OF INSERT
AS
BEGIN
DECLARE #new_super TABLE (
rownum int IDENTITY (1, 1),
super_id int
);
INSERT INTO super (subtype_discriminator)
OUTPUT INSERTED.super_id INTO #new_super (super_id)
SELECT 'SUB1' FROM INSERTED;
WITH enumerated AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rownum
FROM inserted
)
INSERT INTO sub1 (super_id, other columns)
SELECT n.super_id, i.other columns
FROM enumerated AS i
INNER JOIN #new_super AS n
ON i.rownum = n.rownum;
END;
As you can see, an IDENTIY(1,1) column is added to #new_user, so the temporarily inserted super_id values will additionally be enumerated starting from 1. To provide the mapping between the new super_ids and the new data rows, the ROW_NUMBER function is used to enumerate the INSERTED rows as well. As a result, every row in the INSERTED set can now be linked to a single super_id and thus complemented to a full data row to be inserted into sub1.
Note that the order in which the new super_ids are inserted may not match the order in which they are assigned. I considered that a no-issue. All the new super rows generated are identical save for the IDs. So, all you need here is just to take one new super_id per new sub1 row.
If, however, the logic of inserting into super is more complex and for some reason you need to remember precisely which new super_id has been generated for which new sub row, you'll probably want to consider the mapping method discussed in this Stack Overflow question:
Using merge..output to get mapping between source.id and target.id
While Andriy's proposal will work well for INSERTs of a small number of records, full table scans will be done on the final join as both 'enumerated' and '#new_super' are not indexed, resulting in poor performance for large inserts.
This can be resolved by specifying a primary key on the #new_super table, as follows:
DECLARE #new_super TABLE (
row_num INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
super_id int
);
This will result in the SQL optimizer scanning through the 'enumerated' table but doing an indexed join on #new_super to get the new key.

SQL server trigger question

I am by no means a sql programmer and I am trying to accomplish something that I am pretty sure has been done a million times before.
I am trying to auto generate a customer number in sql every time a new customer is inserted, but the trigger (or sp?) will only work if at least the first name, last name and another value called case number is entered. If any of these fields are missing, the system generates an error. If the criteria is met, the system generates and assigns a unique id to that customer that begins with letters GL- and then uses 5 digit number so a customer John Doe would be GL-00001 and Jane Doe would be GL-00002.
I am sorry if I am asking too much but I am basically a select insert update guy and nothing more so thanks in advance for any help.
If I were in this situation, I would:
--Alter the table(s) so that first name, last name and case number are required (NOT NULL) columns. Handle your checks for required fields on the application side before submitting the record to the database.
--If it doesn't already exist, add an identity column to the customer table.
--Add a persisted computed column to the customer table that will format the identity column into the desired GL-00000 format.
/* Demo computed column for customer number */
create table #test (
id int identity,
customer_number as 'GL-' + left('00000', 5-len(cast(id as varchar(5)))) + cast(id as varchar(5)) persisted,
name char(20)
)
insert into #test (name) values ('Joe')
insert into #test (name) values ('BobbyS')
select * from #test
drop table #test
This should satisfy your requirements without the need to introduce the overhead of a trigger.
So what do you want to do? generate a customer number even when these fields arn't populated?
Have you looked at the SQL for the trigger? You can do this in SSMS (SQL Server Managment Studio) by going to the table in question in the Object Explorer, expanding the table and then expanding triggers.
If you open up the trigger you'll see what it does to generate the customer number. If you are unsure on how this code works, then post the code for the trigger up.
If you are making changes to an existing system i'd advise you to find out any implications that changing the way data is inputted works.
For example, others parts of the application may depend on all of the initial values being populated, so after changing the trigger to allow incomplete data to be added, you may inturn break something else.
You have probably a unique constraint and/or NOT NULL constraints set on the table.
Remove/Disable these (for example with the SQL-Server Management Console in Design Mode) and then try again to insert the data. Keep in mind, that you will probably not be able to enable the constraints after your insert, since you are violating conditions after the insert. Only disable or reomve the constraints, if you are absolutely sure that they are unecessary.
Here's example syntax (you need to know the constraint names):
--disable
ALTER TABLE customer NOCHECK CONSTRAINT your_constraint_name
--enable
ALTER TABLE customer CHECK CONSTRAINT your_constraint_name
Caution: If I were you, I'd rather try to insert dummy values for the not null columns like this:
insert into customers select afield , 1 as dummyvalue, 2 as dummyvalue from your datasource
A very easy way to do this would be to create a table of this sort of structure:
CustomerID of type in that is a primary key and set it as identity
CustomerIDPrfix of type varchar(3) which stores GL- as a default value.
Then add your other fields and set them to NOT NULL.
If that way is not acceptable and you do need to write a trigger check out these two articles:
http://msdn.microsoft.com/en-us/library/aa258254(SQL.80).aspx
http://www.kodyaz.com/articles/sql-trigger-example-in-sql-server-2008.aspx
Basiclly it is all about getting the logic right to check if the fields are blank. Experiment with a test database on your local machine. This will help you get it right.

Resources