INSTEAD OF UPDATE Trigger and Updating the Primary Key - sql-server

I am making changes to an existing database while developing new software. There is also quite a lot of legacy software that uses the database that needs to continue working, i.e. I would like to maintain the existing database tables, procs, etc.
Currently I have the table
CREATE TABLE dbo.t_station (
tx_station_id VARCHAR(4) NOT NULL,
tx_description NVARCHAR(max) NOT NULL,
tx_station_type CHAR(1) NOT NULL,
tx_current_order_num VARCHAR(20) NOT NULL,
PRIMARY KEY (tx_station_id)
)
I need to include a new field in this table that refers to a Plant (production facility) and move the tx_current_order_num to another table because it is not required for all rows. So I've created new tables:-
CREATE TABLE Private.Plant (
PlantCode INT NOT NULL,
Description NVARCHAR(max) NOT NULL,
PRIMARY KEY (PlantCode)
)
CREATE TABLE Private.Station (
StationId VARCHAR(4) NOT NULL,
Description NVARCHAR(max) NOT NULL,
StationType CHAR(1) NOT NULL,
PlantCode INT NOT NULL,
PRIMARY KEY (StationId),
FOREIGN KEY (PlantCode) REFERENCES Private.Plant (PlantCode)
)
CREATE TABLE Private.StationOrder (
StationId VARCHAR(4) NOT NULL,
OrderNumber VARCHAR(20) NOT NULL,
PRIMARY KEY (StationId)
)
Now, I don't want to have the same data in two places so I decided to change the dbo.t_station table into a view and provide instead of triggers to do the DELETE, INSERT and UPDATE. No problem I have [most of] them working.
My question regards the INSTEAD OF UPDATE trigger, updating the Primary Key column (tx_station_id) and updates to multiple rows.
Inside the trigger block, is there any way to join the inserted and deleted [psuedo] tables so that I know the 'before update primary key' and the 'after update primary key'? Something like this...
UPDATE sta
SET sta.StationId = ins.tx_station_id
FROM Private.Station AS sta
INNER JOIN deleted AS del
INNER JOIN inserted AS ins
ON ROW_IDENTITY_OF(del) = ROW_IDENTITY_OF(ins)
ON del.tx_station_id = sta.StationId
At this stage I've put a check in the trigger block that rollbacks the update if the primary key column is updated and there is more than one row in the inserted, or deleted, table.

The short answer is no.
You could put a surrogate key on Private.Station, and expose that through the view, and use that to identify before and after values. You wouldn't need to change the primary key or foreign key relationship, but you would have to expose some non-updateable cruft through the view, so that it showed up in the pseudo-tables. eg:
alter table Private.Station add StationSk int identity(1,1) not null
Note, this may break the legacy application if it uses SELECT *. INSERT statements without explicit insert column lists should be ok, though.
Short of that, there may be some undocumented & consistent ordering between INSERTED and DELETED, such that ROW_NUMBER() OVER (ORDER BY NULLIF(StationId,StationId)) would let you join the two, but I'd be very hesitant to take the route. Very, very hesitant.
Have you intentionally not enabled cascade updates? They're useful when primary key values can be updated. eg:
CREATE TABLE Private.Station (
StationId VARCHAR(4) NOT NULL,
Description NVARCHAR(max) NOT NULL,
StationType CHAR(1) NOT NULL,
PlantCode INT NOT NULL,
PRIMARY KEY (StationId),
FOREIGN KEY (PlantCode) REFERENCES Private.Plant (PlantCode)
ON UPDATE CASCADE
-- maybe this too:
-- ON DELETE CASCADE
)
Someone might have a better trick. Wait and watch!

Related

Update/Delete violate foreign key on either side

