Prohibit Inserting Rows With Default Constraint - sql

Is there any way of how to prevent inserting data in specified columns in table and use only the default (constraint) values?
E.g. I have columns:
LogInsert (DF GETDATE())
LogUser (DF ORIGINAL_LOGIN())
both defined with DEFAULT constraint. I do not want to allow users to insert into those columns, but use default values here instead when inserting new row.
This should raise an error.
INSERT INTO T1
( C1
,C2
,LogInsert
,LogUser
)
VALUES ( 'A'
,'B'
,'20160101 10:53'
,'domain\user'
);
User should be able to perform the following script without error.
INSERT INTO T1
( C1, C2 )
VALUES ( 'A', 'B' );

You could always give your users a view to work against instead of a table. You can then either choose to hide the columns completely or (as here) make them computed so that they cannot insert a value into the column, via the view:
create table dbo._T1 (
ID int IDENTITY(1,1) not null,
Inserted datetime2 constraint DF__T1_Inserted DEFAULT (SYSDATETIME()) not null,
ABC varchar(10) not null,
constraint PK__T1 PRIMARY KEY (ID)
)
go
create view dbo.T1
with schemabinding
as
select
ID,
COALESCE(Inserted,SYSDATETIME()) as Inserted,
ABC
from dbo._T1
go
insert into dbo.T1 (ABC) values ('abc')
go
insert into dbo.T1 (ABC,Inserted) values ('def',SYSDATETIME())
Results:
(1 row(s) affected)
Msg 4406, Level 16, State 1, Line 19
Update or insert of view or function 'dbo.T1' failed because it contains a derived or constant field.
All of the users queries continue to just use T1. It just happens to be a view rather than a table.
In the above, the view uses COALESCE(Inserted,SYSDATETIME()). It doesn't really matter what's used here, and it doesn't need to match e.g. the default definition. All that's important is that some computation is performed on the Inserted column so that it becomes a read-only column in the view.

You can create a Check Constraint on the table, for example the below
CREATE TABLE [dbo].[T1]
(
[C1] VARCHAR(50)
,[LogInsert] DATETIME DEFAULT GETDATE()
,[LogUser] VARCHAR(500) DEFAULT ORIGINAL_LOGIN()
)
ALTER TABLE [T1] WITH CHECK ADD CONSTRAINT [CK_T1_LogInsert] CHECK ([LogInsert] = GETDATE())
ALTER TABLE [T1] WITH CHECK ADD CONSTRAINT [CK_T1_LogUser] CHECK ([LogUser] = ORIGINAL_LOGIN())
INSERT INTO [dbo].[T1] ([C1]) VALUES ('A')
INSERT INTO [dbo].[T1] ([C1]) VALUES ('B')
INSERT INTO [dbo].[T1] ([C1]) VALUES ('C')
SELECT * FROM [dbo].[T1]
--Will Fail
INSERT INTO [dbo].[T1] ([C1],[LogInsert]) VALUES ('D','2016-11-11 00:00')
INSERT INTO [dbo].[T1] ([C1],[LogUser]) VALUES ('D','Not Your UserName')
OR
You can force the user to only insert using a stored procedure and not allow that as a parameter, this can be done with a table variable too for bulk inserts.

Related

sql unique constraint with time window

