MS SQL Check for duplicate in two fields - sql-server

I am trying create a trigger that will check if the Author already exist in a table based on a combination of their first and last name. From what Ive been reading this trigger should work, but when I try to insert any new author into the table it gives the "Author exists in table already!" error even though I am inserting an author that does not exist in the table.
Here is the trigger
USE [WebsiteDB]
GO
CREATE TRIGGER [dbo].[tr_AuthorExists] ON [dbo].[Authors]
AFTER INSERT
AS
if exists ( select * from Authors
inner join inserted i on i.author_fname=Authors.author_fname AND i.author_lname=Authors.author_lname)
begin
rollback
RAISERROR ('Author exists in table already!', 16, 1);
End
Here is the table
CREATE TABLE [dbo].[Authors](
[author_id] [int] IDENTITY(1,1) NOT NULL,
[author_fname] [nvarchar](50) NOT NULL,
[author_lname] [nvarchar](50) NOT NULL,
[author_middle] [nvarchar](50) NULL,
CONSTRAINT [PK_Authors] PRIMARY KEY CLUSTERED
(
[author_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Any assistance would be appreciated!

You will need to do this as an INSTEAD of trigger. This also means you need to actually perform the insert inside the trigger. Something along these lines.
CREATE TRIGGER [dbo].[tr_AuthorExists] ON [dbo].[Authors]
instead of insert
AS
set nocount on;
if exists
(
select * from Authors a
inner join inserted i on i.author_fname = a.author_fname AND i.author_lname = a.author_lname
)
begin
rollback
RAISERROR ('Author exists in table already!', 16, 1);
End
else
insert Authors
select i.author_fname
, i.author_lname
, i.author_middle
from inserted i

Related

how to add row in a many to many self relationship table and be coherence?

I have a table with a many to many self relationship. So I have this two Tables.
TableA
{
IDTableA;
Data;
}
Realtionships
{
IDTableA;
IDTableARelated;
}
I would like to know now, for a row in TableA, which rows in TableA are related with this row.
I have two options, to add only one row to Relationsips table, for example (1,2), in this case 2 is related with 1, but how 2 is related with 1, 1 is related with 2 too. But this has the problem how to avoid duplicates. For example, if one precess is trying to add (1,2) and other precess try to add (2,1), how could I avoid this duplicate?
So the second option it is to add always the two rows, (1,2) and (2,1). But I am not sure how to ensure that always I will have the two rows. I was thinking in this two queries:
Begin tran
insert into Relations(IDTableA, IDTableARelated) VALUES(1,2);
insert into Relations(IDTableA, IDTableARelated) VALUES(2,1);
commint
Begin tran
delete TableAB where IDTableA = 1 and IDTableARelated = 2;
delete TableAB where IDTableA = 2 and IDTableARelated = 1;
commit
But in my tests, I can get results in which at the end I have only one of the rows. For example, if the row (1,2) exists, it can happens this:
First process try to add the row (1,2).
Second process try to delete the row (2,1);
First process try to add (2,1);
Second process try to delete (1,2);
the final result is that I have only the row (2,1), so it is no coherence, I should have the row (1,2) too.
Really in my case I could choose the option one or option two, but I would like to know if there are some way with tsql to ensure the coherence of the data, if it is option 1, to avoid duplicates, if it is the second solution, ensure that always I will have the two rows.
Thanks.
EDIT: I add the script of the database with:
USE [TestRelacionN-N]
GO
/****** Object: Table [dbo].[TablaA] Script Date: 26/05/2019 11:07:52 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TablaA](
[IDTablaA] [bigint] IDENTITY(1,1) NOT NULL,
[Dato] [varchar](50) NULL,
CONSTRAINT [PK_TablaA] PRIMARY KEY CLUSTERED
(
[IDTablaA] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[TableAWithTableA] Script Date: 26/05/2019 11:07:52 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TableAWithTableA](
[IDTableA] [bigint] NOT NULL,
[IDTableARelated] [bigint] NOT NULL,
CONSTRAINT [PK_TableAWithTableA] PRIMARY KEY CLUSTERED
(
[IDTableA] ASC,
[IDTableARelated] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[TablaA] ON
INSERT [dbo].[TablaA] ([IDTablaA], [Dato]) VALUES (1, N'Dato01')
INSERT [dbo].[TablaA] ([IDTablaA], [Dato]) VALUES (2, N'Dato02')
INSERT [dbo].[TablaA] ([IDTablaA], [Dato]) VALUES (3, N'Dato03')
SET IDENTITY_INSERT [dbo].[TablaA] OFF
INSERT [dbo].[TableAWithTableA] ([IDTableA], [IDTableARelated]) VALUES (1, 2)

How to write T-SQL Recursive Query to return hierarchical data using common self-joining table to store tree data

Every time I try to revisit recursive queries I feel like I am starting over. I am wanting to query data from a table that stores hierarchical data using a common method, having a table that self-joins.
First of all, there is this table that stores "Groups", i think of as being "folders" using a Windows Explorer analogy. The ID is the PK and there is an associated group Name.
CREATE TABLE [dbo].[BPAGroup](
[id] [varchar](10) NOT NULL,
[name] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_BPAGroup] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Second and lastly, there is a relationship table that associates a group with its parent group. "GroupID" identifies "MemberID"'s group. For example, if you join up BPAGroupGroup.MemberID to [BPAGroup].ID, [BPAGroup].Name would contain the name of the group. if you join up BPAGroupGroup.GroupID to [BPAGroup].ID, [BPAGroup].Name would contain the name of the PARENT group.
CREATE TABLE [dbo].[BPAGroupGroup](
[memberid] [varchar](10) NOT NULL,
[groupid] [varchar](10) NOT NULL,
CONSTRAINT [PK_BPAGroupGroup] PRIMARY KEY CLUSTERED
(
[memberid] ASC,
[groupid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
-------------------------
ALTER TABLE [dbo].[BPAGroupGroup] WITH CHECK ADD CONSTRAINT [FK_BPAGroupGroup_BPAGroup_groupid] FOREIGN KEY([groupid])
REFERENCES [dbo].[BPAGroup] ([id])
GO
ALTER TABLE [dbo].[BPAGroupGroup] CHECK CONSTRAINT [FK_BPAGroupGroup_BPAGroup_groupid]
GO
ALTER TABLE [dbo].[BPAGroupGroup] WITH CHECK ADD CONSTRAINT [FK_BPAGroupGroup_BPAGroup_memberid] FOREIGN KEY([memberid])
REFERENCES [dbo].[BPAGroup] ([id])
GO
ALTER TABLE [dbo].[BPAGroupGroup] CHECK CONSTRAINT [FK_BPAGroupGroup_BPAGroup_memberid]
GO
Here's some sample data in the form
Level1
Level2
Level3
and the SQL for the data
INSERT [dbo].[BPAGroup] ([id], [name]) VALUES (N'A', N'Level1')
INSERT [dbo].[BPAGroup] ([id], [name]) VALUES (N'B', N'Level2')
INSERT [dbo].[BPAGroup] ([id], [name]) VALUES (N'C', N'Level3')
INSERT [dbo].[BPAGroupGroup] ([memberid], [groupid]) VALUES (N'B', N'A')
INSERT [dbo].[BPAGroupGroup] ([memberid], [groupid]) VALUES (N'C', N'B')
How can I write a recursive T-SQL Server Query that returns all of the groups names, the recursion "level number" and IDs for all of the groups. Of course, the root of the tree would have a NULL ParentID and ParentName?
For example, these fields would be in the result set.
Level, GroupID, GroupName, ParentId, ParentName
I realize that there are multiple ways to store this type of data. I don't have flexibility to change the db design.
Ideally, the result should show all group names, even the root node, that doesn't have a parent.
Based on the latest data, this appears to get you the result you want:
WITH rCTE AS(
SELECT 1 AS Level,
id AS GroupID,
[name] AS GroupName,
CONVERT(nvarchar(10),NULL) AS ParentID, --This'll be uniqueidentifier in your real version
CONVERT(nvarchar(255),NULL) AS ParentName
FROM BPAGroup G
WHERE NOT EXISTS (SELECT 1
FROM BPAGroupGroup e
WHERE e.memberid = G.id)
UNION ALL
SELECT r.Level + 1,
G.id AS GroupID,
G.[name] AS GroupName,
r.GroupID AS ParentID,
r.[GroupName] AS ParentName
FROM BPAGroup G
JOIN BPAGroupGroup GG ON G.id = GG.memberid
JOIN rCTE r ON GG.groupid = r.GroupID)
SELECT *
FROM rCTE;
db<>fiddle
It's important you understand how this works though. As you said in your post, you seem to need to revisit these each time. There's nothing wrong with needing to check the syntax for something (there's some things I fail miserably at remembering sometimes, especially the new OPENJSON stuff), but do you understand how this works? If not, which bit don't you?

Optimaze SQL Database table for multiple writes single read

I am developing a feature that will be used as processes progress monitor.
I will span 40-50 threads that might take several minutes or even hours to finish and they will update it's status to a datatable.
From the web app i will create a polling mechanism that will read the processes status using one read every 0.5 sec.
I need to optimize the table for multiple writes at a sec and one read per 0.5 sec. I don't care if I read a dirty state since it's just for monitoring the process, it's not that critical.
This is the table I am using
CREATE TABLE [cmn].[ProcessProgress]
(
[id] [bigint] NOT NULL,
[status] [smallint] NOT NULL,
[step] [int] NOT NULL,
[max_step] [int] NOT NULL,
CONSTRAINT [PK_ProcessProgress] PRIMARY KEY CLUSTERED
(
[id] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
)
GO
And the select with (NOLOCK) I suppose I have to use
SELECT * FROM [cmn].[ProcessProgress] (NOLOCK)
Do I have to use a transaction with a special ISOLATION LEVEL or (nolock) is sufficient? Or (nolock) will make thinks worse?
Can you suggest what would be the most optimized solution for this problem?
Just set the READ_COMMITTED_SNAPSHOT option on your database, and the readers and writers will never conflict. Instead they will use Row Versioning:
alter database current set read_committed_snapshot on
In addition to increasing the concurrency and scalability of your application by eliminating blocking between readers and writers, it eliminates many deadlocks, and removes the incentive to perform dirty reads.
Here is an example (hope id did not make too many mistakes). test code at the bottom is what would be executed by each thread.
if object_id('ProcessProgress') is not null
drop table ProcessProgress
Go
CREATE TABLE [ProcessProgress]
(
[id] [bigint] NOT NULL IDENTITY(1,1), --added identity to shorten sample dev
[status] [smallint] NOT NULL, --1 -ready,2-inprogress, 3-complete
[step] [int] NOT NULL,
[max_step] [int] NOT NULL,
CONSTRAINT [PK_ProcessProgress] PRIMARY KEY CLUSTERED
(
[id] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
)
GO
SET NOCOUNT ON
INSERT INTO [ProcessProgress]
(status, step, max_step)
VALUES
(1, 1, 1)
GO 1000
Go
IF OBJECT_ID('StartWork') is not null drop proc StartWork
GO
CREATE PROC StartWork
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
;WITH TODO
AS (
SELECT TOP 1 Id, [status] from ProcessProgress WITH (ROWLOCK, READPAST) WHERE [status] = 1 --ready
)
UPDATE TODO
SET [status] = 2 --InProgress
OUTPUT inserted.id
COMMIT
END
GO
IF OBJECT_ID('FinishWork') is not null drop proc FinishWork
GO
CREATE PROC FinishWork
#id int
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
Update ProcessProgress
SET [Status] = 3 --finished
WHERE
id = #id
COMMIT
END
GO
/*tester*/
declare #idout table (id int)
insert into #idout exec StartWork
declare #idin int = (Select top 1 id from #idout)
exec FinishWork #idin

SQL - INSERT statement conflicted with FOREIGN KEY constraint - Value already in parent table

I'm aware that this question gets asked a million times, but I've already checked, double, and triple that corresponding values already exist in my parent table. I'm trying to populate a bridge table between my Albums and Artists, both of which are already populated. After getting errors in the C# program that was auto-populating, I tried inserting a single value manually, and still got the INSERT error.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Album](
[Album ID] [int] IDENTITY(1,1) NOT NULL,
[Album Title] [nchar](50) NOT NULL,
[Release Year] [int] NULL,
CONSTRAINT [PK_Album] PRIMARY KEY CLUSTERED
(
[Album ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[Album-Artists] Script Date: 3/6/2015 12:49:33 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Album-Artists](
[Album ID] [int] NOT NULL,
[Artist ID] [int] NOT NULL,
CONSTRAINT [PK_Album-Artists] PRIMARY KEY CLUSTERED
(
[Album ID] ASC,
[Artist ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[Artists] Script Date: 3/6/2015 12:49:33 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Artists](
[Artist ID] [int] IDENTITY(1,1) NOT NULL,
[Artists Name] [nchar](20) NOT NULL,
CONSTRAINT [PK_Artists] PRIMARY KEY CLUSTERED
(
[Artist ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
GO
ALTER TABLE [dbo].[Album-Artists] WITH CHECK ADD CONSTRAINT [FK_Album-Artists_Album] FOREIGN KEY([Album ID])
REFERENCES [dbo].[Album] ([Album ID])
GO
ALTER TABLE [dbo].[Album-Artists] CHECK CONSTRAINT [FK_Album-Artists_Album]
GO
ALTER TABLE [dbo].[Album-Artists] WITH CHECK ADD CONSTRAINT [FK_Album-Artists_Artists] FOREIGN KEY([Album ID])
REFERENCES [dbo].[Artists] ([Artist ID])
GO
ALTER TABLE [dbo].[Album-Artists] CHECK CONSTRAINT [FK_Album-Artists_Artists]
GO
(I've tried with and without the table quantifiers (Artists.[Artist ID], etc))
Your foreign key is trying to match ArtistId to AlbumId.
This is incorrect. You need to recreate it.
ALTER TABLE dbo.[Album-Artists]
DROP CONSTRAINT [FK_Album-Artists_Artists];
ALTER TABLE [dbo].[Album-Artists]
WITH CHECK ADD CONSTRAINT [FK_Album-Artists_Artists]
FOREIGN KEY([Artist ID]) REFERENCES [dbo].[Artists] ([Artist ID]);
Also the correct syntax for the insert is
INSERT INTO [dbo].[Album-Artists]
([Album ID], [Artist ID])
VALUES (10, 3);
Though dot-separated prefixes are currently ignored in the column list for INSERT statements.
Finally I would avoid using spaces or - in object names so you don't have to continually use quoted identifiers or square brackets and can use the unquoted form.
INSERT INTO dbo.AlbumArtists
(AlbumId, ArtistId)
VALUES (10, 3);

Trigger Not Putting Data in History Table

I have the following trigger (along with others on similar tables) that sometimes fails to put data into the historic table. It should put data into a historic table exactly as it's inserted/updated and stamped with a date.
CREATE TRIGGER [dbo].[trig_UpdateHistoricProductCustomFields]
ON [dbo].[productCustomFields]
AFTER UPDATE,INSERT
AS
BEGIN
IF ((UPDATE(data)))
BEGIN
SET NOCOUNT ON;
DECLARE #date bigint
SET #date = datepart(yyyy,getdate())*10000000000+datepart(mm,getdate())*100000000+datepart(dd,getdate())*1000000+datepart(hh,getdate())*10000+datepart(mi,getdate())*100+datepart(ss,getdate())
INSERT INTO historicProductCustomFields (productId,customFieldNumber,data,effectiveDate) (SELECT productId,customFieldNumber,data,#date from inserted)
END
END
Schema:
CREATE TABLE [dbo].[productCustomFields](
[id] [int] IDENTITY(1,1) NOT NULL,
[productId] [int] NOT NULL,
[customFieldNumber] [int] NOT NULL,
[data] [varchar](50) NULL,
CONSTRAINT [PK_productCustomFields] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[historicProductCustomFields](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[productId] [int] NOT NULL,
[customFieldNumber] [int] NOT NULL,
[data] [varchar](50) NULL,
[effectiveDate] [bigint] NOT NULL,
CONSTRAINT [PK_historicProductCustomFields] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
I insert and update only on one record at a time on the productCustomFields table. It seems to work 99% of the time and hard to test for failure. Can anyone shed some light on what I may be doing wrong or better practices for this type of trigger?
Environment is Sql Server Express 2005. I haven't rolled out the service pack yet for sql server either for this particular client.
I think the right way to solve this is keep a TRY CATCH block when inserting into the dbo.historicProductCustomFields table and write the errors into a custom errorlog table. From there it is easy to track this down.
I also see a PK on the historicProductCustomFields table but if you insert and update a given record in ProductCustomFields table then won't you get primary key violations on the historicProductCustomFields table?
You should schema qualify your table that you are inserting into.
You should check to ensure that there are not multiple triggers on the table, as if there are, only 1 trigger for that type of trigger will fire and if there are 2 defined, they are run in random order. In other words, 2 triggers of the same type (AFTER INSERT) then one would fire and the other would not, but you don't necessary have control as to which will fire.
try to use this trigger. i just give you example try to write trigger with this trigger.
create TRIGGER [dbo].[insert_Assets_Tran]
ON [dbo].[AssetMaster]
AFTER INSERT , UPDATE
AS BEGIN
DECLARE #isnum TINYINT;
SELECT #isnum = COUNT(*) FROM inserted;
IF (#isnum = 1)
INSERT INTO AssetTransaction
select [AssetId],[Brandname],[SrNo],[Modelno],[Processor],[Ram],[Hdd],[Display],[Os],[Office],[Purchasedt]
,[Expirydt],[Vendor],[VendorAMC],[Typename],[LocationName],[Empid],[CreatedBy],[CreatedOn],[ModifiedBy]
,[ModifiedOn],[Remark],[AssetStatus],[Category],[Oylstartdt],[Oylenddt],[Configuration]
,[AStatus],[Tassign]
FROM inserted;
ELSE
RAISERROR('some fields not supplied', 16, 1)
WITH SETERROR;
END

Resources