What is the code for a T-SQL check constraint? - tsql

I want to add a check clause to this table so that:
IF a transaction enters a value for the diastolicBloodPressure
THEN the transaction must also insert a value for the systolicBloodPressure.
CREATE TABLE "Constraint-BloodPressure".Patient
(
patientNr int NOT NULL,
diastolicBloodPressure smallint,
systolicBloodPressure smallint,
CONSTRAINT Patient_PK PRIMARY KEY(patientNr)
)
Is this correct?
CONSTRAINT CHK_BloodPressure CHECK (diastolicBloodPressure >0 AND systolicBloodPressure >0 )

You did not specify, whether your table would accept NULL-values and how you would deal with them.
Due to the kind of data, my suggestion was this:
CREATE TABLE dbo.TestCheck
(
patientNr INT NOT NULL CONSTRAINT PK_TestCheck PRIMARY KEY
,diastolicBloodPressure smallint NOT NULL
,systolicBloodPressure smallint NOT NULL
,CONSTRAINT chk_TestCheck_MustEnterBoth CHECK(diastolicBloodPressure BETWEEN 0 AND 250 AND systolicBloodPressure BETWEEN 0 AND 250)
);
--The table will not accept NULL-values and it will force the entered values to keep within realistic borders.
--Instead of smallint you might use tinyint, which is bordered between 0 and 255 by definition.
--Try the following:
INSERT INTO dbo.TestCheck VALUES(1,100,110);
GO
INSERT INTO dbo.TestCheck VALUES(2,100,NULL);
GO
INSERT INTO dbo.TestCheck VALUES(3,NULL,NULL);
GO
INSERT INTO dbo.TestCheck VALUES(4,-1,1000);
GO
SELECT * FROM dbo.TestCheck;
--Only the first insert will succeed, all the other attempts fill fail
--Clean-Up Carefull with real data...
GO
DROP TABLE dbo.TestCheck;

Not sure what database system you are using. On SQL Server 2014 I am require specify "NULL" or "NOT NULL". So my create table statement with the check constraint and insert statements looks like this...
IF OBJECT_ID('tempdb.dbo.#Patient', 'U') IS NOT NULL
DROP TABLE #Patient;
CREATE TABLE #Patient
(
patientNr INT NOT NULL
, diastolicBloodPressure SMALLINT NULL
, systolicBloodPressure SMALLINT NULL
, CONSTRAINT Patient_PK
PRIMARY KEY (patientNr)
, CHECK ((
diastolicBloodPressure IS NOT NULL
AND systolicBloodPressure IS NOT NULL
)
OR (
diastolicBloodPressure IS NULL
AND systolicBloodPressure IS NULL
)
)
);
INSERT INTO #Patient VALUES (1, NULL, NULL);
INSERT INTO #Patient VALUES (2, 120, NULL);
INSERT INTO #Patient VALUES (3, NULL, 80);
INSERT INTO #Patient VALUES (4, 120, 80);
The first and last insert statements work and the middle two fail.

If you want to force both fields to have a value, then you need to check for null values. If you don't, then your current check will allow entry of just the patientNbr or just one value.
CREATE TABLE dbo.Patient
(
patientNr int NOT NULL,
diastolicBloodPressure smallint,
systolicBloodPressure smallint,
CONSTRAINT Patient_PK PRIMARY KEY(patientNr),
CONSTRAINT CHK_BloodPressure CHECK (ISNULL(diastolicBloodPressure, 0) > 0 AND ISNULL(systolicBloodPressure, 0) > 0 )
);
GO
-- Valid
INSERT INTO dbo.Patient VALUES (1, 120, 80);
GO
-- Invalid
INSERT INTO dbo.Patient VALUES (2, 120, 0);
GO
-- Invalid
INSERT INTO dbo.Patient VALUES (3, 0, 80);
GO
-- Invalid
INSERT INTO dbo.Patient (patientNr, diastolicBloodPressure) VALUES (4, 120);
GO
-- Invalid
INSERT INTO dbo.Patient (patientNr) VALUES (5);
GO

Related

Postgres: insert multiple rows on conflict update not working

