I have a database that contains 2 tables (sql server 2008):
LenderCommission
ID int
Commission decimal
CommissionTier
ID int
MinCommission decimal
MaxCommission decimal
I want to create a relationship between the 2 tables. The obvious way would be to add CommissionTierId to the LenderCommission table, however this is then fixed. If the commission structure of a tier changed all LenderCommissions would need to be revaluated.
I'm no expert but I'm guessing that it's not possible to create a relationship where Commission is between MinCommission and MaxCommission. However, is there any way in which this relationship can be made more fluid?
I'm not really sure what you're trying to do - I've put together a demo of one way you might be wanting things to work. We actually create a table that contains more columns and then hide that behind a view with a couple of triggers to make sure that everything works well.
The tables:
create table dbo.CommissionTier (
ID int not null,
MinCommission decimal not null,
MaxCommission decimal not null,
constraint PK_CommissionTier PRIMARY KEY (ID),
constraint UQ_CommissionTier_RangeValidation UNIQUE (ID,MinCommission,MaxCommission) --This is to support our FK below
)
go
create table dbo._LenderCommission (
ID int not null,
Commission decimal not null,
CommissionTier int not null,
_MinCommission decimal not null,
_MaxCommission decimal not null,
constraint PK__LenderCommission PRIMARY KEY (ID),
constraint CK__LenderCommission_InRange CHECK (
_MinCommission < Commission and --Inclusive or exclusive bound? <= or <
Commission < _MaxCommission --Ditto?
),
constraint FK__LenderCommission_Tier
FOREIGN KEY (CommissionTier)
references dbo.CommissionTier (ID), --This FK isn't actually needed
constraint FK__LenderCommission_Tier_Range
FOREIGN KEY (CommissionTier,_MinCommission,_MaxCommission)
references dbo.CommissionTier(ID,MinCommission,MaxCommission) on update cascade
)
The view (which from now on you'll treat as your original table):
create view dbo.LenderCommission
with schemabinding
as
select
ID,
Commission,
CommissionTier
from
dbo._LenderCommission
go
You probably don't need this, but I tend to always put on an index when the view is masquerading as a table, giving it the same name as the original primary key would have been:
create unique clustered index PK_LenderCommission on dbo.LenderCommission(ID)
Now a couple of triggers that do some necessary house cleaning:
create trigger dbo.T_LenderCommission_I
on dbo.LenderCommission
instead of insert
as
set nocount on
insert into dbo._LenderCommission (ID,Commission,CommissionTier,_MinCommission,_MaxCommission)
select i.ID,i.Commission,i.CommissionTier,ct.MinCommission,ct.MaxCommission
from
inserted i
inner join
dbo.CommissionTier ct
on
i.CommissionTier = ct.ID
go
create trigger dbo.T_LenderCommission_U
on dbo.LenderCommission
instead of update
as
set nocount on
update lc
set
Commission = i.Commission,
CommissionTier = i.CommissionTier,
_MinCommission = ct.MinCommission,
_MaxCommission = ct.MaxCommission
from
dbo._LenderCommission lc
inner join
inserted i
on
lc.ID = i.ID
inner join
dbo.CommissionTier ct
on
i.CommissionTier = ct.ID
And now we populate them:
insert into CommissionTier(ID,MinCommission,MaxCommission) values
(1,0,1000),
(2,1000,2000),
(3,2000,3000)
go
insert into LenderCommission(ID,Commission,CommissionTier) values
(1,500,1),
(2,750,1),
(3,1500,2)
Now for some errors:
--This produces an error:
insert into LenderCommission(ID,Commission,CommissionTier) values
(4,500,2)
go
--This produces an error:
update LenderCommission set Commission = 2500 where ID=2
go
--This *doesn't* produce an error because the new value matches the new tier
update LenderCommission set Commission = 2500,CommissionTier = 3 where ID=2
go
--This is okay, we're adjusting one of the tiers
update CommissionTier set MaxCommission = 1550 where ID = 2
go
--This produces an error, because the cascading foreign key updated the values:
update LenderCommission set Commission = 1600 where ID = 3
As an alternative to the above, rather than having CK__LenderCommission_InRange, you might instead create a computed column that performs the same checks and adopts suitable values:
CommissionOutOfRange AS
CASE WHEN Commission < MinCommission THEN -1
CASE WHEN Commission > MaxCommission THEN 1
ELSE 0 END
And now you can quickly search your table for values that are out of range (but nothing stops them from being created
I don't fully understand your goal. There seems to already be a logical relationship between the tables; presumably, the commission can fall between the min and max values. You would do the join as:
select <whatever>
from LenderCommission lc join
CommissionTier ct
on lc.commission between ct.MinCommission and ct.MaxCommission;
What are you trying to do?
One possibility is that you want to make this join more efficient by converting it to an equi-join rather than a "between" join. Having the id would definitely do this. If the CommissionTier table has only a few dozen rows, then this level of efficiency is probably unimportant.
Perhaps you are trying to enforce a constraint between the table, so new commissions can only be added with a commission tier. If so, you can do the checking via a trigger.
Really converting this relationship into a single foreign key will be complicated. You will need to take into account things such as:
Preventing overlapping commission tiers.
Changing the foreign key relationship when a commission tier changes (that is, modifying the data in eaderCommission.
Maintaining historical data on the commission tiers, if you ever need to see "what it was" at a different point in time.
These are all possible, but are perhaps much more complicated than you intend.
Related
This year I've been learning about relational databases and how to design them. In order to strenghten my knowledge, I'm trying to design and implement a database using Python and sqlite3.
The database is about a textile company, and, among other thigs, they want to keep information about the following:
Materials they use to make their products
Shops where they look for materials
Some shops (the ones where they do buy materials) are considered suppliers.
They want to know what suppliers provide what materials
About this last relationship, there are some restrictions:
A supplier can provide more than one material (Supplier class maximum cardinality many)
A material can be provided by more than one supplier (Material class maximum carindality many)
All materials must be provided by at least one supplier (Material class minimum cardinality one)
All suppliers must provide at least one material (Supplier class minimum cardinality one)
This is how I think the ER diagram looks giving these indications:
Entity-Relation diagram for "Provides" relationship
Given the minimum cardinality one, I think I have to implement integrity restrictions by triggers. This is how I think the logic design (the actual tables in the database) looks:
Logical diagram for "Provides" relationship
With the following integrity restrictions:
IR1. Minimum cardinality one in Material-Provides: every value of the 'cod_material' attribute from the Material table must appear at least once as a value of the 'cod_material' attribute in the Provides table.
IR2. Minimum cardinality one in Supplier-Provides: every value of the 'cod_supplier' attribute from the Supplier table must appear at least once as a value of the 'cod_supplier' attribute in the Provides table.
All of this means that, when inserting new suppliers or materials, I will also have to insert what material they provided (in the case of the suppliers) or what supplier has provided it (in the case of the materials).
This is what the triggers I made to take into consideration the integrity restrictions look like (I should also add that I've been working with pl-sql, and sqlite uses sql, so I'm not that used to this syntax, and there may be some errors):
CREATE TRIGGER IF NOT EXISTS check_mult_provides_supl
AFTER INSERT ON Supplier
BEGIN
SELECT
CASE WHEN ((SELECT p.cod_supplier FROM Provides p WHERE p.cod_supplier = new.cod_supplier) IS NULL)
THEN RAISE(ABORT, 'Esta tienda no ha provisto aun ningun material')
END;
END;
CREATE TRIGGER IF NOT EXISTS check_mult_provides_mat
AFTER INSERT ON Material
BEGIN
SELECT
CASE WHEN ((SELECT m.cod_material FROM Material m WHERE m.cod_material = new.cod_material) IS NULL)
THEN RAISE(ABORT, 'Este material no ha sido provisto por nadie')
END;
END;
I've tried adding new rows to the tables Material and Supplier respectively, and the triggers are working (or at least they're not allowing me to insert new rows without a row in the Provides table).
This is when I reach the deadlock:
Having the database empty, if I try to insert a row in the tables Material or Supplier the triggers fire and they don't allow me (because first I need to insert the corresponding row in the table Provides). However, if I try to insert a row in the Provides table, I get a foreign key constraint error (obviously, since that supplier and material are not inserted into their respective tables yet), so basically I cannot insert rows in my database.
The only answers I can think of are not very satisfactory: momentary disabling any constraint (either the foreign key constraint or the integrity one by the trigger) puts the database integrity at risk, since new inserted rows don't fire the trigger even if this one gets enabled after. The other thing I thought of was relaxing the minimum cardinality restrictions, but I assume a many-to-many relationship with minimum cardinality one restriction should be usual in real databases, so there must be another kind of solutions.
How can I get out of this deadlock? Maybe a procedure (although sqlite doesn't have store procedures, I think I can make them with the Python API by create_function() in the sqlite3 module) would do the trick?
Just in case, if anyone wants to reproduce this part of the database, here is the code for the creation of the tables (I finally decided to autoincrement the primary key, so the datatype is an integer, as opposed to the ER diagram and the logical diagram which said a datatype character)
CREATE TABLE IF NOT EXISTS Material (
cod_material integer AUTO_INCREMENT PRIMARY KEY,
descriptive_name varchar(100) NOT NULL,
cost_price float NOT NULL
);
CREATE TABLE IF NOT EXISTS Shop (
cod_shop integer AUTO_INCREMENT PRIMARY KEY,
name varchar(100) NOT NULL,
web varchar(100) NOT NULL,
phone_number varchar(12),
mail varchar(100),
address varchar(100)
);
CREATE TABLE IF NOT EXISTS Supplier (
cod_proveedor integer PRIMARY KEY CONSTRAINT FK_Supplier_Shop REFERENCES Shop(cod_shop)
);
CREATE TABLE IF NOT EXISTS Provides (
cod_material integer CONSTRAINT FK_Provides_Material REFERENCES Material(cod_material),
cod_supplier integer CONSTRAINT FK_Provides_Supplier REFERENCES Supplier(cod_supplier),
CONSTRAINT PK_Provides PRIMARY KEY (cod_material, cod_supplier)
);
I believe that you want a DEFERRED FOREIGN KEY. The triggers, however, will interfere as they would be triggered.
However, you also need to consider the code that you have posted. There is no AUTO_INCREMENT keyword it is AUTOINCREMENT (however you very probably do not do not need AUTOINCREMENT as INTEGER PRIMARY KEY will do all that you required).
If you check SQLite AUTOINCREMENT along with
The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed.
The Supplier table is useless as you have coded it is simply a single column that references a shop with no other data. However, the Provides table references the Supplier table BUT to a non-existent column (cod_supplier).
Coding CONSTRAINT name REFERENCES table(column(s)) doesn't adhere to the SYNTAX as CONSTRAINT is a table level clause, whilst REFERENCES is a column level clause and this appears to cause some confusion.
I suspect that you may have resorted to Triggers because the FK conflicts weren't doing anything. By default FK processing is turned off and has to be enabled as per Enabling Foreign Key Support. I don't believe they are required.
Anyway I believe that the following, that includes changes to overcome the above issues, demonstrates DEFERREED FOREIGN KEYS :-
DROP TABLE IF EXISTS Provides;
DROP TABLE IF EXISTS Supplier;
DROP TABLE IF EXISTS Shop;
DROP TABLE IF EXISTS Material;
DROP TRIGGER IF EXISTS check_mult_provides_supl;
DROP TRIGGER IF EXISTS check_mult_provides_mat;
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS Material (
cod_material integer PRIMARY KEY,
descriptive_name varchar(100) NOT NULL,
cost_price float NOT NULL
);
CREATE TABLE IF NOT EXISTS Shop (
cod_shop integer PRIMARY KEY,
name varchar(100) NOT NULL,
web varchar(100) NOT NULL,
phone_number varchar(12),
mail varchar(100),
address varchar(100)
);
CREATE TABLE IF NOT EXISTS Supplier (
cod_supplier INTEGER PRIMARY KEY, cod_proveedor integer /*PRIMARY KEY*/ REFERENCES Shop(cod_shop) DEFERRABLE INITIALLY DEFERRED
);
CREATE TABLE IF NOT EXISTS Provides (
cod_material integer REFERENCES Material(cod_material) DEFERRABLE INITIALLY DEFERRED,
cod_supplier integer REFERENCES Supplier(cod_supplier) DEFERRABLE INITIALLY DEFERRED,
PRIMARY KEY (cod_material, cod_supplier)
);
/*
CREATE TRIGGER IF NOT EXISTS check_mult_provides_supl
AFTER INSERT ON Supplier
BEGIN
SELECT
CASE WHEN ((SELECT p.cod_supplier FROM Provides p WHERE p.cod_supplier = new.cod_supplier) IS NULL)
THEN RAISE(ABORT, 'Esta tienda no ha provisto aun ningun material')
END;
END;
CREATE TRIGGER IF NOT EXISTS check_mult_provides_mat
AFTER INSERT ON Material
BEGIN
SELECT
CASE WHEN ((SELECT m.cod_material FROM Material m WHERE m.cod_material = new.cod_material) IS NULL)
THEN RAISE(ABORT, 'Este material no ha sido provisto por nadie')
END;
END;
*/
-- END TRANSACTION; need to use this if it fails before getting to commit
BEGIN TRANSACTION;
INSERT INTO Shop (name,web,phone_number,mail,address)VALUES('shop1','www.shop1.com','000000000000','shop1#email.com','1 Somewhere Street, SomeTown etc');
INSERT INTO Supplier (cod_proveedor) VALUES((SELECT max(cod_shop) FROM Shop));
INSERT INTO Material (descriptive_name,cost_price)VALUES('cotton',10.5);
INSERT INTO Provides VALUES((SELECT max(cod_material) FROM Material),(SELECT max(cod_supplier) FROM Supplier ));
COMMIT;
SELECT * FROM shop
JOIN Supplier ON Shop.cod_shop = cod_proveedor
JOIN Provides ON Provides.cod_supplier = Supplier.cod_supplier
JOIN Material ON Provides.cod_material = Material.cod_material
;
DROP TABLE IF EXISTS Provides;
DROP TABLE IF EXISTS Supplier;
DROP TABLE IF EXISTS Shop;
DROP TABLE IF EXISTS Material;
DROP TRIGGER IF EXISTS check_mult_provides_supl;
DROP TRIGGER IF EXISTS check_mult_provides_mat;
When run as is then the result is :-
However, if the INSERT into the Supplier is altered to :-
INSERT INTO Supplier (cod_proveedor) VALUES((SELECT max(cod_shop) + 1 FROM Shop));
i.e. the reference to the shop is not an existing shop (1 greater) then :-
The messages/log are :-
BEGIN TRANSACTION
> OK
> Time: 0s
INSERT INTO Shop (name,web,phone_number,mail,address)VALUES('shop1','www.shop1.com','000000000000','shop1#email.com','1 Somewhere Street, SomeTown etc')
> Affected rows: 1
> Time: 0.002s
INSERT INTO Supplier (cod_proveedor) VALUES((SELECT max(cod_shop) + 1 FROM Shop))
> Affected rows: 1
> Time: 0s
INSERT INTO Material (descriptive_name,cost_price)VALUES('cotton',10.5)
> Affected rows: 1
> Time: 0s
INSERT INTO Provides VALUES((SELECT max(cod_material) FROM Material),(SELECT max(cod_supplier) FROM Supplier ))
> Affected rows: 1
> Time: 0s
COMMIT
> FOREIGN KEY constraint failed
> Time: 0s
That is the deferred inserts were successful BUT the commit failed.
You may wish to refer to SQLite Transaction
I think the design of your database should be reconsidered, since the table Provides represents two different set of informations: which shop provides which materials, and which is the supplier for a certain material. A better design should be to separate those two kind of information, so that you can increase the constraints expressed through foreign keys.
Here is a sketch of the tables, not tied to a specific RDBMS.
Material (cod_material, descriptive_name, cost_price)
PK (cod_material)
Shop (cod_shop, name, web. phone_number, mail, address)
PK (cod_shop)
ShopMaterial (cod_shop, cod_material)
PK (cod_shop, cod_material),
cod_shop FK for Shop, cod_material FK for Material
SupplierMaterial (cod_sup, cod_material)
PK (cod_sup, cod_material)
cod_sup FK for Shop, cod_material FK for material
(cod_sup, cod_material) FK for ShopMaterial
The different foreign keys already take into account several constraints. The only constraint not enforced is, I think:
All materials must be provided by at least one supplier
This constraint cannot be enforced automatically since you have first to insert a material, then to add the corresponding pairs (cod_shop, cod_material) and then the pairs (cod_sup, cod_material). For this, I think the best option is to define, at the application level, a procedure that insert at the same time the material, the shops from which it can be obtained, and the supplier for it, as well as a procedure that remove the material, and the relevant pairs in ShopMaterial and SupplierMaterial tables.
I need to re-write this without using subquery and explicit join..help please been looking around for a while
SELECT snum, pnum, shipdate
FROM supply as b
WHERE EXISTS (SELECT pname, pnum FROM parts as a WHERE b.pnum = a.pnum);
I believe you've been given a trick question. The answer is this.
SELECT snum, pnum, shipdate
FROM supply
The reason is that the condition you're checking for should be impossible in a well designed database.
Let's have a look at what the original query is doing.
SELECT snum, pnum, shipdate
FROM supply as b
WHERE EXISTS (
SELECT pname, pnum FROM parts as a WHERE b.pnum = a.pnum
);
It's getting every row in supply where there's a corresponding part in parts. How do you do this in a query without a join? You shouldn't have to do it in the first place. Instead you should rely on referential integrity.
Referential integrity is a property of good table design that says all references are valid. There should be no need to check that each part in supply exists in parts because such a condition should be impossible. You do this with a well designed schema with appropriate use of foreign key and not null constraints.
(My examples are done in Postgres. The syntax for your database may vary.)
create table parts(
pnum integer primary key,
pname text not null
);
create table supply(
snum integer primary key,
pnum integer references parts(pnum) not null,
shipdate date not null
);
By declaring supply.pnum as references parts(pnum) we have told the database this is a foreign key and there must be a corresponding row in parts. Adding not null guarantees each row in supply must supply a valid part. The database enforces these constraints automatically.
(Note that MySQL takes a little more convincing to enforce a foreign key constraint. Because MySQL is so non-standard one can pick up bad habits learning on it. Use Postgres or even SQLite instead.)
You can also add the constraints to an existing table using alter table.
test=> alter table supply alter pnum set not null;
ALTER TABLE
test=> alter table supply add constraint pnum_fk foreign key (pnum) references parts(pnum);
ALTER TABLE
For example, let's say we have these parts.
test=> select * from parts;
pnum | pname
------+---------
1 | flange
2 | thingy
3 | whatsit
We can insert a row into supply for one of those parts.
test=> insert into supply (pnum, shipdate) values (3, '2018-02-03');
INSERT 0 1
But if we try to insert a part that doesn't exist, we get an error.
test=> insert into supply (pnum, shipdate) values (99, '2018-02-03');
ERROR: insert or update on table "supply" violates foreign key constraint "supply_pnum_fkey"
DETAIL: Key (pnum)=(99) is not present in table "parts".
Or one with a null part number...
test=> insert into supply (pnum, shipdate) values (null, '2018-02-03');
ERROR: null value in column "pnum" violates not-null constraint
DETAIL: Failing row contains (1, null, 2018-02-03).
The condition you're testing for is now impossible. There's no need for it. So the answer is:
SELECT snum, pnum, shipdate
FROM supply
One way is INTERSECT(column list is limited to common ones):
SELECT pnum
FROM supply
INTERSECT
SELECT pnum
FROM parts;
Using SEMIJOIN:
SELECT b.snum, b.pnum, b.shipdate
FROM supply as b
SEMIJOIN parts as a
ON b.pnum = a.pnum;
The subquery can be replaced by an INNER JOIN, as follows :
SELECT b.snum, b.pnum, b.shipdate
FROM
supply as b
INNER JOIN parts as a ON b.pnum = a.pnum
GROUP BY b.snum, b.pnum, b.shipdate
You could also go for implicit join, but I would not recommend it as it is less readable and out of favor as of now :
SELECT b.snum, b.pnum, b.shipdate
FROM
supply as b,
parts as a
WHERE b.pnum = a.pnum
GROUP BY b.snum, b.pnum, b.shipdate
I am designing the database for an accounting system, currently working on the Expenses table.
According to IRS rules, whenever you update a row in any accounting table, you need to cancel out the existing row by negating its values, and create a new row with the modified information, like so:
Set the row's Status flag to "Modified"
Create an identical copy of this row, with all Money fields negated, so that the sum of the two rows is 0
Create a 3rd row, identical to the first one, with the modified data
Each expense has an identity field called ID for internal identification purposes, and an ExpenseID field, which identifies the transaction to the users. The two cannot be the same, because
ExpenseID can be repeated twice if the transaction was modified and its row was duplicated.
ExpenseIDs MUST be consecutive and NEVER have gaps, while identity fields can skip numbers if a transaction is rolled back and the identity is not reseeded.
In general, I believe the primary key should have no business meaning whatsoever.
My problem is that there are other tables used to link these expenses Many-To-Many to other objects in our system. E.g.: each expense can be linked to documents, folders, users, etc.
So it looks something like this:
create table Expenses (
ID int not null identity(1,1),
ExpenseID int not null,
Amount Money not null,
Status tinyint not null,
[...]
)
create table Expenses_Users (
ExpenseID int not null,
UserID int not null
)
alter table Expenses_Users add constraint FK_Expenses_Users_Expenses
foreign key (ExpenseID) references Expenses (ID)
alter table Expenses_Users add constraint FK_Expenses_Users_Users
foreign key (UserID) references Users (ID)
Now, because of the IRS guidelines, I have to duplicate not only rows in the Expenses table, but also in Expenses_Users, and any other table that links Expenses to other tables.
I have two ideas on how to solve this:
Option One: Normalize Expenses like this:
create table Expenses (
ID int not null identity(1,1),
ExpenseID int not null,
Status tinyint not null,
[...]
)
create table ExpensesNormalized (
ExpenseID int not null,
Amount Money not null
)
alter table ExpensesNormalized add constraint FK_ExpensesNormalized_Expenses
foreign key (ExpenseID) references Expenses(ExpenseID)
This means I'll only have to link external tables to Expenses, not ExpensesNormalized. Also, when updating an expense, I'll only duplicate and negate the data in ExpensesNormalized, which means I'll have far less redundant data in the Expenses table.
However, I'll have to use a JOIN clause every single time I SELECT from Expenses. I fear a performance hit because of this.
Option Two: Use the same tables I use now, but have the field Expenses_Users.ExpenseID point to the field Expenses.ExpenseID. This means that I won't have to duplicate any external objects because they'll point to ExpenseID, which may occur several times.
However, this will not be a real foreign key because SQL Server does not allow foreign keys to non-unique fields, so I'll have to implement foreign key logic in a trigger.
I'm having a hard time deciding between these two options. Any feedback would be appreciated.
I need help with constraints in SQL Server. The situation is for each OrderID=1 (foreign key not primary key so there are multiple rows with the same ID) on the table, the bit field can only be 1 for one of those rows, and for each row with OrderID=2, the bit field can only be 1 for one row, etc etc. It should be 0 for all other rows with the same OrderID. Any new records coming in with 1 in the bit field should reject if there is already a row with that OrderID which has the bit field set to 1. Any ideas?
CREATE UNIQUE INDEX ON UnnamedTable (OrderID) WHERE UnnamedBitField=1
It's called a Filtered Index. If you're on a pre-2008 version of SQL Server, you can implement a poor-mans equivalent of a filtered index using an indexed view:
CREATE VIEW UnnamedView
WITH SCHEMABINDING
AS
SELECT OrderID From UnnamedSchema.UnnamedTable WHERE UnnamedBitField=1
GO
CREATE UNIQUE CLUSTERED INDEX ON UnnamedView (OrderID)
You can't really do it as a constraint, since SQL Server only supports column constraints and row constraints. There's no (non-fudging) way to write a constraint that deals with all values in the table.
You could more fully normalize the schema which will help you not have to hunt for the already set bit but use a join. You need to remove the bit field and crate a new table say X containing OrderID and the primary key of your table, with the primary key of X being all those fields.
This means that when you insert you need to insert into your original table and into X f and only if you would have set the bit to 1 on your table. The insert will fail if there is already a row in X which is as if there was already an original row with bit set to 1.
The downside is that this takes up more space than your schema but is easier to maintain as you can't get to the equivalent of having two rows with the bit set to 1.
The only way to do that is to subclass the parent table. You didn't mention it but a common reason for this pattern is to represent one unique active row from the set of all rows with the same common key value. Let's Assume your bit field represents the active Orders....
Then I would create a separate table called ActiveOrders, which will only contain the one row with the bit field set to 1
Create Table ActiveOrders(int Orderid Primary Key Null)
and the other table with all the rows in it, with it's own unique Primary Key OrderId
Create Table AllOrders
(OrderId Integer Primary Key Not Null, ActiveOrderId Integer Not Null,
[All other data fields]
Constraint FK_AllOrders2ActiveOrder
Foreign Key(ActiveOrderId) references ActiveOrders(OrderId))
You now no longer even need the bit field, as the presence of the row in the ActiveOrders table identifies it as the Active Order... To get only the active Orders (the ones that in your scheme would have bit field set to 1), just join the two tables.
I aggree with the other answers and if you can change the schema then do that but if not then I think something like this will do.
CREATE FUNCTION fnMyCheck
(#id INT)
RETURNS INT
AS
BEGIN
DECLARE #i INT
SELECT #i = COUNT(*)
FROM MyTable
WHERE FkCol = #id
AND BitCol = 1
RETURN #i
END
ALTER TABLE YourTable
ADD CONSTRAINT ckMyCheck CHECK (fnMyCheck(FkCol)<=1)
but there are problems that can come from doing using a udf in a check constraint, such as this
Edit to add comment regarding problems with this 'solution':
There are more straightforward issues than what you've linked to.
INSERT INTO YourTable(FkCol,BitCol) VALUES (1,1),(1,0)
followed by
UPDATE YourTable SET BitCol=1
succeeds and leaves two rows with FkCol=1 and BitCol=1
I have an accounts table with the account owner as the primary key. In the update trigger, I want to update some accounts to new owners. Since this table doesn't have an id field, how do I use the inserted/updated tables in the trigger? DB is sql server 2008.
CREATE TRIGGER accounts_change_owner on accounts AFTER INSERT
AS BEGIN
MERGE INTO accounts t
USING
(
SELECT *
FROM inserted e
INNER JOIN deleted f ON
e.account_owner = f.account_owner ---this won't work since the new account owner value is diff
) d
ON (t.account_owner = d.account_owner)
WHEN MATCHED THEN
UPDATE SET t.account_owner = d.account_owner
END
I think I understood your question, but I am not sure. You want to be able update account owner name in one table and to have this update propagated to the referencing tables?
If so you don't really need a trigger, you can use on update cascade foreign key.
Like this:
create table AccountOwner
(
Name varchar(100) not null
constraint PK_AccountOwner primary key
)
create table Account
(
AccountName varchar(100) not null,
AccountOwnerName varchar(100) not null
constraint FK_Account_AccountOwnerName references AccountOwner(Name) on update cascade
)
insert AccountOwner values('Owner1')
insert Account values('Account1', 'Owner1')
Now if I update table AccountOwner like this
update AccountOwner
set Name = 'Owner2'
where Name = 'Owner1'
it will automatically update table 'Account'
select *
from Account
AccountName AccountOwnerName
----------- -----------------
Account1 Owner2
I think you need to modify the design of your table. Recall that the three attributes of a primary key are that the primary key must be
Non-null
Unique
Unchanging
(If the primary key consists of multiple columns, all columns must follow the rules above). Most databases enforce #1 and #2, but the enforcement of #3 is usually left up to the developers.
Changing a primary key value is a classic Bad Idea in a relational database. You can probably come up with a way to do it; that doesn't change the fact that it's a Bad Idea. Your best choice is to add an artificial primary key to your table, put NOT NULL and a UNIQUE constraints on the ACCOUNT_OWNER field (assuming that this is the case), and change any referencing tables to use the artificial key.
The next question is, "What's so bad about changing a primary key value?". Changing the primary key value alters the unique identifier for that particular data; if something else is counting on having the original value point back to a particular row, such as a foreign key relationship, after such a change the original value will no longer point where it's supposed to point.
Good luck.