I have a problem. I am creating a trigger which will prompt the user to not create a purchase order with a higher cost to their selling cost. I have declared a column on my script but its still showing this error:
Msg 207, Level 16, State 1, Procedure _trgZSCheckPrice, Line 31 [Batch Start Line 7]
Invalid column name 'fExclPrice'
The two columns are #Check and #Excl
I have attached my code below any help is advisable:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER _trgZSCheckPrice
ON [dbo].[_btblInvoiceLines]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#Valid int,
#ValidPO int,
#DocType int,
#DocState int,
#Check int,
#Excl float,
#POPrice float
SELECT
#Check = ubIICheck,
#Excl = fExclPrice,
#POPrice = fUnitPriceExcl
FROM
INSERTED
SELECT
#Excl = fExclPrice,
#Check = ubIICheck
FROM
stkitem A
INNER JOIN
_etblPriceListPrices B ON A.StockLink = B.iStockID
SELECT
#POPrice = fUnitPriceExcl
FROM
_btblInvoiceLines C
LEFT JOIN
InvNum D ON C.iInvoiceID = D.AutoIndex
BEGIN
IF (#DocType = 5 AND #DocState <> 7) AND #CHECK = 1
BEGIN
IF #Excl > #POPrice
BEGIN
RAISERROR ('Message from Management:
You are not allowed to Purchase above Selling Cost.
The transaction will be rolled back. ', 16, 1)
ROLLBACK TRANSACTION
END
END
END
END
It seems like ideally, here, you'd be implementing this as a multi-table CHECK constraint (in fact, in standard SQL, these are called ASSERTIONs. So far as I'm aware, only Postgre implements them).
If you're happy to trade away the ability to completely control the error message, I'd usually prefer to implement this in a declarative manner rather than relying on a trigger.
Here's how to do such a check with an indexed view. First a couple of tables that are somewhat like your problem domain (but not very fleshed out since you didn't put any definitions in your question):
create table dbo.ListPrices (
ID int not null,
Price decimal(12,4) not null,
constraint PK_ListPrices PRIMARY KEY (ID)
)
create table dbo.Orders (
ID int not null,
ListPriceID int not null,
MyPrice decimal(12,4) not null,
constraint PK_Orders PRIMARY KEY (ID),
constraint FK_Orders_ListPrices FOREIGN KEY (ListPriceID)
references dbo.ListPrices (ID)
)
go
insert into dbo.ListPrices (ID,Price) values (1,12.50),(2,25.00)
And now we create a special helper table. It's not needed if you already have a suitable table (such as a numbers table) in your database:
create table dbo.Two (
n int not null,
constraint PK_Two PRIMARY KEY (n),
constraint CK_Two_Only CHECK (n in (1,2))
)
go
insert into dbo.Two (n) values (1),(2)
And now we create the view:
go
create view dbo.DRI_NoOrderPricesOverListPrices
with schemabinding
as
select
1 as p /* Constant */
from
dbo.Two t
cross join
dbo.Orders o
inner join
dbo.ListPrices lp
on
o.ListPriceID = lp.ID
where
/*Conditions for failure*/
o.MyPrice > lp.Price
go
create unique clustered index IX_NoOrderPricesOverListPrices
on DRI_NoOrderPricesOverListPrices(p)
The key feature here is that the view joins all of the tables we're interested in together and we can put whatever conditions we like in the where clause, referencing multiple tables, comparing column values, etc.
The conditions we're specifying is for what shouldn't be allowed in the database. So, we're saying we should be able to insert a row in Orders provided that its price is less than or equal to the price in line items.
And we can. This insert succeeds:
insert into Orders (ID,ListPriceID,MyPrice) values (1,2,17.00)
And this one fails:
insert into Orders (ID,ListPriceID,MyPrice) values (2,1,17.00)
Msg 2601, Level 14, State 1, Line 42
Cannot insert duplicate key row in object 'dbo.DRI_NoOrderPricesOverListPrices' with unique index 'IX_NoOrderPricesOverListPrices'. The duplicate key value is (1).
(As I said at the top, we don't get so much control over the error message - but making a good choice of name here should make it reasonable to deduce what's happening and directly exposing SQL Server error messages to the users is something I'd generally try to avoid)
Related
Encountered this problem in production in the form of a deadlock. Figured out that if a transaction was inserting a row on my table, and I wanted to select a totally different row from that table, I would get the following error:
245: Could not position within a file via an index.
144: ISAM error: key value locked
Error in line 1
Near character position 70
My select statement was of the form select * from table where bar = 3 and foo = "CCCC";, where "foo" is a foreign key to a table with 18 rows, and "bar" is the first table's primary key. My insert statement was also inserting a row with foo = "CCCC". Curiously, the select query also returned the desired row before outputting the error.
I tried all this on informix 12.10 with isolation level set to repeatable read. I tried it on production, and in a fresh DB I set up with only the two tables mentioned. The lock mode of both tables is "row".
I investigated by modifying the select statement: select * from table where bar = 3; would not fail. Also, select * from table where bar = 3 and foo = "CCCC" order by ber; would not fail (ber being a random field from the table, ber is not indexed).
I would expect all the select statements I tried to return the desired row without error, OR all of them to fail. My solution in production was to order by a random field in the table, which fixed the deadlock issue
Does anyone know why this issue could have happened ? I suspect it is linked to the indexes on the table, which were all created automatically when adding the primary and foreign keys to the table. But I do not know enough about indexes to understand what happened. Could this be a bug ?
Schema of the tables:
create table options (
foo char(4) not null,
fee int not null)
extent size 16 next size 16
lock mode row;
alter table options add constraint (
primary key (foo)
constraint cons1 );
create table decisions (
bar char(3) not null,
foo char(4) not null,
ber int not null)
extent size 131072 next size 65536
lock mode row;
alter table decisions add constraint (
primary key (bar)
constraint cons2 );
alter table decisions add constraint (
foreign key (foo) references options(foo)
constraint cons3 );
Data I inserted into the "options" table:
AAAA|0|
BBBB|0|
CCCC|1|
DDDD|4|
EEEE|1|
FFFF|8|
Data I inserted into the "decisions" table:
QWE|AAAA|0|
WER|AAAA|9|
ERT|CCCC|2|
RTY|AAAA|32|
TYU|CCCC|1234|
YUI|CCCC|42398|
UIO|AAAA|23178|
IOP|CCCC|1233|
OPA|CCCC|11|
PAS|AAAA|890|
ASD|AAAA|90|
SDF|CCCC|2|
DFG|AAAA|4|
FGH|CCCC|7|
Edit: I used set explain on; for the queries.
select * from decisions where foo = "CCCC" and bar = "QWE" order by foo; returned that the index used was on foo="CCCC". However, for select * from decisions where foo = "CCCC" and bar = "QWE" order by ber;, it's indexed on bar="QWE".
I want to create a trigger which will check if my showtime with Cinema Hall Id exist or not. This will prevent me from booking one Cinema hall at two same Showtimes.
Secondly in this trigger I am also checking if the showtime I am assigning to movie lies in movie release and last date range or not.
But I don't know why none of the statement is working.
Create table Movie([Movie_ID] int primary key not null,[Movie_Name] varchar(50) Unique not null,[Realease_Date] date not Null,[Last_Date] date,Runtime time(0) not null ,Status varchar(20) not null,Rating float)
Create table [Showtime]([Showtime_ID] int primary key not null,Date date not null,Time time(0) not Null)
Create table [Cinema Halls]([Cinema_Halls_ID] int primary key not null,[Total_Seats] int not Null)
Create table [Movie Schedule] (
[Movie_Schedule_ID] int primary key not null,
[Movie_ID] int NOT null,
[Showtime_ID] int not null,
Cinema_Halls_ID int not null
Constraint fk_M_ID FOREIGN KEY ([Movie_ID]) REFERENCES Movie([Movie_ID]),
Constraint fk_Sh_ID FOREIGN KEY ([Showtime_ID]) REFERENCES Showtime([Showtime_ID]),
Constraint fk_C_ID FOREIGN KEY ([Cinema_Halls_ID]) REFERENCES [Cinema Halls] ([Cinema_Halls_ID])
)
/*Trigger Stops duplicate booking of Cinema halls and invalid showtime of movie*/
Create Trigger Trigger_Movie_Shedule
On "Movie Schedule"
After Insert,Update
As
declare #Cinema_Halls_ID int ,#Showtime_ID int,#Movie_ID int,#Release_Date Date,#Last_Date Date, #Showtime_Date date;
Select #Cinema_Halls_ID =Cinema_Halls_ID from inserted ;
Select #Showtime_ID=Showtime_ID from inserted;
Select #Movie_ID=Movie_ID from inserted;
Select Showtime_Date=Date from Showtime where Showtime_ID=#Showtime_ID
Select #Release_Date= Release_Date from Movie where Movie_ID=#Movie_ID;
Select #Last_Date=Last_Date from Movie where Movie_ID=#Movie_ID;
IF EXISTS (select count (Showtime_ID) from "Movie Schedule"
where Showtime_ID = #Showtime_ID and Cinema_Halls_ID = #Cinema_Halls_ID )
BEGIN
PRINT'This Cinema Hall is Already Booked'
Rollback Transaction;
return
END
ELSE IF (#Showtime_DATE >= #Release_Date and #Showtime_Date<= #Last_Date)
BEGIN
PRINT'Movie Showtime not in Range'
Rollback Transaction;
return
END
I think this is what you are looking for. The changes/improvements/best practices are:
Uses set-based logic, which you should always aim to do in a relational database.
Uses Inserted as a table rather than a single row
Semi-colon line terminators
set nocount on
Uses throw
Uses [] instead of ""
Fixed "in range" logic and detection of duplicate logic
CREATE TRIGGER Trigger_Movie_Shedule
ON [Movie Schedule]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM [Movie Schedule] S
-- Restrict the check to the inserted/updated records
INNER JOIN Inserted I on I.Showtime_ID = S.Showtime_ID and I.Cinema_Halls_ID = S.Cinema_Halls_ID
GROUP BY S.Showtime_ID, S.Cinema_Halls_ID
-- If more than one row exists we have a problem Houston
HAVING COUNT(*) > 1
) BEGIN
-- Rolls back, returns and provides an error message all in one.
THROW 51000, 'This Cinema Hall is Already Booked',1;
END; ELSE IF EXISTS (
SELECT 1
FROM Inserted I
INNER JOIN Movie M ON M.Movie_ID = I.Movie_ID
INNER JOIN ShowTime S ON S.ShowTime_ID = I.ShowTime_ID
-- WHERE S.Showtime_DATE >= M.Release_Date and S.Showtime_Date < M.Last_Date
-- Think you logic detects when it *is* in range, whereas the error is when its out of range
WHERE S.Showtime_DATE < M.Release_Date or S.Showtime_Date > M.Last_Date
) BEGIN
-- Rolls back, returns and provides an error message all in one.
THROW 51000, 'Movie Showtime not in Range',1;
END;
END;
Note: I highly recommend reading Using Inserted and Deleted as it explains very clearly how to write triggers.
I'm doing a school project about a school theme where I need to create some tables for Students, Classes, Programmes...
I want to add a Group to determined classes with an auto increment in group_id however I wanted the group_id variable to reset if I change any of those attributes(Classes_id,courses_acronym,year_Semesters) how can I reset it every time any of those change??
Here is my table:
CREATE TABLE Classes_Groups(
Classes_id varchar(2),
Group_id INT IDENTITY(1,1),
courses_acronym varchar(4),
year_Semesters varchar(5),
FOREIGN KEY (Classes_id, year_Semesters,courses_acronym) REFERENCES Classes(id,year_Semesters, courses_acronym),
PRIMARY KEY(Classes_id,courses_acronym,year_Semesters,Group_id)
);
Normally, you do not (need to) reset the identity column of a table. An identity column is used to create unique values for every single record in a table.
So you want to generate entries in your groups table based on new entries in your classes table. You might create a trigger on your classes table for that purpose.
Since Group_id is already unique by itself (because of its IDENTITY), you do not need other fields in the primary key at all. Instead, you may create a separate UNIQUE constraint for the combination (Classes_id, courses_acronym, year_Semesters) if you need it.
And if the id field of your classes table is an IDENTITY column too, you could define a primary key in your classes table solely on that id field. And then your foreign key constraint in your new groups table can only include that Classes_id field.)
So much for now. I guess that your database design needs some more additional tuning and tweaking. ;)
where are you setting the values from?, you can have a stored proc and in your query have the columns have an initial value set when stored proc is hit assuming there are values at the beginning
.Then use an IF statement.
declare #initial_Classes_id varchar(2) = --initial value inserted
declare #initial_courses_acronym varchar(4) = --initial value inserted
declare #initial_year_Semesters varchar(5) = --initial value inserted
declare #compare_Classes_id varchar(2) = (select top 1 Classes_id from Classes_Groups order by --PK column desc for last insert); l would add Dateadded and then order with last insert date
declare #compare_courses_acronym varchar(2) = (select top 1 Classes_id from Classes_Groups where Classes_id = #compare_Classes_id);
declare #compare_year_Semesters varchar(2) = (select top 1 Classes_id from Classes_Groups where Classes_id = #compare_Classes_id);
IF (#initial_Classes_id != #compare_Classes_id OR #initial_courses_acronym != #compare_courses_acronym OR #initial_year_Semesters != #compare_year_Semesters)
BEGIN
DBCC CHECKIDENT ('Group_id', RESEED, 1)
Insert into Classes_Groups (courses_acronym,year_Semesters)
values (
courses_acronym,
year_Semesters
)
END
ELSE
BEGIN
Insert into Classes_Groups (courses_acronym,year_Semesters)
values (
courses_acronym,
year_Semesters
)
END
NB: would advice to use int on the primary key. Unless you have a specific purpose of doing so.
I'm trying to replicate a foreign key across two servers using trigger. I know using trigger across 2 servers is not the best practice but my company on gives me read-only access to their database which I need to relate to my application.
I have DB1 which is my local database and it is attached to DB2 using linked server. I want trigger to check if a specific ID from a DB2_table on DB2 exists before executing an INSERT on DB1_table where the ID from DB2_table will act as a foreign key.
CREATE TRIGGER trigger_DB1_Table_Insert
#ID
BEFORE INSERT ON DB1_Table
AS
BEGIN
if exists(Select ID from DB2_Table where ID = #ID)
--execute insert
END
GO
I would actually recommend using a check constraint instead of a trigger. Check Constraints are designed to enforce data integrity and are a semantically better option. The example below creates some working tables, then creates a function which will check if the record exists in the other table.
The check constraint then uses the function and returns an error if the value doesn't exist.
CREATE TABLE dbo.OtherTable(
id INT
)
CREATE TABLE dbo.a(
id INT IDENTITY(1,1)
,OtherTableId INT
)
GO
INSERT INTO OtherTable (id) VALUES (1), (2), (3)
GO
-- Function will check if the ID exists in the other table
CREATE FUNCTION dbo.CheckOtherTableId(
#OtherTableId INT
)
RETURNS BIT
AS BEGIN
RETURN
(
SELECT
CASE
WHEN EXISTS
(
SELECT 1
FROM OtherTable
WHERE id = #OtherTableId
)
THEN 1
ELSE 0
END
)
END
GO
-- Add check constraint
ALTER TABLE a WITH CHECK
ADD CONSTRAINT [CK_OtherTable] CHECK (1=dbo.CheckOtherTableId(OtherTableId))
GO
-- Test should work
INSERT INTO a (OtherTableId) values (1)
Go
-- Test should fail
INSERT INTO a (OtherTableId) values (8)
GO
How could I set a constraint on a table so that only one of the records has its isDefault bit field set to 1?
The constraint is not table scope, but one default per set of rows, specified by a FormID.
Use a unique filtered index
On SQL Server 2008 or higher you can simply use a unique filtered index
CREATE UNIQUE INDEX IX_TableName_FormID_isDefault
ON TableName(FormID)
WHERE isDefault = 1
Where the table is
CREATE TABLE TableName(
FormID INT NOT NULL,
isDefault BIT NOT NULL
)
For example if you try to insert many rows with the same FormID and isDefault set to 1 you will have this error:
Cannot insert duplicate key row in object 'dbo.TableName' with unique
index 'IX_TableName_FormID_isDefault'. The duplicate key value is (1).
Source: http://technet.microsoft.com/en-us/library/cc280372.aspx
Here's a modification of Damien_The_Unbeliever's solution that allows one default per FormID.
CREATE VIEW form_defaults
AS
SELECT FormID
FROM whatever
WHERE isDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX ix_form_defaults on form_defaults (FormID)
GO
But the serious relational folks will tell you this information should just be in another table.
CREATE TABLE form
FormID int NOT NULL PRIMARY KEY
DefaultWhateverID int FOREIGN KEY REFERENCES Whatever(ID)
From a normalization perspective, this would be an inefficient way of storing a single fact.
I would opt to hold this information at a higher level, by storing (in a different table) a foreign key to the identifier of the row which is considered to be the default.
CREATE TABLE [dbo].[Foo](
[Id] [int] NOT NULL,
CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[DefaultSettings](
[DefaultFoo] [int] NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[DefaultSettings] WITH CHECK ADD CONSTRAINT [FK_DefaultSettings_Foo] FOREIGN KEY([DefaultFoo])
REFERENCES [dbo].[Foo] ([Id])
GO
ALTER TABLE [dbo].[DefaultSettings] CHECK CONSTRAINT [FK_DefaultSettings_Foo]
GO
You could use an insert/update trigger.
Within the trigger after an insert or update, if the count of rows with isDefault = 1 is more than 1, then rollback the transaction.
CREATE VIEW vOnlyOneDefault
AS
SELECT 1 as Lock
FROM <underlying table>
WHERE Default = 1
GO
CREATE UNIQUE CLUSTERED INDEX IX_vOnlyOneDefault on vOnlyOneDefault (Lock)
GO
You'll need to have the right ANSI settings turned on for this.
I don't know about SQLServer.But if it supports Function-Based Indexes like in Oracle, I hope this can be translated, if not, sorry.
You can do an index like this on suposed that default value is 1234, the column is DEFAULT_COLUMN and ID_COLUMN is the primary key:
CREATE
UNIQUE
INDEX only_one_default
ON my_table
( DECODE(DEFAULT_COLUMN, 1234, -1, ID_COLUMN) )
This DDL creates an unique index indexing -1 if the value of DEFAULT_COLUMN is 1234 and ID_COLUMN in any other case. Then, if two columns have DEFAULT_COLUMN value, it raises an exception.
The question implies to me that you have a primary table that has some child records and one of those child records will be the default record. Using address and a separate default table here is an example of how to make that happen using third normal form. Of course I don't know if it's valuable to answer something that is so old but it struck my fancy.
--drop table dev.defaultAddress;
--drop table dev.addresses;
--drop table dev.people;
CREATE TABLE [dev].[people](
[Id] [int] identity primary key,
name char(20)
)
GO
CREATE TABLE [dev].[Addresses](
id int identity primary key,
peopleId int foreign key references dev.people(id),
address varchar(100)
) ON [PRIMARY]
GO
CREATE TABLE [dev].[defaultAddress](
id int identity primary key,
peopleId int foreign key references dev.people(id),
addressesId int foreign key references dev.addresses(id))
go
create unique index defaultAddress on dev.defaultAddress (peopleId)
go
create unique index idx_addr_id_person on dev.addresses(peopleid,id);
go
ALTER TABLE dev.defaultAddress
ADD CONSTRAINT FK_Def_People_Address
FOREIGN KEY(peopleID, addressesID)
REFERENCES dev.Addresses(peopleId, id)
go
insert into dev.people (name)
select 'Bill' union
select 'John' union
select 'Harry'
insert into dev.Addresses (peopleid, address)
select 1, '123 someplace' union
select 1,'work place' union
select 2,'home address' union
select 3,'some address'
insert into dev.defaultaddress (peopleId, addressesid)
select 1,1 union
select 2,3
-- so two home addresses are default now
-- try adding another default address to Bill and you get an error
select * from dev.people
join dev.addresses on people.id = addresses.peopleid
left join dev.defaultAddress on defaultAddress.peopleid = people.id and defaultaddress.addressesid = addresses.id
insert into dev.defaultaddress (peopleId, addressesId)
select 1,2
GO
You could do it through an instead of trigger, or if you want it as a constraint create a constraint that references a function that checks for a row that has the default set to 1
EDIT oops, needs to be <=
Create table mytable(id1 int, defaultX bit not null default(0))
go
create Function dbo.fx_DefaultExists()
returns int as
Begin
Declare #Ret int
Set #ret = 0
Select #ret = count(1) from mytable
Where defaultX = 1
Return #ret
End
GO
Alter table mytable add
CONSTRAINT [CHK_DEFAULT_SET] CHECK
(([dbo].fx_DefaultExists()<=(1)))
GO
Insert into mytable (id1, defaultX) values (1,1)
Insert into mytable (id1, defaultX) values (2,1)
This is a fairly complex process that cannot be handled through a simple constraint.
We do this through a trigger. However before you write the trigger you need to be able to answer several things:
do we want to fail the insert if a default exists, change it to 0 instead of 1 or change the existing default to 0 and leave this one as 1?
what do we want to do if the default record is deleted and other non default records are still there? Do we make one the default, if so how do we determine which one?
You will also need to be very, very careful to make the trigger handle multiple row processing. For instance a client might decide that all of the records of a particular type should be the default. You wouldn't change a million records one at a time, so this trigger needs to be able to handle that. It also needs to handle that without looping or the use of a cursor (you really don't want the type of transaction discussed above to take hours locking up the table the whole time).
You also need a very extensive tesing scenario for this trigger before it goes live. You need to test:
adding a record with no default and it is the first record for that customer
adding a record with a default and it is the first record for that customer
adding a record with no default and it is the not the first record for that customer
adding a record with a default and it is the not the first record for that customer
Updating a record to have the default when no other record has it (assuming you don't require one record to always be set as the deafault)
Updating a record to remove the default
Deleting the record with the deafult
Deleting a record without the default
Performing a mass insert with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record inserts
Performing a mass update with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record updates
Performing a mass delete with multiple situations in the data including two records which both have isdefault set to 1 and all of the situations tested when running individual record deletes
#Andy Jones gave an answer above closest to mine, but bearing in mind the Rule of Three, I placed the logic directly in the stored proc that updates this table. This was my simple solution. If I need to update the table from elsewhere, I will move the logic to a trigger. The one default rule applies to each set of records specified by a FormID and a ConfigID:
ALTER proc [dbo].[cpForm_UpdateLinkedReport]
#reportLinkId int,
#defaultYN bit,
#linkName nvarchar(150)
as
if #defaultYN = 1
begin
declare #formId int, #configId int
select #formId = FormID, #configId = ConfigID from csReportLink where ReportLinkID = #reportLinkId
update csReportLink set DefaultYN = 0 where isnull(ConfigID, #configId) = #configId and FormID = #formId
end
update
csReportLink
set
DefaultYN = #defaultYN,
LinkName = #linkName
where
ReportLinkID = #reportLinkId