I am trying to insert multiple rows into a table, and, in case of conflict, simply update them. What am I doing wrong?
insert into segments(id, departure_hour)
values
(1153, 2),
(1156, 4),
(1154, 2)
on conflict do update set
departure_hour = c.departure_hour
from (values
(1153, 2),
(1156, 4),
(1154, 2))
as c(id, departure_hour)
where c.id = segments.id
As requested, here is my table definition:
CREATE TABLE segments (
id SERIAL PRIMARY KEY,
route_id integer NOT NULL REFERENCES routes(id),
origin_id integer NOT NULL REFERENCES stops(id),
destination_id integer NOT NULL REFERENCES stops(id),
price integer DEFAULT 0,
departure_day integer NOT NULL,
departure_hour integer NOT NULL,
departure_minute integer NOT NULL,
arrival_day integer NOT NULL,
arrival_hour integer NOT NULL,
arrival_minute integer NOT NULL,
hidden boolean NOT NULL DEFAULT false,
deleted boolean NOT NULL DEFAULT false,
CONSTRAINT unique_origin_destination_per_route UNIQUE (route_id, origin_id, destination_id)
);
And here is my error:
ERROR: syntax error at or near "from"
LINE 1: ...pdate set departure_hour = c.departure_hour from (valu...
You don't need the from part in order to be able to reference the values to update.
insert into segments(id, departure_hour)
values
(1153, 2),
(1156, 4),
(1154, 2)
on conflict do update set
departure_hour = excluded.departure_hour;

Default constraint not being enforced

Given the following table definition:
CREATE TABLE ControlledSubstances.NationalDrugCode
(
NationalDrugCodeID INT NOT NULL
,NationalDrugCode VARCHAR(20) NOT NULL
,Product VARCHAR(100)
,Ingredient VARCHAR(500)
,ClassID VARCHAR(50)
,Class VARCHAR(50)
,DrugEnforcementAgencyClassID VARCHAR(50)
,DrugEnforcementAgencyClass VARCHAR(50)
,GenericDrug VARCHAR(50)
,Form VARCHAR(50)
,Drug VARCHAR(50)
,StrengthPerUnit NUMERIC(6,2)
,UnitOfMeasure VARCHAR(50)
,ConversionFactor NUMERIC(4,2)
,LongOrShortActing VARCHAR(50)
,IsPreventionForStates BIT NOT NULL
)
;
ALTER TABLE ControlledSubstances.NationalDrugCode
ADD CONSTRAINT PK_ControlledSubstances_NationalDrugCode PRIMARY KEY (NationalDrugCodeID)
,CONSTRAINT DF_ControlledSubstances_NationalDrugCode_IsPreventionForStates DEFAULT 0 FOR IsPreventionForStates
;
CREATE UNIQUE INDEX UQ_ControlledSubstances_NationalDrugCode_NationalDrugCode ON ControlledSubstances.NationalDrugCode (NationalDrugCode);
Why would I be receiving an error on insert for the column I defined as NOT NULL and created a default constraint of 0? I know I can handle the logic in the insert statement to not pass in NULL values, but I use this logic in multiple tables and have never gotten an error before. The error I receive is:
Cannot insert the value NULL into column 'IsPreventionForStates', table 'Staging.ControlledSubstances.NationalDrugCode'; column does not allow nulls. INSERT fails.
This will happen if you explicitly provide NULL as its value. The default constraint only kicks in when you don't supply a value at all, or when you use the DEFAULT keyword:
For example, if NationalDrugCodeID and IsPreventionForStates were your only two columns in the table (for illustration), this will fail:
INSERT INTO NationalDrugCode(NationalDrugCodeID, IsPreventionForStates) VALUES (5, NULL);
But either of these would work:
INSERT INTO NationalDrugCode(NationalDrugCodeID) VALUES (5);
INSERT INTO NationalDrugCode(NationalDrugCodeID, IsPreventionForStates) VALUES (5, DEFAULT);
In the edge case where you need ALL columns to have default values inserted, you can use:
INSERT INTO NationalDrugCode DEFAULT VALUES;

sql drop primary key from temp table

I want to create e temp table using select into syntax. Like:
select top 0 * into #AffectedRecord from MyTable
Mytable has a primary key. When I insert record using merge into syntax primary key be a problem. How could I drop pk constraint from temp table
The "SELECT TOP (0) INTO.." trick is clever but my recommendation is to script out the table yourself for reasons just like this. SELECT INTO when you're actually bringing in data, on the other hand, is often faster than creating the table and doing the insert. Especially on 2014+ systems.
The existence of a primary key has nothing to do with your problem. Key Constraints and indexes don't get created when using SELECT INTO from another table, the data type and NULLability does. Consider the following code and note my comments:
USE tempdb -- a good place for testing on non-prod servers.
GO
IF OBJECT_ID('dbo.t1') IS NOT NULL DROP TABLE dbo.t1;
IF OBJECT_ID('dbo.t2') IS NOT NULL DROP TABLE dbo.t2;
GO
CREATE TABLE dbo.t1
(
id int identity primary key clustered,
col1 varchar(10) NOT NULL,
col2 int NULL
);
GO
INSERT dbo.t1(col1) VALUES ('a'),('b');
SELECT TOP (0)
id, -- this create the column including the identity but NOT the primary key
CAST(id AS int) AS id2, -- this will create the column but it will be nullable. No identity
ISNULL(CAST(id AS int),0) AS id3, -- this this create the column and make it nullable. No identity.
col1,
col2
INTO dbo.t2
FROM t1;
Here's the (cleaned up for brevity) DDL for the new table I created:
-- New table
CREATE TABLE dbo.t2
(
id int IDENTITY(1,1) NOT NULL,
id2 int NULL,
id3 int NOT NULL,
col1 varchar(10) NOT NULL,
col2 int NULL
);
Notice that the primary key is gone. When I brought in id as-is it kept the identity. Casting the id column as an int (even though it already is an int) is how I got rid of the identity insert. Adding an ISNULL is how to make a column nullable.
By default, identity insert is set to off here to this query will fail:
INSERT dbo.t2 (id, id3, col1) VALUES (1, 1, 'x');
Msg 544, Level 16, State 1, Line 39
Cannot insert explicit value for identity column in table 't2' when IDENTITY_INSERT is set to OFF.
Setting identity insert on will fix the problem:
SET IDENTITY_INSERT dbo.t2 ON;
INSERT dbo.t2 (id, id3, col1) VALUES (1, 1, 'x');
But now you MUST provide a value for that column. Note the error here:
INSERT dbo.t2 (id3, col1) VALUES (1, 'x');
Msg 545, Level 16, State 1, Line 51
Explicit value must be specified for identity column in table 't2' either when IDENTITY_INSERT is set to ON
Hopefully this helps.
On a side-note: this is a good way to play around with and understand how select insert works. I used a perm table because it's easier to find.

Use a sequence to populate multiple columns with successive values

How can I use default constraints, triggers, or some other mechanism to automatically insert multiple successive values from a sequence into multiple columns on the same row of a table?
A standard use of a sequence in SQL Server is to combine it with default constraints on multiple tables to essentially get a cross-table identity. See for example the section "C. Using a Sequence Number in Multiple Tables" in the Microsoft documentation article "Sequence Numbers".
This works great if you only want to get a single value from the sequence for each row inserted. But sometimes I want to get multiple successive values. So theoretically I would create a sequence and table like this:
CREATE SEQUENCE DocumentationIDs;
CREATE TABLE Product
(
ProductID BIGINT NOT NULL IDENTITY(1, 1) PRIMARY KEY
, ProductName NVARCHAR(100) NOT NULL
, MarketingDocumentationID BIGINT NOT NULL DEFAULT ( NEXT VALUE FOR DocumentationIDs )
, TechnicalDocumentationID BIGINT NOT NULL DEFAULT ( NEXT VALUE FOR DocumentationIDs )
, InternalDocumentationID BIGINT NOT NULL DEFAULT ( NEXT VALUE FOR DocumentationIDs )
);
Unfortunately this will insert the same value in all three columns. This is by design:
If there are multiple instances of the NEXT VALUE FOR function specifying the same sequence generator within a single Transact-SQL statement, all those instances return the same value for a given row processed by that Transact-SQL statement. This behavior is consistent with the ANSI standard.
Increment by hack
The only suggestion I could find online was to use a hack where you have the sequence increment by the number of columns you need to insert (three in my contrived example) and manually add to the NEXT VALUE FOR function in the default constraint:
CREATE SEQUENCE DocumentationIDs START WITH 1 INCREMENT BY 3;
CREATE TABLE Product
(
ProductID BIGINT NOT NULL IDENTITY(1, 1) PRIMARY KEY
, ProductName NVARCHAR(100) NOT NULL
, MarketingDocumentationID BIGINT NOT NULL DEFAULT ( NEXT VALUE FOR DocumentationIDs )
, TechnicalDocumentationID BIGINT NOT NULL DEFAULT ( ( NEXT VALUE FOR DocumentationIDs ) + 1 )
, InternalDocumentationID BIGINT NOT NULL DEFAULT ( ( NEXT VALUE FOR DocumentationIDs ) + 2 )
)
This does not work for me because not all tables using my sequence require the same number of values.
One possible way using AFTER INSERT trigger is following.
Table definition need to be changed slighlty (DocumentationID columns should be defaulted to 0, or allowed to be nullable):
CREATE TABLE Product
(
ProductID BIGINT NOT NULL IDENTITY(1, 1)
, ProductName NVARCHAR(100) NOT NULL
, MarketingDocumentationID BIGINT NOT NULL CONSTRAINT DF_Product_1 DEFAULT (0)
, TechnicalDocumentationID BIGINT NOT NULL CONSTRAINT DF_Product_2 DEFAULT (0)
, InternalDocumentationID BIGINT NOT NULL CONSTRAINT DF_Product_3 DEFAULT (0)
, CONSTRAINT PK_Product PRIMARY KEY (ProductID)
);
And the trigger doing the job is following:
CREATE TRIGGER Product_AfterInsert ON Product
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM INSERTED)
RETURN;
CREATE TABLE #DocIDs
(
ProductID BIGINT NOT NULL
, Num INT NOT NULL
, DocID BIGINT NOT NULL
, PRIMARY KEY (ProductID, Num)
);
INSERT INTO #DocIDs (ProductID, Num, DocID)
SELECT
i.ProductID
, r.n
, NEXT VALUE FOR DocumentationIDs OVER (ORDER BY i.ProductID, r.n)
FROM INSERTED i
CROSS APPLY (VALUES (1), (2), (3)) r(n)
;
WITH Docs (ProductID, MarketingDocID, TechnicalDocID, InternalDocID)
AS (
SELECT ProductID, [1], [2], [3]
FROM #DocIDs d
PIVOT (MAX(DocID) FOR Num IN ([1], [2], [3])) pvt
)
UPDATE p
SET
p.MarketingDocumentationID = d.MarketingDocID
, p.TechnicalDocumentationID = d.TechnicalDocID
, p.InternalDocumentationID = d.InternalDocID
FROM Product p
JOIN Docs d ON d.ProductID = p.ProductID
;
END