I have a table where records have a (begin, end) time window of existence (for things like employement duration, birth and death, rent duration, ...)
begin IS NULL or end IS NULL if there is no bound.
CREATE TABLE mytable(
id int primary key,
value int, --UNIQUE at any point in time
begin datetime NULL,
end datetime NULL
);
I want column value to be unique at any point in time.
INSERT INTO mytable VALUES(1, 1, '2021-07-23', '2021-07-24'),(2, 1, '2021-07-25', NULL);
Is OK
Whereas
INSERT INTO mytable VALUES(1, 1, '2021-07-23', '2021-07-30'),(2, 1, '2021-07-25', NULL);
Is not OK, because both records have value=1 and overlapping time windows.
Is there a way to enforce such a constraint in SQL ?
You can't do this on the table, no, as there's nothing to make UNIQUE on.
What you could do, however, is use a VIEW to enforce it.
Firstly, let's create your table. I assume the columns datetime, should actually be begin and end; I recommend against these names as they are reserved keywords. As such I am calling them DateBegin and DateEnd. I am also assuming that they are date only (no time portion) values and so define them as a date not a datetime:
CREATE TABLE dbo.mytable(ID int primary key,
Value int,
[BeginDate] date NULL,
[EndEnd] date NULL);
And we'll INSERT your first 2 rows, as they are "ok":
INSERT INTO dbo.mytable (ID, Value, BeginDate, EndDate)
VALUES(1, 1, '20210723', '20210724'),
(2, 1, '20210725', NULL);
Now we need to make a VIEW, but we need one row per date. As such you'll want to create a Calendar Table. I'm not going to cover how to create one here, but there are literally 100's of articles, such as there on SQL Server Central: Bones of SQL - The Calendar Table, Calendar Tables in T-SQL.
Once you have your Calendar table, you can create the VIEW below, which JOINs the data in your table to the calendar table. We're going to make it so that the VIEW just returns the columns value and the date. WE're also going to schemabind it; this means we'll be able to add an UNIQUE INDEX to it:
CREATE VIEW dbo.MyView
WITH SCHEMABINDING
AS
SELECT MT.[Value],
CT.CalendarDate
FROM dbo.MyTable MT
JOIN dbo.CalendarTable CT ON MT.BeginDate <= CT.CalendarDate --I assume, despite your schema, MT.BeginDate can't be NULL
AND (MT.EndDate >= CT.CalendarDate OR MT.EndDate IS NULL);
Now we have a VIEW that has a row for each date, and for each value. This means we can now create our UNIQUE INDEX:
CREATE UNIQUE CLUSTERED INDEX MyIndex ON dbo.MyView ([Value], CalendarDate);
Now if we try to INSERT a row that is on the same date and value, we'll get an error:
INSERT INTO dbo.MyTable (ID, Value, BeginDate, EndDate)
VALUES(3, 1, '20210720', '20210723');
Cannot insert duplicate key row in object 'dbo.MyView' with unique index 'MyIndex'. The duplicate key value is (1, 2021-07-23).

How to create trigger that updates field on parent with the sum from 2 children

Updated to include screenshot - I need to create a trigger to update a field on a parent table with the sum of the values from two child tables. When the parent record is saved it should calculate ParentTotalEmployees = Sum(CountryTotEmployees) + Sum(StateTotEmployees). I can get it to populate if I only reference one child table but I haven't been able to figure out how to include the second child table.
ALTER TRIGGER [dbo].[DD_UpdateTotEmp] ON [dbo].[DEALDATA]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
;WITH GrandTotCountry AS (
SELECT c.QDEALDATA1,
SUM(QTOTCOUNTRYEMP) AS TotCountryEmp
FROM
DEALDATA1 c
GROUP BY c.QDEALDATA1
),
GrandTotState AS (
SELECT c.QDEALDATA,
SUM(QNUMSTATEEMP) AS TotStateEmp
FROM
DEALDATA2 c
GROUP BY c.QDEALDATA)
UPDATE T1
SET T1.QGRANDTOTEMP = (SELECT TotCountryEmp
FROM GrandTotCountry T2
WHERE T2.QDEALDATA=i.QDEALDATA)
FROM DEALDATA T1
INNER JOIN Inserted i ON T1.QDEALDATA=i.QDEALDATA
END
OR THIS ONE
CREATE TRIGGER [dbo].[DD_UpdateTotEmp] ON [dbo].[DEALDATA]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
UPDATE T1
SET T1.QGRANDTOTEMP = (SELECT SUM(QTOTCOUNTRYEMP)
FROM DEALDATA1 T2
WHERE T2.QDEALDATA=i.QDEALDATA)
FROM DEALDATA T1
INNER JOIN Inserted i ON T1.QDEALDATA=i.QDEALDATA
END
Sample Data
USE TEMPDB
GO
-- Parent Table
CREATE TABLE [dbo].[DEALDATA](
[QDEALDATA] [varchar](36) NOT NULL PRIMARY KEY CLUSTERED,
[MATTERSYSID] [varchar](36) NULL,
[QGRANDTOTEMP] [numeric](12, 0) NULL )
GO
INSERT INTO DEALDATA VALUES ('1404fcb1','C333897E',NULL);
INSERT INTO DEALDATA VALUES ('a51f9f8a','8AE3F809',NULL);
GO
-- Country Emp Table
CREATE TABLE [dbo].[DEALDATA1](
[QDEALDATA1] [varchar](36) NOT NULL PRIMARY KEY CLUSTERED,
[QDEALDATA] [varchar](36) NULL,
[QCOUNTRY] [varchar](40) NULL,
[QTOTCOUNTRYEMP] [numeric](12, 0) NULL )
GO
INSERT INTO DEALDATA1 VALUES ('60ae5737','a51f9f8a','Monaco',5);
INSERT INTO DEALDATA1 VALUES ('62ceecb9','a51f9f8a','Australia',10);
INSERT INTO DEALDATA1 VALUES ('a645fcd1','1404fcb1','United States',100);
GO
-- State Emp Table
CREATE TABLE [dbo].[DEALDATA2](
[QDEALDATA2] [varchar](36) NOT NULL PRIMARY KEY CLUSTERED,
[QDEALDATA] [varchar](36) NULL,
[QEMPSTATE] [varchar](40) NULL,
[QNUMSTATEEMP] [numeric](12, 0) NULL )
GO
INSERT INTO DEALDATA2 VALUES ('453b7b64','a51f9f8a','NY',50);
INSERT INTO DEALDATA2 VALUES ('e803b38f','a51f9f8a','KY',50);
INSERT INTO DEALDATA2 VALUES ('413954e1','1404fcb1','MO',20);
INSERT INTO DEALDATA2 VALUES ('ef2213e5','1404fcb1','HI',10);
GO
Thank you in advance in helping me with this.
A trigger (insert, Update, and/or Delete) belongs to a particular table. If you need a trigger on two tables (or many tables) you will need two triggers (or many triggers).
However, you can write a stored-procedure and call it from two triggers. And Since you have used after trigger, you don't need to use Inserted, Deleted objects.
It can be like this:
ALTER TRIGGER Trigger1 ON Table1
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
EXEC‌ TheProcedure
END‌
and
ALTER TRIGGER Trigger2 ON Table2
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
EXEC‌ TheProcedure
END‌
As you see the notes bellow, the above code performance is really bad. The best you can do is to redesign your tables. However, if you prefer slight modification on your data base design, you can create two aggregate tables for your child tables and then use a VIEW‌ to combine them into a single result.
Here is the solution. Thanks to all who responded!
UPDATE dst
SET dst.GrandTotEmp = COALESCE(tot1.TotCountryEmp, 0) + COALESCE(tot2.TotStateEmp, 0)
FROM DEALDATA as dst
JOIN inserted AS i ON dst.QDEALDATA = i.QDEALDATA
LEFT JOIN GrandTotCountry AS tot1 ON tot1.QDEALDATA = dst.QDEALDATA
LEFT JOIN GrantTotState AS tot2 ON tot2.QDEALDATA = dst.QDEALDATA

