I want to make a date constraint in my table (I use sql server). I want to make sure that the date in one of my columns is later than the current date and time (I know it sounds weird, but it's an assignment so I have no choice). I tried to do it this way:
ALTER TABLE sales ADD CONSTRAINT d CHECK (Date > CURRENT_TIMESTAMP);
but later when inserting DEFAULT into date column I get the following error:
The INSERT statement conflicted with the CHECK constraint "d".
The conflict occurred in database "Newsagents", table "dbo.Sales", column 'Date'.
This is the said table:
CREATE TABLE Sales (
ID INT IDENTITY(1,1) NOT NULL ,
ClientID INT REFERENCES Client(ClientID),
ProductNumber CHAR(10) REFERENCES Product(ProductNumber),
Quantity INT NOT NULL,
Price FLOAT NOT NULL ,
Date TIMESTAMP NOT NULL,
PRIMARY KEY ( ID )
and this how I insert my data into Sales column and get the constraint conflict:
DECLARE #counter INT
DECLARE #quantity int
DECLARE #prodNum varchar(20)
SET #counter = 0
WHILE #counter < 10
BEGIN
SET #quantity = (select FLOOR(RAND()*100))
SET #prodNum = (select TOP 1 ProductNumber from Product Order by NEWID())
insert into Sales (ClientID, ProductNumber, Quantity, Price, Date )
values(
(select TOP 1 ClientID from Client Order by NEWID()),
(select #prodNum),
(select #quantity),
((select #quantity)*(select TOP 1 Price from Product where ProductNumber = #prodNum)),
DEFAULT
)
SET #counter = #counter + 1
END
Is there a different way to do this? Or am I doing something wrong?
ALTER TABLE sales ADD CONSTRAINT d CHECK (Date > GETDATE());
change the Date column to datetime
Related
There are 3 tables: Event, Booking, and Booking_Day.
The idea is that one can book separate days of the event.
I would like to put a constraint on Booking_Day so that Day has to be within Date_Start and Date_End range of the corresponding Event. I decided to use a function that will do this
create table Event
(
Event_ID int identity
constraint Event_pk
primary key nonclustered,
Date_Start date not null,
Date_End date
)
create table Booking
(
Booking_ID int identity
constraint Booking_pk
primary key nonclustered,
Event_ID int not null
constraint Booking_Event_Event_ID_fk
references Event
)
create table Booking_Day
(
Day date not null,
Booking_ID int not null
constraint Booking_Day_Booking_Booking_ID_fk
references Booking,
constraint Booking_Day_pk
primary key nonclustered (Day, Booking_ID)
)
And the function:
CREATE FUNCTION check_if_in_range (
#Event_id int,
#Day DATE
) RETURNS int
BEGIN
declare #result TABLE (Day DATE,Booking_ID INT,Event_ID INT,Date_start DATE, Data_end DATE)
INSERT into #result
SELECT Booking_Day.Day, Booking.Event_ID, Event.Date_Start, Event.Date_End
FROM ((Booking_Day INNER JOIN Booking on Booking_Day.Booking_ID = B.Booking_ID )
INNER JOIN Event on Event.Event_ID = Booking.Event_ID) WHERE Booking_Day.Day = #Day AND B.Event_ID = #Event_id
return ((#Day >= #result.Date_start) AND (#Day <= #result.Data_end))
END
Because of the primary key constraint on Booking_day table, the above should return only one row.
When trying to add function do database I get “[[S0001][137] Must declare the scalar variable “#result".
How do I deal with it? Is my approach entirely wrong and I don’t need a table within the function for this?
I don't understand why you would be using a table variable for this. You cannot just refer to a table unless you specify a FROM clause -- you are confusing table variables and scalar variables.
But why bother with variables at all?
IF (EXISTS (SELECT 1
FROM Booking_Day bd INNER JOIN
Booking b
ON bd.Booking_ID = B.Booking_ID INNER JOIN
Event e
ON e.Event_ID = b.Event_ID
WHERE b.Day = #Day AND
b.Event_ID = #Event_id AND
#Day >= e.Date_Start AND
#Day <= e.Data_end
)
)
BEGIN
return 1
END;
return 0
I have a staff table and then staff_schedule table which references staff id. I want to limit the number of schedules created for a single staff to 8 records per week. How can I do that?
Thanks in advance.
What I have so far..
CREATE TABLE Staff
(
staffID int,
fullName varchar(100) NOT NULL,
s_category varchar(25),
s_email varchar(50),
s_contactNo int,
speciality varchar(100),
qualifications varchar(250),
pre_employment varchar(200),
salary numeric(8,2),
staff_gender char(1),
CONSTRAINT PK_Staff PRIMARY KEY (staffID),
CONSTRAINT CHK_StaffGender CHECK (staff_gender='M' OR staff_gender='F'),
CONSTRAINT CHK_FullName CHECK (fullName NOT LIKE '%0%' AND fullName NOT LIKE '%1%' AND fullName NOT LIKE '%2%' AND fullName NOT LIKE '%3%' AND fullName NOT LIKE '%4%' AND fullName NOT LIKE '%5%' AND fullName NOT LIKE '%6%' AND fullName NOT LIKE '%7%' AND fullName NOT LIKE '%8%' AND fullName NOT LIKE '%9%'),
CONSTRAINT CHK_SALARY CHECK (salary>0 AND salary<=150000)
);
CREATE TABLE Staff_Allocation
(
allocationId int,
staff_Id int,
branch_Id int,
staff_start_date DateTime,
staff_end_date DateTime,
CONSTRAINT PK_Staff_Allocation PRIMARY KEY (allocationId),
CONSTRAINT FK_Staff_Allocation_Staff FOREIGN KEY (staff_Id) REFERENCES Staff(staffID),
CONSTRAINT FK_Staff_Allocation_Branch FOREIGN KEY (branch_Id) REFERENCES Branch(branchID),
CONSTRAINT CHK_StaffAllocationRotaDaily CHECK (DATEDIFF(hh, staff_start_date, staff_end_date) <=6)
);
A trigger is definitely the way to go for enforcing this constraint. TABLE constraints cannot enforce requirements that span multiple rows.
There are a number of things to test for your requirements
Single row inserted
Multiple rows attempted to be inserted across different staff_ids
Multiple rows attempted to be inserted for same staff_id
Below trigger will satisfy these. I left the date range check as empty as I'm not sure the exact criteria. The inner query collects all the rows from rows being inserted along with all rows already present for staff_ids in the current insert. The outer query aggregates by staff_id after applying the date range criteria. Finally the IF EXISTS check is used to throw an error.
CREATE TRIGGER insert_checkScheduleCount
ON Staff_Allocation AS
BEGIN
IF EXISTS (
SELECT * FROM
(
SELECT staff_id, staff_start_date, staff_end_date
FROM
inserted // relevant columns from the current set of rows
// attempting to be inserted
UNION ALL
SELECT staff_id, staff_start_date, staff_end_date
FROM
Staff_Allocation
WHERE
staff_id IN (SELECT staff_id FROM inserted)
// rows already in the table for staff being touched in this insert
) AS staffRows
WHERE
<apply you criteria on staff_start_date and staff_end_date>
GROUP BY
staff_id
HAVING COUNT(*) > 8
)
BEGIN
RAISERROR(N'More than 8 rows', 16, -1)
END
END
You can create an instead of trigger:
Sample Query for your reference:
CREATE TRIGGER INSTEADOF_INS
ON Staff_Allocation
INSTEAD OF INSERT AS
BEGIN
DECLARE #CNT_ALLOC TINYINT
DECLARE #CNT_ALLOC_TAB TINYINT
DECLARE #ST_DATE DATE
DECLARE #END_DATE DATE
SELECT #ST_DATE=DATEADD(dd, -(DATEPART(dw, GETDATE())-1), GETDATE())
SELECT #END_DATE=DATEADD(dd, 7-(DATEPART(dw, GETDATE())), GETDATE())
SELECT #CNT_ALLOC=COUNT(*)
FROM Staff_Allocation S INNER JOIN INSERTED I
ON S.staff_Id = I.staff_Id AND S.staff_start_date>#ST_DATE AND S.staff_end_date<#END_DATE
IF (#CNT_ALLOC >8 )
BEGIN
RAISERROR (N'8 rows Per Week',
16, 1)
RETURN
END
INSERT INTO Staff_Allocation (allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date)
SELECT allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date
FROM inserted
END
GO
If you do not want to use a trigger you could just use an IF statement
DECLARE #TableCount INT
SELECT #TableCount = COUNT(*)
FROM YourTable
WHERE StartDate BETWEEN GETDATE()-7 AND GETDATE()
AND EndDate BETWEEN GETDATE()-7 AND GETDATE()
IF #TableCount < 8
INSERT...
ELSE
RAISERROR...
RETURN
I created a function that will serves as primary key for my table
CREATE FUNCTION dbo.NewCustomerPK()
RETURNS VARCHAR (10)
AS
BEGIN
DECLARE #LastCustID VARCHAR(10)
DECLARE #newID INT
DECLARE #charID CHAR(10)
SELECT
#LastCustID = MAX(CustID)
FROM
dbo.TestCust
IF (#LastCustID IS NULL)
BEGIN
SET #LastCustID = 'CUST000001'
END
ELSE
BEGIN
SET #newID = RIGHT(#LastCustID, 6) + 1
SET #charID = 'CUST' + RIGHT(('0000000' + CONVERT(VARCHAR(6), #newID)), 6)
SET #LastCustID = #charID
END
RETURN #LastCustID
END
CREATE TABLE dbo.TestCust
(
CustID VARCHAR(10) PRIMARY KEY NOT NULL DEFAULT dbo.NewCustomerPK(),
Name VARCHAR(50)
)
And tried to insert a test data
DECLARE #Counter INT = 1,
#Stopper INT = 500000
WHILE(#Counter <= #Stopper)
BEGIN
INSERT INTO dbo.TestCust(NAME)
VALUES('test'+CONVERT(VARCHAR(6), #Counter))
SET #Counter = #Counter + 1
END
It works fine but when I try a parallel run(Running the loop data insertion in the new window) it cause a Primary Constraint Violation Error
Another way to do this is to use an identity column in combination with a calculated column.
create table dbo.TestCust
(
ID int identity not null,
CustID as isnull('CUST'+right('0000000' + convert(varchar(6), ID), 6), ''),
Name varchar(50),
constraint PK_TestCust_CustID primary key clustered (CustID)
);
The isnull around the calculation of CustID is there to make sure the column will never have NULL values and that makes it possible to use CustID as a primary key.
Not sure if it is possible to fix your current design. Perhaps using isolation level serializable when adding new rows would do the trick.
CREATE TRIGGER tr_Group ON TargetTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO dbo.targetTable
SELECT dbo.NewCustomerPK(),<other fields>
FROM INSERTED
END
I have five tables named guest, orders, order_details, food, and employees. Their characteristics are as follows:
create table guest
(
guest_id int primary key identity(1,1),
guest_fname nvarchar(50),
national_id int,
mobile nvarchar(50),
nationality_id int
constraint c15
foreign key(nationality_id) references nationality(nationality_id)
)
create table employees
(
emp_id int primary key ,
emp_fname nvarchar(50),
constraint c1
--foreign key(mgr_id) references employees(emp_id),
foreign key(super_id) references employees(emp_id),
)
go
create table food
(
food_id int primary key identity(1,1),
food_name nvarchar(50),
food_price money,
food_desc nvarchar(200),
cat_id int
constraint c5
foreign key(cat_id) references food_categories(cat_id)
)
go
create table orders
(
order_id int primary key identity(1,1),
order_date datetime,
total float,
guest_id int,
emp_id int,
is_paid bit
constraint c6
foreign key(guest_id) references guest(guest_id),
foreign key(emp_id) references employees(emp_id)
)
go
create table order_details
(
order_id int,
food_id int,
price money,
qty int
constraint c7
primary key(order_id,food_id),
foreign key(order_id) references orders(order_id),
foreign key(food_id) references food(food_id)
)
Go
There is a 1-to-M relationship between orders and order_details.
I want to insert a single row into orders and multiple rows into order_details via a stored procedure. Please help me!
Please give me a stored procedure and explain its algorithm to point me in the right direction.
As #thepirat000 points out, you can use a TVP for the order details/line items since you are using SQL Server 2012. Pinal Dave has a great article on TVPs that you may find helpful. Combine the TVP with scalar parameters for order properties.
For example:
-- NOTE: *Not* accounting for order_id in this case - i.e. a new order where
-- order_id is an identity value.
CREATE TYPE [dbo].[order_details_type] AS TABLE
(
food_id int,
price money,
qty int
)
GO
CREATE PROCEDURE [dbo].[usp_insert_order]
(
#order_id int output,
#order_date datetime = NULL,
#total float,
#guest_id int,
#emp_id int,
#is_paid bit = 0,
#details order_details_type readonly
)
AS
BEGIN
SET NOCOUNT ON;
-- TODO: Wrap the inserts into dbo.orders and dbo.order_details in a
-- transaction as desired.
-- TODO: Check that #order_id does not already exist in dbo.orders and
-- dbo.order_details etc.
-- FORNOW: Proceed optimistically. :}
IF #order_date IS NULL
SET #order_date = GETDATE();
INSERT INTO dbo.orders (order_date, total, guest_id, emp_id, is_paid)
VALUES (#order_date, #total, #guest_id, #emp_id, #is_paid);
SET #order_id = SCOPE_IDENTITY();
INSERT INTO dbo.order_details (order_id, food_id, price, qty)
SELECT #order_id, food_id, price, qty
FROM #details;
END
GO
Executing such a stored procedure is pretty straighforward, as you prepare the TVP argument much like you would a table variable:
/*
* Order up!
*/
/* data I added for a quick check
SELECT TOP 1 * FROM dbo.employees;
--emp_id emp_fname
--1 Wendy
SELECT TOP 1 * FROM dbo.guest;
--guest_id guest_fname national_id mobile
--1 Joe 1 619-555-1212
SELECT TOP 2 * FROM dbo.food;
--food_id food_name food_price food_desc
--1 Onion Rings 5.00 Beer-battered onion rings.
--2 Kobe Burger 10.00 Kobe-beef burger.
*/
-- Prepare the order.
DECLARE #order_id int; -- to get on order insert
DECLARE #now datetime = getdate();
DECLARE #order_details order_details_type;
INSERT INTO #order_details
SELECT 1, 5, 1
UNION
SELECT 2, 10, 2;
-- Insert the order.
EXEC dbo.usp_insert_order
#order_id = #order_id OUTPUT,
#order_date=#now,
#total=25,
#guest_id=1,
#emp_id=1,
#is_paid=0,
#details=#order_details;
/*
* Check the order.
*/
SELECT * FROM dbo.orders WHERE order_id = #order_id;
--order_id order_date total guest_id emp_id is_paid
--1 2014-03-13 21:44:45.400 25 1 1 0
SELECT * FROM dbo.order_details WHERE order_id = #order_id;
--order_id food_id price qty
--1 1 5.00 1
--1 2 10.00 2
Another option that pre-dates TVPs may interest you too - using an XML parameter for the order details/line items and scalar parameters for order properties. Related resources and examples abound.
A variation of the XML-param approach that is to pass XML for the order and its details/line items; but I think this is overkill personally.
you need 2 store procs, 1 for order and 1 for Order_details, in the last one add update statement for food table qty. you do not want to make 1 sproc for all
I have a program that captures orders into two SQL tables. One is a header table with order info that has OrderID as auto increment PK. The other is a details table which captures vendor, part_number, and quantity data.
I have an after insert trigger on the header table which is suppose to update a table in another database with order info and a total quantity of parts in that order.
Everything works fine except when the trigger goes to calculate the total number of parts in the details table. What is happening is the trigger fires before the data is written to the details table and always returns NULLs for the total quantity of parts. What's the best workaround for this? Am I using a wrong trigger?
OrderHeader Table
ID int
TransCode int
CustID int
StoreID int
Date datetime
OrderDetail
OrderID int
MFG nchar(3)
PartNumber nchar(35)
Qty int
ALTER TRIGGER [dbo].[trig_Update_PRIME]
on [Cleansweep].[dbo].[OrderHeader]
after insert
AS
BEGIN
declare #ID as int
declare #DateInput as date
declare #CustID as int
declare #transcode as int
declare #storeID as int
declare #TotItems as int
select #ID = (select ID from inserted)
select #DateInput = (select date from inserted)
select #CustID = (select CustID from inserted)
select #transcode = (select TransCode from inserted)
select #storeID = (select StoreID from inserted)
set #TotItems = ( select SUM(qty) FROM [Cleansweep].[dbo].[OrderDetail] where OrderID = #ID )
if #transcode = 0
begin
insert into PRIME.dbo.PRIME_RepCalls (Customer, Cores)
values (#CustID, #TotItems)
end
if #transcode = 1
begin
insert into PRIME.dbo.PRIME_RepCalls (Customer, NewReturns)
values (#CustID, #TotItems))
end
END
I belive that there two insert process in your DB - first one is when insert into Header table, and the second when is insert to Details table.
If you do insert Header first - than your trigger works before details of your documents were inserted in Db. That's why SUM can return NULL