Postgres before insert trigger using sequence from another table

Using Postgres, what I would like to achieve is to be able to have many different instrument types, with corresponding [TYPE].instrument tables, which all have a unique ID in the table, but also reference a unique ID in the instrument.master table. I have the following:
create schema instrument
CREATE TABLE instrument.type (
id smallserial NOT NULL,
name text not null,
code text not null,
CONSTRAINT pk_instrument_type PRIMARY KEY (id)
);
ALTER TABLE instrument.type ADD CONSTRAINT unq_instrument_type_code UNIQUE(code);
ALTER TABLE instrument.type ADD CONSTRAINT unq_instrument_type_name UNIQUE(name);
insert into instrument.type (name, code) values ('futures', 'f');
CREATE TABLE instrument.master (
id serial NOT NULL,
type smallint not null references instrument.type (id),
timestamp timestamp with time zone not null,
CONSTRAINT pk_instrument_master PRIMARY KEY (id)
);
CREATE TABLE futures.definition (
id smallserial NOT NULL,
code text not null,
CONSTRAINT pk_futures_definition PRIMARY KEY (id)
);
ALTER TABLE futures.definition ADD CONSTRAINT unq_futures_definition_code UNIQUE(code);
insert into futures.definition (code) values ('ED');
CREATE TABLE futures.instrument (
id smallserial NOT NULL,
master serial not null references instrument.master (id),
definition smallint not null references futures.definition (id),
month smallint not null,
year smallint not null,
CONSTRAINT pk_futures_instrument PRIMARY KEY (id),
check (month >= 1),
check (month <= 12),
check (year >= 1900)
);
ALTER TABLE futures.instrument ADD CONSTRAINT unq_futures_instrument UNIQUE(definition, month, year);
CREATE OR REPLACE FUNCTION trigger_master_futures()
RETURNS trigger AS
$BODY$
BEGIN
insert into instrument.master (type, timestamp)
select id, current_timestamp from instrument.type where code = 'f';
NEW.master := currval('instrument.master_id_seq');
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
create trigger trg_futures_instrument before insert on futures.instrument
for each row
execute procedure trigger_master_futures();
I then test with:
insert into futures.instrument (definition, month, year)
select id, 3, 2015 from futures.definition where code = 'ED';
Everything works almost as I would like it to. The only issue is that somehow, instrument.master.id ends up being one more than futures.instrument.master. I am not sure what I need to do to achieve the behavior I want, which is that whenever an entry is inserted into futures.instrument, an entry should be inserted into instrument.master, and the id entry of the latter should be inserted into the master entry of the former. I actually think it should have failed since the foreign key relationship is violated somehow.
As it turns out, everything was correct. The issue was that in futures.instrument, the type of the master column is serial, and it should have been int.