Specify "NEXT VALUE" for INSERT statement using identity column in SQL Server

Consider the following table and SQL from Microsoft's INSERT documentation that deals with IDENTITY columns:
CREATE TABLE dbo.T1 (column_1 int IDENTITY, column_2 VARCHAR(30));
GO
INSERT T1 (column_2) VALUES ('Row #2');
The INSERT statement does not specify column_1 as a column of the table, and SQL Server auto-populates the next value for that identity column. This is the normal way identity columns are handled.
How can I have the same behavior, while also specifying the column name?
For example, I'm looking for something like:
INSERT INTO T1 (column_1, column_2)
VALUES (NEXT VALUE, 'Row #3');
GO
I don't believe NEXT VALUE works here, but is there something that does work? Is there a key token or function that will indicate that the identity column should be used?
Note: the reason I ask is that the framework I'm using requires all columns to be specified in the column list.
If you are on SQL Server 2012 and later, you can use sequence. But you must remove the IDENTITY property from Column1 first. This can only be done by copy-and-rename a new table.
CREATE SEQUENCE Column1_Sequence
AS int
START WITH 0;
CREATE TABLE T1
(
Column1 int DEFAULT (NEXT VALUE FOR Column1_Sequence) PRIMARY KEY
, Column2 nvarchar(30)
)
After that, you can insert data into the table in 2 ways:
INSERT INTO T1 (Column1, Column2)
SELECT NEXT VALUE FOR Column1_Sequence
, 'Row #2'
INSERT INTO T1 (Column2)
SELECT 'Hello world'
Can you set the identity insert on before inserting and then set the identity insert off
You cannot set value for identity column unless you set identity_insert on for this table (one at time). Some examples:
create table #tmp (id int identity(1,1), name varchar(10))
insert #tmp (id,name) values (2,'test')
--error Cannot insert explicit value for identity column in table '#tmp
set identity_insert #tmp on --for one table in DB
insert #tmp (id,name) values (2,'qwas')
select * from #tmp
set identity_insert #tmp off -- good practice
--works
--see current identity value
SELECT IDENT_CURRENT ('#tmp') AS Current_Identity;
--Reset identity value
DBCC CHECKIDENT (#tmp, RESEED, 999)
--next insert will be 1000
Of course, if you reset next identity to a value which conflicts with PK (common usage of identity) you will have Violation of PRIMARY KEY constraint error
I am pretty sure there is no way to do that with SQL Server. Two workarounds that I can think of:
Fix the library if possible.
If the library supports it, you can create a view and INSERT into that instead. For example:
CREATE TABLE MyTable
(
ID INT IDENTITY(1, 1),
SomeColumn VARCHAR(100)
)
GO
CREATE VIEW MyTableView
AS
SELECT SomeColumn
FROM MyTable
GO
INSERT INTO MyTableView (SomeColumn) VALUES ('Test')

default column value

i'm trying to have a table with a column that has a default value.
right now i can only get this by having a trigger change the value to the default, is it possible to have it declared on the table right from the start?
Would it be possible to have something like the Identity, where i don't have to pass the value into the insert?
egx: insert into Direct values(2)
and the table would become
id | item
1 | 2
the id = 1, would be the deafult value
thanks in advance!
you can create constraints at time of table creation or later.
create table
#test
(
id int identity(1,2),
name char(255) default newid(),
code int default 2
)
---if a table contains all default values,you can insert like below
insert into #test
default values
updated as per comments:
create table
#test1
(
id int identity(1,2),
name char(255) default newid(),
code int default 2,
notdf int
)
---if a table contains one default value and rest all are default
insert into #test1(notdf)
select 2
Further if you want to add a default value after table creation you can do it like below
create table
tt1
(
valuue int,
address char(2) not null
)
insert into tt1
select 1,'a'
ALTER TABLE tt1 ADD CONSTRAINT test1 DEFAULT null FOR address;
Use default. You can change an existing column by doing:
ALTER TABLE t ADD CONSTRAINT df_t_column DEFAULT 1 for id;
An identity is trickier. I would suggest copying the data over to a temporary table, dropping the table, creating it with an identity column and reloading the data.

SQL unique constraint on either of 2 columns

I have a table in SQL that I would like to have a unique constraint so that any of two values cannot already exist.
for example if I have 2 columns, I would like it to not insert if the value in column B does not exist in column A or column B.
Is this possible and if so how is it done?
example:
Column A | Column B
--------------------
4 | 6
I would want any object that tries to insert 4 or 6 not to be allowed into the table
Trigger with ROLLBACK TRANSACTION is the way to go.
create trigger dbo.something after insert as
begin
if exists ( select * from inserted where ...check here if your data already exists... )
begin
rollback transaction
raiserror ('some message', 16, 1)
end
end
You can create a function which takes in these values & create a check constraint on it (referencing your functions return values) to your table.
create table t11 (Code int, code2 int)
create function fnCheckValues (#Val1 int, #Val2 int)
Returns int /*YOu can write a better implementation*/
as
Begin
DECLARE #CntRow int
IF(#Val1 IS NULL OR #Val2 IS NULL) RETURN 0
select #CntRow = count(*) from t11
where Code in (#Val1,#Val2 ) or Code2 in (#Val1,#Val2 )
RETURN #CntRow
End
GO
alter table t11 Add constraint CK_123 check ([dbo].[fnCheckValues]([Code],[code2])<=(1))
When one want to enforce a multi-row constraint that is not offered by the database engine, the obvious solution is use of a trigger or stored procedure. This often does not work because the database isolates the transactions the triggers and stored procedures run in, allowing violations in the presense of concurrency.
Instead turn the constraint into something that the database engine will enforce.
CREATE TABLE dbo.T (A INT, B INT)
GO
CREATE TABLE dbo.T_Constraint_Helper (ColumnName sysname PRIMARY KEY)
INSERT INTO dbo.T_Constraint_Helper (ColumnName)
VALUES ('A'), ('B')
GO
CREATE VIEW T_Constraint_VW
WITH SCHEMABINDING AS
SELECT CASE CH.ColumnName WHEN 'A' THEN T.A ELSE T.B END AS Value
FROM dbo.T
CROSS JOIN dbo.T_Constraint_Helper CH
GO
CREATE UNIQUE CLUSTERED INDEX FunnyConstraint_VW_UK ON dbo.T_Constraint_VW (Value)
GO
INSERT INTO T VALUES (1, 2)
-- works
INSERT INTO T VALUES (2, 3)
-- Msg 2601, Level 14, State 1, Line 1
-- Cannot insert duplicate key row in object 'dbo.T_Constraint_VW' with unique index 'T_Constraint_VW_UK'. The duplicate key value is (2).
INSERT INTO T VALUES (4, 4)
-- Msg 2601, Level 14, State 1, Line 1
-- Cannot insert duplicate key row in object 'dbo.T_Constraint_VW' with unique index 'T_Constraint_VW_UK'. The duplicate key value is (4).
INSERT INTO T VALUES (5, 6)
-- works