Speed up SQL Server AFTER INSERTED trigger - sql

I have a SQL Express 2019 server with a table on which I created a trigger that updates a row of that table when another row is inserted. I have to INSERT about 100,000 rows a day. Unfortunately the insert takes about 1-2 hours with the trigger, which is okay but frustrating while testing.
This is my table structure:
[ID] BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[REPORT_ID] BIGINT NOT NULL,
[CLUSTER_ID] BIGINT NOT NULL,
[ID_NEXT] BIGINT NULL,
[CREATED_AT] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
[ID_SUCCESSOR] BIGINT NULL,
[CREATED_AT] DATETIME NOT NULL DEFAULT GETUTCDATE(),
CONSTRAINT [FK_CRASHREPORTS_REPORTS2CLUSTERS_CRASHREPORTS] FOREIGN KEY ([REPORT_ID]) REFERENCES [CRASHREPORTS]([ID]),
CONSTRAINT [FK_CRASHREPORTS_REPORTS2CLUSTERS_ID_PREV] FOREIGN KEY ([ID_NEXT]) REFERENCES [CRASHREPORTS_REPORTS2CLUSTERS]([ID]),
CONSTRAINT [FK_CRASHREPORTS_REPORTS2CLUSTERS_ID_PREV] FOREIGN KEY ([ID_SUCCESSOR]) REFERENCES [CRASHREPORTS_REPORTS2CLUSTERS]([ID]),
CONSTRAINT [FK_CRASHREPORTS_REPORTS2CLUSTERS_CRASHREPORTS_CLUSTERS_ID] FOREIGN KEY ([CLUSTER_ID]) REFERENCES [CRASHREPORTS_CLUSTERS]([ID])
And this is my trigger:
CREATE TRIGGER [dbo].[update_prev_after_insert]
ON [dbo].[CRASHREPORTS_REPORTS2CLUSTERS]
AFTER INSERT
AS
BEGIN
MERGE INTO [dbo].[CRASHREPORTS_REPORTS2CLUSTERS] T
USING inserted I
ON T.ID != I.ID AND T.ID_SUCCESSOR is NULL and T.REPORT_ID = I.REPORT_ID
WHEN MATCHED THEN
UPDATE SET ID_SUCCESSOR = I.ID;
END
Everything works as expected but really slow. Does anyone knows how to speed this up?

Related

SQL The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Score_Gebruikersnaam"

I have a create script for my SQL database (see below). Everything works fine except when I run the INSERT INTO Scores() I get an error.
The error I get is:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Score_Gebruikersnaam". The conflict occurred in database "gamescores", table "dbo.Players", column 'gebruikersnaam'
I don't understand what I'm doing wrong so please help me :)
I already tried to drop the database first and then run the rest of the script but that didn't help. I think something went wrong with the foreign key...
Thanks!
USE master;
GO
DROP DATABASE IF EXISTS [gamescores];
CREATE DATABASE gamescores;
GO
USE gamescores;
GO
SET DATEFORMAT dmy;
CREATE TABLE [Players]
(
[gebruikersnaam] VARCHAR(75) NOT NULL,
[voornaam] VARCHAR(75) NOT NULL,
[achternaam] VARCHAR(75) NOT NULL,
[emailadres] VARCHAR(75) NOT NULL,
[geboortdatum] DATE NOT NULL
);
CREATE TABLE [Scores]
(
[scoreID] INT IDENTITY(1,1) NOT NULL,
[gebruikersnaam] VARCHAR(75) NOT NULL,
[aantalScore] INT NOT NULL,
[datum] DATE NOT NULL
);
ALTER TABLE [Players]
ADD CONSTRAINT [PK_Speler]
PRIMARY KEY (gebruikersnaam);
ALTER TABLE [Scores]
ADD CONSTRAINT [PK_Score]
PRIMARY KEY (scoreID);
ALTER TABLE [Players]
ADD CONSTRAINT [AK_Speler_Emailadres]
UNIQUE (emailadres)
ALTER TABLE [Scores]
ADD CONSTRAINT [FK_Score_Gebruikersnaam]
FOREIGN KEY (gebruikersnaam) REFERENCES Players(gebruikersnaam);
GO
INSERT INTO Players([gebruikersnaam], [voornaam], [achternaam], [emailadres], [geboortdatum])
VALUES ('apraundlin1', 'Angelle', 'Praundlin', 'apraundlin1#mapy.cz', '15-9-1997'),
('rnoore3', 'Rebekah', 'Noore', 'rnoore3#vk.com', '9-10-1987'),
('nplevinh', 'Nicolais', 'Plevin', 'nplevinh#mediafire.com', '18-3-2001');
-- SCORE table vullen
INSERT INTO Scores([gebruikersnaam], [aantalScore], [datum])
VALUES ('rsprasen0', 551, '15-5-2021'),
('fwhawell8', 309, '8-4-2021'),
('rgravett9', 1063, '16-11-2021');
You got an error because the database schema, though a foreign key, enforces that a player referenced in the scores table must first exist in the players table. Add the player to the players table, before trying to update their score in the scores table.

