Can I make this trigger shorter / better / faster? - sql-server

The task itself is very basic. I have a transaction table that moves a certain quantity of a product in or out of stock. It becomes a bit more difficult knowing that a product can be composed from multiple 'base' products and only the base products may exist in stock.
My current trigger works but works like this:
Create temporary table that can combine inserted, deleted and composed products.
Add inserted non composed products to this temp table
Add inserted composed products
Subtract deleted non composed products
Subtract deleted composed products
I now have a table containing all transactions of non composed products. But i.e. with an update of a row, the same product is added twice. Once subtracting the old value and once adding the new value.
Create second temporary table for the joined non composed products
Take a sum of the products and put these into the new temp table
Merge the stock with this temp table
Again.. my trigger works fine and i'm having no performance problem (yet) but I feel i'm missing something. I bet there is someone around here with a better solution then mine.
My tables and sample data: (fiddle here)
CREATE TABLE [dbo].[Product](
[id] [int] IDENTITY(1,1) NOT NULL,
[plantId] [int] NOT NULL,
[reference] [nvarchar](50) NOT NULL,
[composed] [bit] NOT NULL DEFAULT ((0)),
[name] [nvarchar](max) NOT NULL,
[unit] [nvarchar](50) NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate())
);
INSERT INTO [dbo].[Product] VALUES (2, 'FILLER VULPROF 2 BR', 0, 'FILLER VULPROF 2 BR', 'ton', GETDATE());
INSERT INTO [dbo].[Product] VALUES (2, 'K 0/2GEW BR', 0, 'K 0/2GEW BR', 'ton', GETDATE());
INSERT INTO [dbo].[Product] VALUES (2, 'K 14/20 BR', 0, 'K 14/20 BR', 'ton', GETDATE());
INSERT INTO [dbo].[Product] VALUES (2, 'KWS BR 3A31', 1, 'KWS BR 3A31', 'ton', GETDATE());
CREATE TABLE [dbo].[ProductComposition](
[id] [int] IDENTITY(1,1) NOT NULL,
[productId] [int] NOT NULL,
[parentId] [int] NOT NULL, -- This is the parent product
[quantity] [float] NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate())
);
INSERT INTO [dbo].[ProductComposition] VALUES (1, 4, 0.001, GETDATE());
INSERT INTO [dbo].[ProductComposition] VALUES (3, 4, 0.001, GETDATE());
CREATE TABLE [dbo].[Transaction](
[id] [int] IDENTITY(1,1) NOT NULL,
[load] [bit] NOT NULL,
[plantId] [int] NOT NULL,
[vehicleId] [int] NOT NULL,
[productId] [int] NOT NULL,
[contactId] [int] NOT NULL,
[quantity] [float] NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate()),
[isSynched] [bit] NOT NULL CONSTRAINT [DF_Transaction_isSynched] DEFAULT ((0))
);
CREATE TABLE [dbo].[Stock](
[id] [int] IDENTITY(1,1) NOT NULL,
[plantId] [int] NOT NULL,
[productId] [int] NOT NULL,
[quantity] [float] NOT NULL,
[dateUpdate] [datetime] NOT NULL DEFAULT (getdate())
);
My Trigger:
ALTER TRIGGER [dbo].[StockCalculate]
ON [dbo].[Transaction]
AFTER INSERT, UPDATE, DELETE AS
BEGIN
-- Create temporary table
CREATE TABLE #TransactionsComposed (
[load] [bit] NOT NULL,
[plantId] [int] NOT NULL,
[productId] [int] NOT NULL,
[quantity] [float] NOT NULL
);
-- Insert basic materials
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
[Inserted].[load],
[Inserted].[plantId],
[Inserted].[productId],
[Inserted].[quantity]
FROM
[Inserted]
INNER JOIN [Product] ON [Product].[id] = [Inserted].[productId]
WHERE
[Product].[composed] = 0;
-- Insert operations
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
[Inserted].[load],
[Inserted].[plantId],
[ProductComposition].[productId],
([Inserted].[quantity] * [ProductComposition].[quantity])
FROM
[Inserted]
INNER JOIN [Product] ON [Product].[id] = [Inserted].[productId]
INNER JOIN [ProductComposition] ON [ProductComposition].[parentId] = [Product].[id]
WHERE
[Product].[composed] = 1;
-- Insert basic materials but ALTER it's load status.
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
CASE [Deleted].[load]
WHEN 0 THEN 1
WHEN 1 THEN 0
END,
[Deleted].[plantId],
[Deleted].[productId],
[Deleted].[quantity]
FROM
[Deleted]
INNER JOIN [Product] ON [Product].[id] = [Deleted].[productId]
WHERE
[Product].[composed] = 0;
-- Insert operations but ALTER it's load status.
INSERT INTO #TransactionsComposed (
[load], [plantId], [productId], [quantity])
SELECT
CASE [Deleted].[load]
WHEN 0 THEN 1
WHEN 1 THEN 0
END,
[Deleted].[plantId],
[ProductComposition].[productId],
([Deleted].[quantity] * [ProductComposition].[quantity])
FROM
[Deleted]
INNER JOIN [Product] ON [Product].[id] = [Deleted].[productId]
INNER JOIN [ProductComposition] ON [ProductComposition].[parentId] = [Product].[id]
WHERE
[Product].[composed] = 1;
-- Prepare multiple products for merge
CREATE TABLE #TransactionsJoined (
[plantId] [int] NOT NULL,
[productId] [int] NOT NULL,
[quantity] [float] NOT NULL
);
INSERT INTO #TransactionsJoined
([plantId], [productId], [quantity])
SELECT
[plantId],
[productId],
SUM(CASE [load]
WHEN 0 THEN [quantity]
WHEN 1 THEN 0 - [quantity]
END)
FROM
#TransactionsComposed
GROUP BY [plantId], [productId]
-- Merge composed transactions into the stock
MERGE [Stock]
USING #TransactionsJoined
ON [Stock].[plantId]=#TransactionsJoined.[plantId]
AND [Stock].[productId]=#TransactionsJoined.[productId]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([plantId], [productId], [quantity])
VALUES (
#TransactionsJoined.[plantId],
#TransactionsJoined.[productId],
#TransactionsJoined.[quantity])
WHEN MATCHED THEN
UPDATE SET
[Stock].[quantity] = [Stock].[quantity] + #TransactionsJoined.[quantity],
[Stock].[dateUpdate] = GETDATE();
END

Related

After insert trigger doesn't work when using Inserted

I'm trying to write a trigger on my Employees table that should not allow the insertion of a new employee that has a hire date that is older than the hire date of his boss
CREATE TABLE [dbo].[Employees]
(
[EID] [int] IDENTITY(1,1) NOT NULL,
[Ename] [nvarchar](20) NOT NULL,
[Gender] [nvarchar](1) NOT NULL,
[IsMarried] [nvarchar](1) NOT NULL,
[Birthdate] [date] NOT NULL,
[HireDate] [date] NOT NULL,
[Salary] [float] NOT NULL,
[Notes] [nvarchar](200) NULL,
[NationalityID] [int] NULL,
[BossID] [int] NULL,
CONSTRAINT [PK_Employees]
PRIMARY KEY CLUSTERED ()
)
And here's the trigger code:
CREATE TRIGGER [dbo].[Trig_04]
ON [dbo].[Employees]
AFTER INSERT
AS
BEGIN
IF ((SELECT INSERTED.HireDate FROM INSERTED WHERE BossID <> EID) <
(SELECT Employees.HireDate FROM Employees
WHERE EID IN (SELECT Employees.BossID FROM Employees WHERE BossID <> EID)))
ROLLBACK
END
It executes normally (no errors) but it just doesn't work, but when I was using the employees table in the subquery instead of the inserted table, it was working normally. Does anyone have an answer for this?
You have to write triggers in SQL Server to handle the fact that INSERTED could contain multiple records. You cannot assume it will only be a single record. I think the following is what you are looking for:
if exists (
select 1
from Inserted I
where I.BossID <> I.EID
and I.HireDate < (select E.HireDate from Employees E where E.EID = I.BossID)
) begin
ROLLBACK;
end

How to avoid duplicate record while inserting data using user defined table type in SQL Server

I am trying to insert entire model in database using my .net application. I am using a user-defined table type.
This is my procedure and user-defined table; I am using SQL Server 2012.
CREATE TYPE [dbo].[TmpAccessRequest] AS TABLE
(
[RequestId] [int] NULL,
[RequesterID] [int] NULL,
[RequestType] [int] NULL,
[NextApprover] [int] NULL,
[RequestStatus] [varchar](100) NULL,
[Delegation] [int] NULL,
[CreatedOn] [date] NULL,
[CreatedBy] [varchar](100) NULL,
[Description] [varchar](max) NULL,
[IsSepecialRequest] [bit] NULL,
[DelegationDetailID] [int] NULL,
[IsActive] [bit] NULL,
[IsDeleted] [bit] NULL,
[ModifiedOn] [date] NULL
)
GO
CREATE PROCEDURE [dbo].[proc_SaveAccessRequest]
(#TmpAR TmpAccessRequest READONLY,
#IsUAMSRequest BIT,
#RequestID INT OUTPUT)
AS
BEGIN
INSERT INTO tblRequests (RequesterID, RequestType, NextApprover, RequestStatus,
Delegation, CreatedOn, CreatedBy, Description,
IsSepecialRequest, DelegationDetailID, IsActive, IsDeleted, ModifiedOn)
SELECT
RequesterID, RequestType, NextApprover, RequestStatus,
Delegation, CreatedOn, CreatedBy, Description,
IsSepecialRequest, DelegationDetailID, IsActive, IsDeleted, ModifiedOn
FROM
#TmpAR
SET #RequestID = SCOPE_IDENTITY()
--SET #RequestID=IDENT_CURRENT('tblRequests')
SELECT #RequestID
END
I want to check if duplicate data should not insert at the same time. So how can I do that with user-defined table type ?
Please find the changes done to your script to avoid inserting duplicate record. So i considered two columns data should be unique to avoid duplication for user understanding purpose
CREATE PROCEDURE [dbo].[proc_SaveAccessRequest]
(
#TmpAR TmpAccessRequest READONLY,
#IsUAMSRequest bit,
#RequestID int OUTPUT
)
AS
BEGIN
Insert into tblRequests
(
RequesterID
,RequestType
,NextApprover
,RequestStatus
,Delegation
,CreatedOn
,CreatedBy
,[Description]
,IsSepecialRequest
,DelegationDetailID
,IsActive
,IsDeleted
,ModifiedOn
)
SELECT
RequesterID
,RequestType
,NextApprover
,RequestStatus
,Delegation
,CreatedOn
,CreatedBy
,Description
,IsSepecialRequest
,DelegationDetailID
,IsActive
,IsDeleted
,ModifiedOn
FROM #TmpAR
WHERE NOT EXISTS ( SELECT 1
FROM tblRequests i
INNER JOIN #TmpAR o
ON i.RequesterID = o.RequesterID
AND i.RequestType = o.RequestType
AND i.NextApprover = o.NextApprover)
SELECT #RequestID = SCOPE_IDENTITY()
SELECT #RequestID
END

Join table where table name is a value in another table

I have been tasked with getting some data out of another database not designed by me, so the design cannot be altered.
Looking at the data I need to join a table dynamically based on the value held in another table.
E.G
SELECT * FROM TableA LEFT JOIN TableB oN TableA.TBID = TableB.ID LEFT JOIN TableB.TableOffset AS C ON C.TableAID = TableA.ID
So table B has a column called TableOffset, this holds the name of the table that needs to be joined on as C
(I've tried to do an SQLFIDDLE but the site isn't working with SQL Server ATM)
The issue is there are 112 attribute tables where the data could be, so doing a left join for every one of them would slow down the query I imagine.
So based on the below I would need to get a result set of:
| TableAID | TableATitle | AttrTableA21 | AttrTableA22 |
|----------|-------------|--------------|--------------|
|1 |test | Name | 2019 |
|2 |test2 | Name 2 | 2016 |
Example SQL Code
CREATE TABLE [dbo].[TableA](
[ID] [int] NOT NULL,
[Title] [nvarchar](100) NOT NULL,
[TableBID] [int] NOT NULL
);
CREATE TABLE [dbo].[TableB](
[ID] [int] NOT NULL,
[TableName] [nvarchar](100) NOT NULL
);
CREATE TABLE [dbo].[ATTR_A](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
CREATE TABLE [dbo].[ATTR_B](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
CREATE TABLE [dbo].[ATTR_C](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
CREATE TABLE [dbo].[ATTR_D](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
INSERT INTO TableA VALUES(1, 'test', 1);
INSERT INTO TableB VALUES(1, 'ATTR_C');
INSERT INTO ATTR_C VALUES (1, 1, 'Name', 2019);
INSERT INTO TableA VALUES(2, 'test2', 2);
INSERT INTO TableB VALUES (2, 'ATTR_A');
INSERT INTO ATTR_A VALUES (1, 2, 'Name 2', 2016);
```
You were told already, that this approach is the wrong one entirely. But if you have to stick with this, you might try this:
Your test scenario in a new database (carefull, if you use this database name already...)
USE master;
GO
CREATE DATABASE MyTestDb;
GO
USE MyTestDb;
GO
CREATE TABLE [dbo].[TableA](
[ID] [int] NOT NULL,
[Title] [nvarchar](100) NOT NULL,
[TableBID] [int] NOT NULL
);
CREATE TABLE [dbo].[TableB](
[ID] [int] NOT NULL,
[TableName] [nvarchar](100) NOT NULL
);
CREATE TABLE [dbo].[ATTR_A](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
CREATE TABLE [dbo].[ATTR_B](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
CREATE TABLE [dbo].[ATTR_C](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
CREATE TABLE [dbo].[ATTR_D](
[ID] [int] NOT NULL,
[TableAID] [int] NOT NULL,
[A21] [nvarchar](100) NOT NULL,
[A22] [int] NOT NULL
);
INSERT INTO TableA VALUES(1, 'test', 1);
INSERT INTO TableB VALUES(1, 'ATTR_C');
INSERT INTO ATTR_C VALUES (1, 1, 'Name', 2019);
INSERT INTO TableA VALUES(2, 'test2', 2);
INSERT INTO TableB VALUES (2, 'ATTR_A');
INSERT INTO ATTR_A VALUES (1, 2, 'Name 2', 2016);
GO
--I create an inlined table valued function where all attribute tables are returned as one set using UNION ALL
--The engine is smart enough, to avoid the call, if the parameter does not fit.
CREATE FUNCTION dbo.GetTheRightSet(#SetKey VARCHAR(10))
RETURNS TABLE
AS
RETURN
SELECT ID,TableAID,A21,A22 FROM dbo.ATTR_A WHERE #SetKey='ATTR_A'
UNION ALL
SELECT ID,TableAID,A21,A22 FROM dbo.ATTR_B WHERE #SetKey='ATTR_B'
UNION ALL
SELECT ID,TableAID,A21,A22 FROM dbo.ATTR_C WHERE #SetKey='ATTR_C'
UNION ALL
SELECT ID,TableAID,A21,A22 FROM dbo.ATTR_D WHERE #SetKey='ATTR_D'
GO
--This is how to use it
SELECT TableA.*
,TableB.*
,TheSet.*
FROM TableA
LEFT JOIN TableB ON TableA.TableBID = TableB.ID
OUTER APPLY dbo.GetTheRightSet(TableB.TableName) TheSet
GO
--Clean up (carefull with real data)
USE master;
GO
DROP DATABASE MyTestDb;

Compare two child table items with two similar parent tables from two databases

I need help creating a query to compare the Equipment in one database to Asset in another table. Here is my database setup:
CREATE DATABASE database1
GO
USE [database1]
GO
CREATE TABLE [dbo].[Application]
(
[ApplicationID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[APP_NUMBRER] [int] NULL,
)
GO
CREATE TABLE [dbo].[Equipment]
(
[EquipID] [int] IDENTITY(1,1) NOT NULL,
[ApplicationID] [int] NULL,
[Year] [varchar](4) NULL,
[Make] [varchar](50) NULL,
[Model] [varchar](50) NULL
)
ALTER TABLE [dbo].[Equipment] WITH CHECK
ADD CONSTRAINT [FK_EQUIP_1]
FOREIGN KEY([ApplicationID]) REFERENCES [dbo].[APPLICATION] ([ApplicationID])
GO
INSERT INTO [Application]
VALUES (1), (2), (3)
INSERT INTO [Equipment]
VALUES (1, '1998', 'Equip1', 'Model1'),
(1, '1855', 'Equip2', 'Model2'),
(2, '1222', 'Equip3', 'Model4'),
(2, '1333', 'Equip4', 'Model4'),
(3, '1777', 'Equip5', 'Model5')
GO
CREATE DATABASE database2
GO
USE [database2]
GO
CREATE TABLE [dbo].[Application]
(
[APP_KEY] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[APP_DESCRIPTION] [varchar](40) NOT NULL
)
GO
CREATE TABLE [dbo].[ASSET]
(
[AS_KEY] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[AS_APP_FKEY] [int] NOT NULL,
[Year] [varchar](4) NULL,
[Make] [varchar](50) NULL,
[Model] [varchar](50) NULL
)
ALTER TABLE [dbo].[ASSET] WITH CHECK
ADD CONSTRAINT [FK_ASSET_1]
FOREIGN KEY([AS_APP_FKEY]) REFERENCES [dbo].[APPLICATION] ([APP_KEY])
GO
INSERT INTO [Application]
VALUES ('AppDesc1'), ('AppDesc2')
INSERT INTO [ASSET]
VALUES (1, '1998', 'Asset1', 'Db2Model1'),
(1, '1855', 'Asset2', 'Db2Model2'),
(2, '1222', 'Asset3', 'Db2Model3'),
(2, '1333', 'Asset4', 'Db2Model4')
GO
My query:
SELECT
ap1.APP_NUMBRER,
e.Year, e.Make, e.Model,
db2.APP_KEY, db2.Year, db2.Make, db2.Model
FROM
database1.dbo.Application ap1
JOIN
database1.dbo.Equipment e ON E.APPLICATIONID = ap1.APPLICATIONID
LEFT JOIN
(SELECT
APP_KEY, Year, Make, Model
FROM
[database2].dbo.APPLICATION ap2
JOIN
[database2].dbo.ASSET ON asset.AS_APP_FKEY = ap2.APP_KEY) db2 ON ap1.APP_NUMBRER = db2.APP_KEY
Result:
Expected result: my query is creating a few duplicate items that compare all of db1 equipment to db2 assets. I want a one to one comparison. I don't want items 2,3,6,7. Is this because of how the table relationships are set up.
It's because you're only joining on APP_NUMBER=APP_KEY. Add Year=Year to the JOIN and you will get your desired result.
Add YEAR in you join. It will solve your problem.
SELECT ap1.APP_NUMBRER, e.Year, e.Make, e.Model, db2.APP_KEY, db2.Year, db2.Make, db2.Model
FROM database1.dbo.Application ap1
JOIN database1.dbo.Equipment e
ON E.APPLICATIONID = ap1.APPLICATIONID
LEFT JOIN(
SELECT APP_KEY, Year, Make, Model
FROM [database2].dbo.APPLICATION ap2
JOIN [database2].dbo.ASSET
ON asset.AS_APP_FKEY = ap2.APP_KEY
) db2
ON ap1.APP_NUMBRER = db2.APP_KEY AND e.Year = db2.Year
Amend your JOIN condition like so
db2 ON ap1.APP_NUMBRER = db2.APP_KEY
AND e.Year = db2.Year

SQl results duplication

I have 3 tables
CREATE TABLE [Distributors]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](max) NULL,
)
INSERT INTO [Distributors]([Name]) VALUES ('A')
INSERT INTO [Distributors]([Name]) VALUES ('B')
CREATE TABLE [Points]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Distributor_ID] [int] NULL,
[Point_Count] [int] NULL,
[Point_Type] [nvarchar](max) NULL,
[Point_Level] [int] NULL,
[From_Distributor] [int] NULL,
[Is_Calculated] [bit] NULL,
[Point_Date] [datetime]
)
INSERT INTO [Points] ([Distributor_ID], [Point_Count], [Point_Type],[Point_Level], [From_Distributor], [Is_Calculated], [Point_Date])
VALUES(1, 1, 'AN', '', 2, 'True', '2015-02-16')
INSERT INTO [Points] ([Distributor_ID], [Point_Count], [Point_Type],[Point_Level], [From_Distributor], [Is_Calculated], [Point_Date])
VALUES(1, 1, 'Offer', '', 2, 'True', '2015-02-16')`
CREATE TABLE [dbo].[Profit]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Distributor_ID] [int] NULL,
[Profit] [decimal](18, 2) NULL,
[Percentage] [int] NULL,
[Profit_Date] [datetime] NULL,
[From_Distributor_ID] [int] NULL,
[Is_Paid] [bit] NULL,
[Paid_By] [int] NULL
)
INSERT INTO [Profit]([Distributor_ID], [Profit], [Percentage],[Profit_Date], [From_Distributor_ID], [Is_Paid], [Paid_By])
VALUES (1, '80', 10, '2015-02-16', 2, 'False', 0)
INSERT INTO [Profit]([Distributor_ID], [Profit], [Percentage],[Profit_Date], [From_Distributor_ID], [Is_Paid], [Paid_By])
VALUES (1, '40', 5, '2015-02-16', 2, 'False', 0)
When I do the select query with join statement I face a duplication
SELECT distinct Points.[ID]
,Points.[Distributor_ID]
,[Point_Count]
,[Point_Type]
,[Point_Level]
,[From_Distributor]
,[Is_Calculated]
,[Point_Date]
--,Profit.Profit
FROM
[Points]
INNER JOIN
Profit ON Points.From_Distributor = Profit.From_Distributor_ID
AND Points.Distributor_ID = Profit.Distributor_ID
WHERE
(Points.[Distributor_ID] = 1)
AND ([Point_Date] >= '2/16/2015')
AND ([Point_Date] <= '2/28/2015')
results are tow rows
when using " Profit.Profit " in the select statement it got 4 rows
i think problem in join statement
please i need your advice

Resources