I have two tables, below are the strutures
CREATE TABLE IF NOT EXISTS nl_address (
id int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
address_text varchar(100),
pincode varchar(6),
city_id int NOT NULL,
state_id int NOT NULL,
country_id int NOT null,
is_active boolean default true,
PRIMARY KEY (id),
CONSTRAINT fk_city_id FOREIGN KEY(city_id) REFERENCES nl_city(id),
CONSTRAINT fk_state_id FOREIGN KEY(state_id) REFERENCES nl_state(id),
CONSTRAINT fk_country_id FOREIGN KEY(country_id) REFERENCES nl_country(id)
);
CREATE TABLE IF NOT EXISTS nl_customer (
cust_id int NOT NULL,
prefix varchar(10) default 'CUST-',
suffix varchar(2),
org_name varchar(100) NOT NULL,
domain_name varchar(100) NOT NULL,
pan_number varchar(10) NOT null,
pri_contact varchar(10) NOT NULL,
pri_number varchar(10) NOT NULL,
pri_email varchar(30) NOT NULL,
sec_contact varchar(10),
sec_number varchar(10),
sec_email varchar(30),
is_active boolean default true,
addr_id int not null,
created_date date,
created_by varchar(10),
updated_date date,
updated_by varchar(10),
PRIMARY KEY (cust_id),
CONSTRAINT fk_address_id FOREIGN KEY(addr_id) REFERENCES nl_address(id)
);
The problem is, neither I am able to update or delete
If i am trying to update record in nl_address, I got an violation error that the field is used inside `nl_customer.
If i tried to update from nl_customer, then I got an violation error that the field is used inside nl_address
It was originated, when JPA trying to persist the data, I have inserted a dummy data with id 1, when JPA trying to insert another record then it throws
.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "nl_address_pkey"
Detail: Key (id)=(1) already exists.
It seems there is something wrong with the table structure, any help appreciated
Actually this is common that you cannot update or delete that belong to primary/foreign key if you generate duplicates, as all values should be unique (i.e. if you have already id=1 and update id=2 to id=1, you will get the error you mentioned) and because a foreign key construct is a specific relationship it should be clarified what will happen with this relationship.
In case of 'nl_address' you used 'GENERATED BY DEFAULT AS IDENTITY' which have the same purpose as SERIAL (i.e. auto increment), but it is more compliant with SQL standard. (I assume you are also aware of difference between GENERATED BY DEFAULT and GENERATED ALWAYS)
However, you can specify the sequence in order to ensure the proper auto increment functionality.
ALTER TABLE nl_address
ALTER COLUMN "id"
DROP IDENTITY IF EXISTS;
ALTER TABLE nl_address
ALTER COLUMN "id"
ADD GENERATED BY DEFAULT AS IDENTITY (START WITH 1 INCREMENT 1);
If you use UPDATE or DELETE on FOREIGN KEY construct ensure what should happen with relationship:
[CONSTRAINT fk_name]
FOREIGN KEY(fk_columns)
REFERENCES parent_table(parent_key_columns)
[ON DELETE delete_action]
[ON UPDATE update_action]
/* as delete_action or update_action you can use e.g. SET NULL, RESTRICT or CASCADE;
so ensure what happen with records in related table*/

Did I write this query correctly?

I'm given the question
My solution is
USE Finances
CREATE TABLE Account
(AccountID varchar(25) NOT NULL PRIMARY KEY,
AccountName varchar(50) NOT NULL,
AccountAddress varchar(30) NULL,
AccountCity varchar(25) NULL,
AccountState char(2) NULL,
AccountZip varchar(10) NULL,
AccountPhone varchar(14) NULL)
CREATE TABLE Transactions
(TransactionID INT NOT NULL PRIMARY KEY IDENTITY,
AccountID varchar(25) REFERENCES Account (AccountID),
TransactionDate smalldatetime NOT NULL,
TransactionAmount money NOT NULL)
CREATE TABLE Register
(RegisterID INT NOT NULL PRIMARY KEY IDENTITY,
TransactionID INT NOT NULL REFERENCES Transactions (TransactionID));
Part of the reason I'm confused is because I don't completely understand the primary/foreign key relationship. Like I look at the question and say, ok the registerID will be the single/unique ID to seperate every account from each other. But I see that there's a TranasctionID in the same table. And that TransactionID is a foreign key to the TransactionID in the transaction table. So does that mean whenever a new tranasctionID is added there is a corresponding RegisterID. Like whats the point of the Register ID?? And did I write this query correctly?
The only issue in you script is your column "AccountID" in table "Transactions" is creating with allowing NULL and "TransactionAmount" is NOT NULL where as it should allow NULL. Other wise I found the tables are creating with appropriate type and relation as instructed.
Regarding your confusion, I am confused too thinking about the purpose of table "Register". As per instruction, yes it will create a new row every time a new transaction take place. As registerId is a PK, with each transaction a new PK will generate. But the question is why a new RegisterId is required for each TransactionID? If RegisterId is referring to other table for a transaction details, it can be easily use the TransactionID directly as both TransactionID and RegisterId are unique (PK).

Can't Create Foreign Key: May Cause Cycles

I've got a very simple relationship between two tables that are used to manage custom UI branding:
ui_portal_branding
CREATE TABLE ui_portal_branding
(
id VARBINARY(16) NOT NULL,
branding_type VARCHAR(128) NOT NULL,
portal_name NVARCHAR(128) NOT NULL,
theme_id VARBINARY(16) NOT NULL,
portal_logo VARBINARY(16) NULL,
portal_favicon VARBINARY(16) NULL,
background_color VARCHAR(50) NULL,
organization_id VARBINARY(16) NULL,
CONSTRAINT pk_ui_port_bran_id PRIMARY KEY (id)
)
ui_portal_resource
CREATE TABLE ui_portal_resource
(
id VARBINARY(16) NOT NULL,
mime_type NVARCHAR(128) NOT NULL,
binary_data VARBINARY(MAX) NOT NULL,
CONSTRAINT pk_ui_port_reso_id PRIMARY KEY (id)
)
Branding is the main table, Resources is a BLOB store for binary data. Both portal_logo and portal_favicon in the branding table are optional binary data from the resource table.
I'd like to define this as a foreign key constraint with the following general logic: neither logo or favicon are required to be defined. If they are defined, they point to a record in the resource table by ui_portal_resource.id. If the data is deleted from the resource table, I want to set the corresponding column in the branding table to null. I don't want to disallow the resource deletion, I don't want to cascade the delete to the branding table.
So I define the following:
ALTER TABLE ui_portal_branding
ADD CONSTRAINT fk_ui_port_bran2ui_port_reso
FOREIGN KEY (portal_logo) REFERENCES ui_portal_resource (id)
ON DELETE SET NULL
So far so good. Now I define:
ALTER TABLE ui_portal_branding
ADD CONSTRAINT fk_ui_port_bran2ui_port_reso2
FOREIGN KEY (portal_favicon) REFERENCES ui_portal_resource (id)
ON DELETE SET NULL
and all of a sudden we have a problem:
Introducing FOREIGN KEY constraint 'fk_ui_port_bran2ui_port_reso2' on table 'ui_portal_branding' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
This to me seems wrong. I'm not introducing a cycle. It's two tables with the foreign keys defined in a single direction. I guess it can technically be multiple cascade paths–if the same resource is the favicon and the logo it has to set 2 things null. But really? This is the deal breaker for the SQL Server engine? Oracle and Postgres both find this situation to be acceptable.
Is there a sensible workaround for this issue? I'm not interested in a solution involving triggers. Is there a better way to model the data? I was hoping that the resources table could service more than just the branding table, which led to the current FK placement. But maybe that is just not possible?

Foreign Key to multiple tables

I've got 3 relevant tables in my database.
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner int NOT NULL,
Subject varchar(50) NULL
)
Users belong to multiple groups. This is done via a many to many relationship, but irrelevant in this case. A ticket can be owned by either a group or a user, via the dbo.Ticket.Owner field.
What would be the MOST CORRECT way describe this relationship between a ticket and optionally a user or a group?
I'm thinking that I should add a flag in the ticket table that says what type owns it.
You have a few options, all varying in "correctness" and ease of use. As always, the right design depends on your needs.
You could simply create two columns in Ticket, OwnedByUserId and OwnedByGroupId, and have nullable Foreign Keys to each table.
You could create M:M reference tables enabling both ticket:user and ticket:group relationships. Perhaps in future you will want to allow a single ticket to be owned by multiple users or groups? This design does not enforce that a ticket must be owned by a single entity only.
You could create a default group for every user and have tickets simply owned by either a true Group or a User's default Group.
Or (my choice) model an entity that acts as a base for both Users and Groups, and have tickets owned by that entity.
Heres a rough example using your posted schema:
create table dbo.PartyType
(
PartyTypeId tinyint primary key,
PartyTypeName varchar(10)
)
insert into dbo.PartyType
values(1, 'User'), (2, 'Group');
create table dbo.Party
(
PartyId int identity(1,1) primary key,
PartyTypeId tinyint references dbo.PartyType(PartyTypeId),
unique (PartyId, PartyTypeId)
)
CREATE TABLE dbo.[Group]
(
ID int primary key,
Name varchar(50) NOT NULL,
PartyTypeId as cast(2 as tinyint) persisted,
foreign key (ID, PartyTypeId) references Party(PartyId, PartyTypeID)
)
CREATE TABLE dbo.[User]
(
ID int primary key,
Name varchar(50) NOT NULL,
PartyTypeId as cast(1 as tinyint) persisted,
foreign key (ID, PartyTypeId) references Party(PartyID, PartyTypeID)
)
CREATE TABLE dbo.Ticket
(
ID int primary key,
[Owner] int NOT NULL references dbo.Party(PartyId),
[Subject] varchar(50) NULL
)
The first option in #Nathan Skerl's list is what was implemented in a project I once worked with, where a similar relationship was established between three tables. (One of them referenced two others, one at a time.)
So, the referencing table had two foreign key columns, and also it had a constraint to guarantee that exactly one table (not both, not neither) was referenced by a single row.
Here's how it could look when applied to your tables:
CREATE TABLE dbo.[Group]
(
ID int NOT NULL CONSTRAINT PK_Group PRIMARY KEY,
Name varchar(50) NOT NULL
);
CREATE TABLE dbo.[User]
(
ID int NOT NULL CONSTRAINT PK_User PRIMARY KEY,
Name varchar(50) NOT NULL
);
CREATE TABLE dbo.Ticket
(
ID int NOT NULL CONSTRAINT PK_Ticket PRIMARY KEY,
OwnerGroup int NULL
CONSTRAINT FK_Ticket_Group FOREIGN KEY REFERENCES dbo.[Group] (ID),
OwnerUser int NULL
CONSTRAINT FK_Ticket_User FOREIGN KEY REFERENCES dbo.[User] (ID),
Subject varchar(50) NULL,
CONSTRAINT CK_Ticket_GroupUser CHECK (
CASE WHEN OwnerGroup IS NULL THEN 0 ELSE 1 END +
CASE WHEN OwnerUser IS NULL THEN 0 ELSE 1 END = 1
)
);
As you can see, the Ticket table has two columns, OwnerGroup and OwnerUser, both of which are nullable foreign keys. (The respective columns in the other two tables are made primary keys accordingly.) The CK_Ticket_GroupUser check constraint ensures that only one of the two foreign key columns contains a reference (the other being NULL, that's why both have to be nullable).
(The primary key on Ticket.ID is not necessary for this particular implementation, but it definitely wouldn't harm to have one in a table like this.)
Another approach is to create an association table that contains columns for each potential resource type. In your example, each of the two existing owner types has their own table (which means you have something to reference). If this will always be the case you can have something like this:
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner_ID int NOT NULL,
Subject varchar(50) NULL
)
CREATE TABLE dbo.Owner
(
ID int NOT NULL,
User_ID int NULL,
Group_ID int NULL,
{{AdditionalEntity_ID}} int NOT NULL
)
With this solution, you would continue to add new columns as you add new entities to the database and you would delete and recreate the foreign key constraint pattern shown by #Nathan Skerl. This solution is very similar to #Nathan Skerl but looks different (up to preference).
If you are not going to have a new Table for each new Owner type then maybe it would be good to include an owner_type instead of a foreign key column for each potential Owner:
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner_ID int NOT NULL,
Owner_Type string NOT NULL, -- In our example, this would be "User" or "Group"
Subject varchar(50) NULL
)
With the above method, you could add as many Owner Types as you want. Owner_ID would not have a foreign key constraint but would be used as a reference to the other tables. The downside is that you would have to look at the table to see what the owner types there are since it isn't immediately obvious based upon the schema. I would only suggest this if you don't know the owner types beforehand and they won't be linking to other tables. If you do know the owner types beforehand, I would go with a solution like #Nathan Skerl.
Sorry if I got some SQL wrong, I just threw this together.
Yet another option is to have, in Ticket, one column specifying the owning entity type (User or Group), second column with referenced User or Group id and NOT to use Foreign Keys but instead rely on a Trigger to enforce referential integrity.
Two advantages I see here over Nathan's excellent model (above):
More immediate clarity and simplicity.
Simpler queries to write.
you can also use an enum to identify whether Owner is user or group like this:
CREATE TABLE dbo.Group
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TABLE dbo.User
(
ID int NOT NULL,
Name varchar(50) NOT NULL
)
CREATE TYPE Enum_OwnerType AS ENUM ('Group', 'User');
CREATE TABLE dbo.Ticket
(
ID int NOT NULL,
Owner int NOT NULL,
OwnerType Enum_OwnerType NOT NULL,
Subject varchar(50) NULL
)
Maybe it's no better than any of proposed solutions, it might not offer any advantage. In fact, I think that this might require altering Enum_OwnerType and even ticket in order to change OwnerType, I guess... I hope it's useful anyway.
I have many cases like this and I just use polymorphic ability like below:
example
I have turnovers table that have this columns id, amount, user_id and I need to know the refrence of every records, So I just add two Fields table_id and table_type and my final turnovers table is like id, amount, user_id,table_id, table_type.
if new record is about order record inserted like this
[1,25000,2,22,order]
and if new record is about increment credit like this
[1,25000,2,23,credit]
note
if using M:M tables its take so much time two retrieve the records
and my way
Cons is turnovers table records number is grows up
Pons is more flexible in new records and readable and search ability
nathan_jr's 4th option (model an entity that acts as a base for both Users and Groups, and have tickets owned by that entity) doesn't enforce referential integrity on PartyId. You'd have to do that on the application layer which invites all sorts of trouble. Can't really call it an antipattern when django's genericforeignkey implements the same solution, but no doubt you can design something more robust and performant using your framework's orm (using something like django's Multi-table inheritance)
CREATE TABLE dbo.OwnerType
(
ID int NOT NULL,
Name varchar(50) NULL
)
insert into OwnerType (Name) values ('User');
insert into OwnerType (Name) values ('Group');
I think that would be the most general way to represent what you want instead of using a flag.

How can I create this trigger for Microsoft SQL Server?

I have this limitation I have to implement server-side (in the database). Normally I'd do this on the client side, but I'd figure why not learn the server aspect. :p
I don't want my Clients to be able to rent a Movie from the XXX genre if they are under 18.
Here is the script I used to generate the tables:
-- =============================================
-- Sergio's Lab Tests MWA HA HA
-- =============================================
use AlquilerPeliculas
create table Client
(
ID int primary key not null identity(1,1),
Address nvarchar(1024) not null,
Phone nvarchar(256) not null,
NIT nvarchar(32) not null
)
go
create table Genre
(
ID int primary key not null identity(1,1),
Name nvarchar(256)
)
go
create table Movie
(
ID int primary key not null identity(1,1),
Name nvarchar(256) not null,
IDGenre int foreign key references Genre(ID)
)
go
create table Natural
(
IDCliente int primary key references Cliente(ID),
Age as datediff(d, FechaDeNacimiento,getdate())/365.00,
Nombre nvarchar(1024) not null,
ApellidoPaterno nvarchar(512) not null,
FechaDeNacimiento datetime,
Sexo varchar(1) not null check(Sexo='M' or Sexo='F')
)
go
create table Alquiler
(
ID int primary key not null identity(1,1),
FechaDeAlquiler datetime,
Total nvarchar(20) not null,
IDClient int foreign key references Client(ID)
)
go
create table Ejemplar
(
ID int primary key not null identity(1,1),
NumeroDeEjemplar nvarchar(256) not null,
Descripcion nvarchar(1024),
IDFormato int foreign key references Formato(ID),
IDPelicula int foreign key references Pelicula(ID)
)
go
create table DetalleAlquiler
(
ID int primary key not null identity(1,1),
IDEjemplar int foreign key references Ejemplar(ID),
IDAlquiler int foreign key references Alquiler(ID),
PrecioDeAlquiler nvarchar(128),
FechaDevolucion datetime,
FechaDevolucionProgramada datetime
)
I asked a friend what I should use and he said a Trigger, but it's my understanding that a trigger is a function that's run when the triggers conditions are met, right? If I used a trigger I'd have to insert then delete a naughty record right?
Thanks for the help.
Read all about triggers here
They come in many flavors, they can run before every insert / after every update and so on. The triggering of the trigger is a blanket action, if you have a BEFORE trigger, it will always run before.
Then, if you want to do any filtering or error handling, for example, only run a bit of code if a particular column has a particular value, you include that inside the trigger (in conditional code).
Generally, I recommend against using triggers, they can often make locking more complicated and are "hidden" in a place no one tends to look (which make them very hard to debug).
Another approach you can take DB-wise, it have particular stored procs that you call, instead of calling tables directly, eg: spRentMovie.

Resources