Composite Keys and Referential Integrity in T-SQL

Is it possible, in T-SQL, to have a relationship table with a composite key composed of 1 column defining Table Type and another column defining the Id of a row from a table referenced in the Table Type column?
For a shared-email address example:Three different user tables (UserA, UserB, UserC)One UserType Table (UserType)One Email Table (EmailAddress)One Email-User Relationship Table (EmailRelationship)The EmailRelationship Table contains three columns, EmailId, UserTypeId and UserId
Can I have a relationship from each User table to the EmailRelationship table (or some other way?) to maintain referential integrity?
I've tried making all three columns in the EmailRelationship table into primary keys, I've tried making only UserTypeId and UserId primary.
CREATE TABLE [dbo].[UserType](
[Id] [int] IDENTITY(1,1) NOT NULL ,
[Type] [varchar](50) NOT NULL)
insert into [dbo].[UserType]
([Type])
values
('A'),('B'),('C')
CREATE TABLE [dbo].[UserA](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserA]
(UserTypeId,Name)
values
(1,'UserA')
CREATE TABLE [dbo].[UserB](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserB]
(UserTypeId,Name)
values
(2,'UserB')
CREATE TABLE [dbo].[UserC](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserTypeId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL)
insert into [dbo].[UserC]
(UserTypeId,Name)
values
(3,'UserC')
CREATE TABLE [dbo].[Email](
[Id] [int] IDENTITY(1,1) NOT NULL,
[EmailAddress] [varchar](50) NOT NULL)
insert into [dbo].[email]
(EmailAddress)
values
('SharedEmail#SharedEmail.com')
CREATE TABLE [dbo].[EmailRelationship](
[EmailId] [int] NOT NULL,
[UserTypeId] [int] NOT NULL,
[UserId] [int] NOT NULL)
insert into [dbo].[EmailRelationship]
(EmailId, UserTypeId, UserId)
values
(1,1,1),(1,2,1),(1,3,1)
No there isn't, a foreign key can refer to one table, and one table only, I can think of three ways you could approach this.
The first is to have 3 columns, one for each user table, each column with a foreign key, and a check constraint to check that at one, and only one of the values is not null
CREATE TABLE dbo.EmailRelationship
(
EmailId INT NOT NULL,
UserTypeId INT NOT NULL,
UserAId INT NULL,
UserBId INT NULL,
UserCId INT NULL,
CONSTRAINT FK_EmailRelationship__UserAID FOREIGN KEY (UserAId)
REFERENCES dbo.UserA (Id),
CONSTRAINT FK_EmailRelationship__UserBID FOREIGN KEY (UserBId)
REFERENCES dbo.UserB (Id),
CONSTRAINT FK_EmailRelationship__UserCID FOREIGN KEY (UserCId)
REFERENCES dbo.UserC (Id),
CONSTRAINT CK_EmailRelationship__ValidUserId CHECK
(CASE WHEN UserTypeID = 1 AND UserAId IS NOT NULL AND ISNULL(UserBId, UserCId) IS NULL THEN 1
WHEN UserTypeID = 2 AND UserBId IS NOT NULL AND ISNULL(UserAId, UserCId) IS NULL THEN 1
WHEN UserTypeID = 3 AND UserCId IS NOT NULL AND ISNULL(UserAId, UserBId) IS NULL THEN 1
ELSE 0
END = 1)
);
Then as a quick example trying to insert a UserAId with a user Type ID of 2 gives you an error:
INSERT EmailRelationship (EmailID, UserTypeID, UserAId)
VALUES (1, 1, 1);
The INSERT statement conflicted with the CHECK constraint "CK_EmailRelationship__ValidUserId".
The second approach is to just have a single user table, and store user type against it, along with any other common attributes
CREATE TABLE dbo.[User]
(
Id INT IDENTITY(1, 1) NOT NULL,
UserTypeID INT NOT NULL,
Name VARCHAR(50) NOT NULL,
CONSTRAINT PK_User__UserID PRIMARY KEY (Id),
CONSTRAINT FK_User__UserTypeID FOREIGN KEY (UserTypeID) REFERENCES dbo.UserType (UserTypeID),
CONSTRAINT UQ_User__Id_UserTypeID UNIQUE (Id, UserTypeID)
);
-- NOTE THE UNIQUE CONSTRAINT, THIS WILL BE USED LATER
Then you can just use a normal foreign key constraint on your email relationship table:
CREATE TABLE dbo.EmailRelationship
(
EmailId INT NOT NULL,
UserId INT NOT NULL,
CONSTRAINT PK_EmailRelationship PRIMARY KEY (EmailID),
CONSTRAINT FK_EmailRelationship__EmailId
FOREIGN KEY (EmailID) REFERENCES dbo.Email (Id),
CONSTRAINT FK_EmailRelationship__UserId
FOREIGN KEY (UserId) REFERENCES dbo.[User] (Id)
);
It is then no longer necessary to store UserTypeId against the email relationship because you can join back to User to get this.
Then, if for whatever reason you do need specific tables for different user types (this is not unheard of), you can create these tables, and enforce referential integrity to the user table:
CREATE TABLE dbo.UserA
(
UserID INT NOT NULL,
UserTypeID AS 1 PERSISTED,
SomeOtherCol VARCHAR(50),
CONSTRAINT PK_UserA__UserID PRIMARY KEY (UserID),
CONSTRAINT FK_UserA__UserID_UserTypeID FOREIGN KEY (UserID, UserTypeID)
REFERENCES dbo.[User] (Id, UserTypeID)
);
The foreign key from UserID and the computed column UserTypeID back to the User table, ensures that you can only enter users in this table where the UserTypeID is 1.
A third option is just to have a separate junction table for each User table:
CREATE TABLE dbo.UserAEmailRelationship
(
EmailId INT NOT NULL,
UserAId INT NOT NULL,
CONSTRAINT PK_UserAEmailRelationship PRIMARY KEY (EmailId, UserAId),
CONSTRAINT FK_UserAEmailRelationship__EmailId FOREIGN KEY (EmailId)
REFERENCES dbo.Email (Id),
CONSTRAINT FK_UserAEmailRelationship__UserAId FOREIGN KEY (UserAId)
REFERENCES dbo.UserA (Id)
);
CREATE TABLE dbo.UserBEmailRelationship
(
EmailId INT NOT NULL,
UserBId INT NOT NULL,
CONSTRAINT PK_UserBEmailRelationship PRIMARY KEY (EmailId, UserBId),
CONSTRAINT FK_UserBEmailRelationship__EmailId FOREIGN KEY (EmailId)
REFERENCES dbo.Email (Id),
CONSTRAINT FK_UserBEmailRelationship__UserBId FOREIGN KEY (UserBId)
REFERENCES dbo.UserB (Id)
);
Each approach has it's merits and drawbacks, so you would need to assess what is best for your scenario.
No it does not work that way. You cannot use a column value as a dynamic reference to different tables.
In general the data design is flawed.
Thanks to #GarethD I created a CHECK constraint that called a scalar-function that would enforce referential integrity (only upon insert, refer to caveat below):
Using my above example:
alter FUNCTION [dbo].[UserTableConstraint](#Id int, #UserTypeId int)
RETURNS int
AS
BEGIN
IF EXISTS (SELECT Id From [dbo].[UserA] WHERE Id = #Id and UserTypeId = #UserTypeId)
return 1
ELSE IF EXISTS (SELECT Id From [dbo].[UserB] WHERE Id = #Id and UserTypeId = #UserTypeId)
return 1
ELSE IF EXISTS (SELECT Id From [dbo].[UserC] WHERE Id = #Id and UserTypeId = #UserTypeId)
return 1
return 0
end;
alter table [dbo].[emailrelationship]
--drop constraint CK_UserType
with CHECK add constraint CK_UserType
CHECK([dbo].[UserTableConstraint](UserId,UserTypeId) = 1)
I am sure there is a not insignificant overhead to a Scalar-function call from within a CONSTRAINT. If the above becomes prohibitive I will report back here, though the tables in question will not have to deal with a large volume of INSERTs.
If there are any other reasons to not do the above, I would like to hear them. Thanks!
Update:
I've tested INSERT and UPDATE with 100k rows (SQL Server 2014, 2.1ghz quadcore w/ 8gb ram):
INSERT takes 2 seconds with out the CONSTRAINT
and 3 seconds with the CHECK CONSTRAINT
Turning on IO and TIME STATISTICS causes the INSERT tests to run in:
1.7 seconds with out the CONSTRAINT
and 10 seconds with the CHECK CONSTRAINT
I left the STATISTICS on for the UPDATE 100k rows test:
just over 1sec with out the CONSTRAINT
and 1.5sec with the CHECK CONSTRAINT
My referenced tables (UserA, UserB, UserC from my example) only contain around 10k rows each, so anybody else looking to implement the above may want to run some additional testing, especially if your referenced tables contain millions of rows.
Caveat:
The above solution may not be suitable for most uses, as the only time referential integrity is checked is during the CHECK CONSTRAINT upon INSERT. Any other operations or modifications of the data needs to take that into account. For example, using the above, if an Email is deleted any related EmailRelationship entries will be pointing to invalid data.

primary key of new row automatically goes to 2nd table where it's a foreign key in a new row

This is my 1st table
CREATE TABLE [dbo].[Booking_Date]
(
[Book_ID] INT IDENTITY (1, 1) NOT NULL,
[Book_Checkin_Date] DATETIME NULL,
[Book_Checkout_date] DATETIME NULL,
[Adults] INT NULL,
[Children] INT NULL,
CONSTRAINT [PK_Booking_Date]
PRIMARY KEY CLUSTERED ([Book_ID] ASC)
);
And this is my 2nd table
CREATE TABLE [dbo].[Room_Detail]
(
[R_D_ID] INT IDENTITY (1, 1) NOT NULL,
[Cust_ID] INT NULL,
[Book_ID] INT NULL,
[Room_ID] INT NULL,
[Room_Price] MONEY NULL,
PRIMARY KEY CLUSTERED ([R_D_ID] ASC),
CONSTRAINT [FK_Room_Detail_Customer]
FOREIGN KEY ([Cust_ID]) REFERENCES [dbo].[Customer] ([Cust_ID]),
CONSTRAINT [FK_Room_Detail_Booking_Date]
FOREIGN KEY ([Book_ID]) REFERENCES [dbo].[Booking_Date] ([Book_ID]),
CONSTRAINT [FK_Room_Detail_Room]
FOREIGN KEY ([Room_ID]) REFERENCES [dbo].[Room] ([Room_ID])
);
When I insert data into my 1st table a with booking date, then primary key of that data automatically is inserted into the 2nd table that is Room Detail in the Book_ID column
If you want to do this in T-SQL / SQL Server, you can use an AFTER INSERT trigger on the Booking_Date table - something like this:
CREATE TRIGGER trgInsertBookingDate
ON dbo.Booking_Date
AFTER INSERT
AS
-- for each row newly inserted into "Booking_Date",
-- insert a new (empty) row into "Room_Detail"
INSERT INTO dbo.Room_Detail(Book_ID)
SELECT i.Book_ID
FROM Inserted i
So every time you insert one or multiple row(s) into Booking_Date, a new (more or less empty) row will be inserted into Room_Detail for each of those new rows inserted. Since you don't have any other information available, you can only set the Book_ID column of Room_Detail in the trigger - the other columns will have to somehow be specified / filled later

H2 database CREATE TABLE with constraint

I have two SQL statements:
CREATE TABLE legs(legid INT PRIMARY KEY AUTO_INCREMENT NOT NULL,
playerid1 INT NOT NULL REFERENCES players(playerid),
playerid2 INT NOT NULL REFERENCES players(playerid),
added TIMESTAMP AS CURRENT_TIMESTAMP NOT NULL);
ALTER TABLE legs ADD CONSTRAINT distinct_players CHECK(playerid1 <> playerid2);
I am 99% sure I should be able to condense them into one, i.e:
CREATE TABLE table(...
playerid2 INT NOT NULL REFERENCES players(playerid) CHECK(playerid1 <> playerid2),
...);
However, I am consistently getting a syntax error. AFAIK, this is where the constraint should be.
CREATE TABLE legs(legid INT PRIMARY KEY AUTO_INCREMENT NOT NULL,
playerid1 INT NOT NULL REFERENCES players(playerid),
playerid2 INT NOT NULL REFERENCES players(playerid),
added TIMESTAMP AS CURRENT_TIMESTAMP NOT NULL,
CHECK (playerid1 <> playerid2));

MySQL and foreign key conflicts when trying to INSERT

I'm doing the Agile Yii book.
Anyway, I'm trying to execute this command:
INSERT INTO tbl_project_user_assignment (project_id, user_id) values ('1','1'), ('1','2');
And I get this error:
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`trackstar_dev`.`tbl_project_user_assignment`, CONSTRAINT `FK_project_user` FOREIGN KEY (`project_id`) REFERENCES `tbl_project` (`id`) ON DELETE CASCADE)
So.. I figure let's see if tbl_project table have project_id=1. Did a quick SELECT * FROM tbl_project; and the project exist.
Ok then let's just check the user, SELECT * FROM tbl_user; Yup 2 user with id 1 and 2.
What am I doing wrong? Is there a typo? The agile yii book have several typos but they're not as serious and it's too new so there's no errata reported (checked already).
Here's the database schema from the source code:
-- Disable foreign keys
SET FOREIGN_KEY_CHECKS = 0 ;
-- Create tables section -------------------------------------------------
-- Table tbl_project
CREATE TABLE IF NOT EXISTS `tbl_project` (
`id` INTEGER NOT NULL auto_increment,
`name` varchar(128) NOT NULL,
`description` text NOT NULL,
`create_time` DATETIME default NULL,
`create_user_id` INTEGER default NULL,
`update_time` DATETIME default NULL,
`update_user_id` INTEGER default NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
;
-- DROP TABLE IF EXISTS `tbl_issue` ;
CREATE TABLE IF NOT EXISTS `tbl_issue`
(
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` varchar(256) NOT NULL,
`description` varchar(2000),
`project_id` INTEGER,
`type_id` INTEGER,
`status_id` INTEGER,
`owner_id` INTEGER,
`requester_id` INTEGER,
`create_time` DATETIME,
`create_user_id` INTEGER,
`update_time` DATETIME,
`update_user_id` INTEGER
) ENGINE = InnoDB
;
-- DROP TABLE IF EXISTS `tbl_user` ;
-- Table User
CREATE TABLE IF NOT EXISTS `tbl_user`
(
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`email` Varchar(256) NOT NULL,
`username` Varchar(256),
`password` Varchar(256),
`last_login_time` Datetime,
`create_time` DATETIME,
`create_user_id` INTEGER,
`update_time` DATETIME,
`update_user_id` INTEGER
) ENGINE = InnoDB
;
-- DROP TABLE IF EXISTS `tbl_project_user_assignment` ;
-- Table User
CREATE TABLE IF NOT EXISTS `tbl_project_user_assignment`
(
`project_id` Int(11) NOT NULL,
`user_id` Int(11) NOT NULL,
`create_time` DATETIME,
`create_user_id` INTEGER,
`update_time` DATETIME,
`update_user_id` INTEGER,
PRIMARY KEY (`project_id`,`user_id`)
) ENGINE = InnoDB
;
-- The Relationships
ALTER TABLE `tbl_issue` ADD CONSTRAINT `FK_issue_project` FOREIGN KEY (`project_id`) REFERENCES `tbl_project` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `tbl_issue` ADD CONSTRAINT `FK_issue_owner` FOREIGN KEY (`owner_id`) REFERENCES `tbl_user` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `tbl_issue` ADD CONSTRAINT `FK_issue_requester` FOREIGN KEY (`requester_id`) REFERENCES `tbl_user` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `tbl_project_user_assignment` ADD CONSTRAINT `FK_project_user` FOREIGN KEY (`project_id`) REFERENCES `tbl_project` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
ALTER TABLE `tbl_project_user_assignment` ADD CONSTRAINT `FK_user_project` FOREIGN KEY (`user_id`) REFERENCES `tbl_user` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
-- Insert some seed data so we can just begin using the database
INSERT INTO `tbl_user`
(`email`, `username`, `password`)
VALUES
('test1#notanaddress.com','Test_User_One', MD5('test1')),
('test2#notanaddress.com','Test_User_Two', MD5('test2'))
;
-- Enable foreign keys
SET FOREIGN_KEY_CHECKS = 1 ;
Anyway, thanks in advance!
EDIT:
Clarification that the project does indeed exist ^^.
mysql> select id,name from tbl_project;
+----+-------------------+
| id | name |
+----+-------------------+
| 6 | Project 1 |
| 1 | project zombied 1 |
+----+-------------------+
2 rows in set (0.00 sec)
The project_id and user_id in tbl_project_user_assignment are typed as INT(11) rather than INTEGER. I'm inclined to think that INTEGER is 4 BYTES and INT(11) would go to 8 BYTES.
As commented above INTEGER fixes the problem.
This is a strange issue you have encountered, as well as an odd fix for it. As far as I am aware, there is no internal difference between INTEGER, INT, OR INT(XX) (where XX is some number) They are all the same datatype with the same byte storage allocation and min/max range. This should not play a role in MySQL evaluation of type mismatch for some fk relationships. My version/configuration of MySQL (5.1.49) does not throw the same constraint violation you are experiencing when given using INT(11) in one table and INTEGER in another. I wonder if this is somehow more related to your configuration or if you are using other external DB tools.
One can read more about the internals of MySQL datatype here:
http://dev.mysql.com/doc/refman/5.0/en/numeric-types.html
of particular interest on this page:
Another extension is supported by
MySQL for optionally specifying the
display width of integer data types in
parentheses following the base keyword
for the type (for example, INT(4)).
This optional display width may be
used by applications to display
integer values having a width less
than the width specified for the
column by left-padding them with
spaces. (That is, this width is
present in the metadata returned with
result sets. Whether it is used or not
is up to the application.) The display
width does not constrain the range of
values that can be stored in the
column, nor the number of digits that
are displayed for values having a
width exceeding that specified for the
column. For example, a column
specified as SMALLINT(3) has the usual
SMALLINT range of -32768 to 32767, and
values outside the range permitted by
three characters are displayed using
more than three characters.