Multiple Table Insert with Merge? - sql

I am trying to insert some rows in a parent/child relationship. How does one accomplish this in SQL?
This would be the base query I'd use to find the info I would be inserting:
SELECT * FROM parentTable p
INNER JOIN childTable c
ON p.ID = c.ParentTableID
WHERE p.PlanID = 123456
What needs to happen is that I insert the ParentTable row first which is really just a copy of the matched row but with a new PlanID and Created Date. Then I take that ParentTable ID from the inserted row and use that for the same process but for the Child Table.
So I need to do in .Net speak at least is where I loop through all parentTable matches and for each match create 1 childTable row.
I was trying to use MERGE and the OUTPUT clause but it seems like I'm maybe using a square peg for a round hole. Would I be better off using a CURSOR of some sorts?
So this is what I have so far based on the answer below. Unfortunately it isn't grabbing the SCOPE_IDENTITY()...is there a trick to it? Because that is returning NULL I get FK errors on the inserts.
USE MemberCenteredPlan
DECLARE #NewPlanID BIGINT;--49727690
DECLARE #OldPlanID BIGINT;--49725211
DECLARE #NewSWReAssID BIGINT;
SET #NewPlanID = 49727690
SET #OldPlanID = 49725211
BEGIN
INSERT INTO tblSocialWorkerReAssessment
SELECT
#NewPlanID
,[Discussed]
,Discussion
,NULL
,NULL
,NULL
,CreatedBy
,GETDATE()
,NULL
,NULL
FROM tblSocialWorkerReAssessment
WHERE PlanID = #OldPlanID
SELECT #NewSWReAssID = SCOPE_IDENTITY();
INSERT INTO tblPlanDomain
SELECT
#NewPlanID
,[DomainTypeID]
,[MemberOutcomes]
,[MemberPreferences]
,[DomainDate]
,[Steps]
,[ClinicalFunctionalConcerns]
,[ReportWageES]
,[ReportWageSSA]
,#NewSWReAssID
,[CreatedBy]
,GETDATE()
,NULL
,NULL
,NEWID()
FROM tblPlanDomain
WHERE planID = #OldPlanID
END

You don't need MERGE and you definitely don't need cursors. And an INSERT (or a MERGE) can only ever affect one table at a time, so you'll need to perform multiple statements anyway. If you are only ever dealing with one plan at a time, you can do this:
DECLARE #NewPlanID INT;
INSERT dbo.ParentTable(cols...)
SELECT cols...
FROM dbo.ParentTable WHERE PlanID = 123456;
SELECT #NewPlanID = SCOPE_IDENTITY();
INSERT dbo.ChildTable(ParentTableID, cols...)
SELECT #NewPlanID, cols...
FROM dbo.ChildTable WHERE PlanID = 123456;
If you need to reference multiple new plans, it gets a little more complicated, and in that case you would need to use MERGE (at the present time, INSERT's composable DML is a little on the light side - you can't reference the source table in the OUTPUT clause).
DECLARE #p TABLE(OldPlanID INT, NewPlanID INT);
MERGE dbo.ParentTable WITH (HOLDLOCK)
USING
(
SELECT ID, cols... FROM dbo.ParentTable
WHERE ID IN (123456, 234567)
) AS src ON src.ID IS NULL
WHEN NOT MATCHED THEN INSERT(cols...)
VALUES(src.cols...)
OUTPUT src.ID, inserted.ID INTO #p;
INSERT dbo.ChildTable(ParentTableID, cols...)
SELECT p.NewPlanID, t.cols...
FROM dbo.ChildTable AS t
INNER JOIN #p AS p
ON t.ParentTableID = p.OldPlanID;
However, you should be very wary about this... I link to several issues and unresolved bugs with MERGE in this answer over on dba.SE. I've also posted a cautionary tip here and several others agree.

I think this is what you're after.
Use Northwind
GO
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#OrderAuditHolder') IS NOT NULL
begin
drop table #OrderAuditHolder
end
CREATE TABLE #OrderAuditHolder
(
[OriginalOrderID] [int] NOT NULL,
[NewOrderID] [int] NOT NULL,
[CustomerID] [nchar](5) NULL,
[EmployeeID] [int] NULL,
[OrderDate] [datetime] NULL,
[RequiredDate] [datetime] NULL,
[ShippedDate] [datetime] NULL,
[ShipVia] [int] NULL,
[Freight] [money] NULL,
[ShipName] [nvarchar](40) NULL,
[ShipAddress] [nvarchar](60) NULL,
[ShipCity] [nvarchar](15) NULL,
[ShipRegion] [nvarchar](15) NULL,
[ShipPostalCode] [nvarchar](10) NULL,
[ShipCountry] [nvarchar](15) NULL,
)
declare #ExampleOrderID int
select #ExampleOrderID = (select top 1 OrderID from dbo.Orders ords where exists (select null from dbo.[Order Details] innerOD where innerOD.OrderID = ords.OrderID ) )
print '/#ExampleOrderID/'
print #ExampleOrderID
print ''
insert into dbo.Orders (CustomerID,EmployeeID,OrderDate,RequiredDate,ShippedDate,ShipVia,Freight,ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry)
output #ExampleOrderID , inserted.OrderID,inserted.CustomerID,inserted.EmployeeID,inserted.OrderDate,inserted.RequiredDate,inserted.ShippedDate,inserted.ShipVia,inserted.Freight,inserted.ShipName,inserted.ShipAddress,inserted.ShipCity,inserted.ShipRegion,inserted.ShipPostalCode,inserted.ShipCountry
into #OrderAuditHolder
Select
CustomerID,EmployeeID,OrderDate,RequiredDate,ShippedDate,ShipVia,Freight,ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry
from dbo.Orders where OrderID = #ExampleOrderID
print '/#OrderAuditHolder/'
Select * from #OrderAuditHolder
print ''
Insert into dbo.[Order Details] ( OrderID , ProductID , UnitPrice , Quantity , Discount )
Select
holder.NewOrderID , od.ProductID , od.UnitPrice , od.Quantity , od.Discount
from #OrderAuditHolder holder
join dbo.[Order Details] od on holder.OriginalOrderID = od.OrderID
/* Note, the "join" is on the OriginalOrderID, but the inserted value is the NewOrderID */
/* below is not needed, but shows results */
declare #MaxOrderID int
select #MaxOrderID = (select MAX(OrderID) from dbo.Orders ords where exists (select null from dbo.[Order Details] innerOD where innerOD.OrderID = ords.OrderID ) )
select * from dbo.[Order Details] where OrderID = #MaxOrderID order by OrderID desc
/**/
IF OBJECT_ID('tempdb..#OrderAuditHolder') IS NOT NULL
begin
drop table #OrderAuditHolder
end
SET NOCOUNT OFF

Related

SQL Query Optimization with million of records

I am currently using below query to get record based on senderid. I have 2 million record in messagein table and also entries comes parallel in this table as well.
But it take morethen 5 sec to return result. This table having only one non-clustered index created on Providerid (include column priorityid, senderid,maskid)
Can any one sql expert help me out on this.
ALTER PROCEDURE [dbo].[GetNextSmsQueue] #NoOfRow int,
#GatewayId int
AS
BEGIN TRY
BEGIN TRAN;
CREATE TABLE #SmsIn ([Id] [bigint] NOT NULL,
[UserId] [bigint] NOT NULL,
[MaskId] [varchar](50) NOT NULL,
[Number] [varchar](20) NOT NULL,
[Message] [nvarchar](1300) NOT NULL,
[SenderId] [varchar](20) NOT NULL,
[UDH] [nvarchar](50) NULL,
[Credit] [int] NOT NULL,
[CurrentStatus] [int] NOT NULL,
[CheckDND] [bit] NULL,
[CheckFail] [bit] NULL,
[CheckBlackList] [bit] NULL,
[ProviderId] [int] NULL,
[PriorityId] [int] NULL,
[ScheduleDate] [datetime] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
[EsmClass] [nvarchar](10) NOT NULL,
[DataCoding] [int] NOT NULL,
[Priority] [int] NOT NULL,
[Interface] [int] NOT NULL);
DECLARE #PriorityIn table ([PriorityId] [int] NOT NULL);
DECLARE #COUNT bigint;
INSERT INTO #PriorityIn
SELECT PriorityId
FROM PriorityProviders
WHERE ProviderId = #GatewayId
AND Type = 0;
SELECT #COUNT = COUNT(*)
FROM MessageIn m
LEFT JOIN #PriorityIn o ON m.PriorityId = o.PriorityId
WHERE ((ProviderId IS NULL
AND o.PriorityId IS NOT NULL)
OR ProviderId = #GatewayId);
IF #COUNT > 0
BEGIN
INSERT INTO #SmsIn ([Id],
[UserId],
[MaskId],
[Number],
[Message],
[SenderId],
[UDH],
[Credit],
[CurrentStatus],
[CheckDND],
[CheckFail],
[CheckBlackList],
[ProviderId],
[PriorityId],
[ScheduleDate],
[CreatedDate],
[EsmClass],
[DataCoding],
[Priority],
[Interface])
(SELECT [Id],
[UserId],
[MaskId],
[Number],
[Message],
[SenderId],
[UDH],
[Credit],
[CurrentStatus],
[CheckDND],
[CheckFail],
[CheckBlackList],
[ProviderId],
[PriorityId],
[ScheduleDate],
[CreatedDate],
[EsmClass],
[DataCoding],
[Priority],
[Interface]
FROM MessageIn
WHERE MaskId IN (SELECT MaskId
FROM (SELECT ROW_NUMBER() OVER (PARTITION BY SenderId ORDER BY ScheduleDate) AS RowNo,
MaskId
FROM MessageIn msg
LEFT JOIN #PriorityIn o ON msg.PriorityId = o.PriorityId
WHERE ((msg.ProviderId IS NULL
AND o.PriorityId IS NOT NULL)
OR msg.ProviderId = #GatewayId)) res
WHERE res.RowNo <= #NoOfRow));
DELETE msgin
FROM MessageIn msgin
INNER JOIN #SmsIn temp ON msgin.MaskId = temp.MaskId;
END;
SELECT *
FROM #SmsIn;
DROP TABLE #SmsIn;
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END;
END CATCH;
Execution plan is available in Here:
Execution Plan
Updated Query:
BEGIN TRY
begin tran;
CREATE TABLE #tmpMaskId (MaskId varchar(25) PRIMARY KEY)
INSERT INTO #tmpMaskId(MaskId)
SELECT DISTINCT MaskId From
(SELECT ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo, MaskId FROM MessageIn msg
LEFT JOIN PriorityProviders o on o.ProviderId = #GatewayId AND o.Type = 0 and msg.PriorityId = o.PriorityId
WHERE
((msg.ProviderId is null AND o.PriorityId is not null) OR msg.ProviderId = #GatewayId)
)as res WHERE res.RowNo <= #NoOfRow
Select [Id],[UserId],m.[MaskId],[Number],[Message],[SenderId],[UDH],[Credit],[CurrentStatus],[CheckDND],[CheckFail],[CheckBlackList],[ProviderId]
,[PriorityId],[ScheduleDate],[CreatedDate],[EsmClass],[DataCoding],[Priority],[Interface]
From MessageIn m inner join #tmpMaskId msk on m.MaskId = msk.MaskId
DELETE msgin
FROM MessageIn msgin
INNER JOIN #tmpMaskId temp ON msgin.MaskId=temp.MaskId
DROP TABLE #tmpMaskId
Commit;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
END CATCH;
ALTER PROCEDURE [dbo].[GetNextSmsQueue]
#NoOfRow INT
,#GatewayId INT
AS
BEGIN TRY
BEGIN TRAN;
CREATE TABLE #tmpMaskId (MaskId INT PRIMARY KEY)
DECLARE #PriorityIn TABLE ([PriorityId] [INT] NOT NULL)
INSERT INTO #PriorityIn
SELECT PriorityId
FROM PriorityProviders
WHERE ProviderId=#GatewayId AND Type=0
INSERT INTO #tmpMaskId (MaskId)
SELECT DISTINCT MaskId
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY SenderId ORDER BY ScheduleDate) AS RowNo
,MaskId
FROM MessageIn msg
WHERE ((msg.ProviderId IS NULL AND o.PriorityId IS NOT NULL) OR msg.ProviderId=#GatewayId)
) res
WHERE res.RowNo<=#NoOfRow
SELECT [Id]
,[UserId]
,[MaskId]
,[Number]
,[Message]
,[SenderId]
,[UDH]
,[Credit]
,[CurrentStatus]
,[CheckDND]
,[CheckFail]
,[CheckBlackList]
,[ProviderId]
,[PriorityId]
,[ScheduleDate]
,[CreatedDate]
,[EsmClass]
,[DataCoding]
,[Priority]
,[Interface]
FROM MessageIn mi
WHERE EXISTS (SELECT 1 FROM #tmpMaskId AS tmi WHERE tmi.MaskId=mi.MaskId)
DELETE msgin
FROM MessageIn msgin
INNER JOIN #tmpMaskId temp ON msgin.MaskId=temp.MaskId
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT>0
BEGIN
ROLLBACK TRANSACTION;
END;
END CATCH;
DROP TABLE #tmpMaskId
IMO, As per your requirement, I will only return record from this proc to send sms.After sms is successfully I send only require id from Message table to another proc to delete those records.
Technically it sound good.your existing proc is not slow because of delete.but its not ok to delete before sending sms and again trying to insert.
In my previous scipt,I pointed that you do not need Join on PriorityProviders.
I have revise my script(INNER if possible),
SET NOCOUNT ON
BEGIN TRY
begin tran;
CREATE TABLE #tmpMaskId (MaskId varchar(25) not null)
INSERT INTO #tmpMaskId(MaskId)
SELECT MaskId From
(SELECT ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo, MaskId FROM MessageIn msg with(nolock)
LEFT JOIN PriorityProviders with(nolock)
o on o.ProviderId = msg.ProviderId and o.ProviderId= #GatewayId AND o.Type = 0 and msg.PriorityId = o.PriorityId
WHERE
((msg.ProviderId is null AND o.PriorityId is not null) OR msg.ProviderId = #GatewayId)
)as res WHERE res.RowNo <= #NoOfRow
CREATE TABLE #tmpMaskId (MaskId INT not null)
create clusetered index ix_mask on #tmpMaskId
Select [Id],[UserId],m.[MaskId],[Number],[Message],[SenderId],[UDH],[Credit],[CurrentStatus]
,[CheckDND],[CheckFail],[CheckBlackList],[ProviderId]
,[PriorityId],[ScheduleDate],[CreatedDate],[EsmClass],[DataCoding],[Priority],[Interface]
From MessageIn m
inner join
#tmpMaskId msk
on m.MaskId = msk.MaskId
DELETE msgin
FROM MessageIn msgin
where exists(select 1 from #tmpMaskId temp where msgin.MaskId=temp.MaskId)
DROP TABLE #tmpMaskId
Commit;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
END CATCH;
notice how I hv remove PK from Temp table and made it clustered Index.
How I hv remove distinct ?
Now main culprit is this statement,
ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo
I think once if you comment it then proc will perform better.
Now you need only index.
Whichever column is most Selective make that column as Clustered Index.
Since I do not know selectivity of each column I can't say whether you should make composite clustered or composite Non clustered index.
If you go for Composite Non Clustered index then make ID as clustered index(PK) and keep the most selective column on left side and so on
Composite Non Clustered index can be (maskid,ProviderId,SenderId,PriorityId)Include(other columns of message table which are require in Resultset)
I am not telling you to remove Row_number().Create composite non clustered index and rebuild index as I have describe above.
With (nolock) :It has nothing to do with data duplicity.
If there is no chance of getting uncommitted data.
If there is not much concurrency issue in message table and is not very frequently insert/updated.
Then you can safely use it.you can Google this "Advantage and disadvantage of with (Nolock)".
In one or two places you can use it if it improve your important query.
Like you said if you create index on maskid then it create deadlock.That is because of faulty script in Insert.

In SQL replace null value with another value

Here is my SQL Query
SELECT p.StudentID, ai.RollNo, p.FirstName, p.MiddleName, p.LastName,
om.ExamID, et.ExamName, om.SubjectID,
ISNULL(CONVERT(varchar(20),om.ObtainedMarksTheory), 'A') as 'ObtainedMarksTheory',
ISNULL(CONVERT(varchar(20),om.ObtainedPracticalMarks),'A') as 'ObtainedPracticalMarks'
FROM Students.PersonalInfo p
INNER JOIN Students.AcademicCourse ac on p.StudentID = ac.StudentID
INNER JOIN Students.AcademicInfo ai on p.StudentID=ai.StudentID
LEFT OUTER JOIN Exam.ObtainedMarkEntry om on p.StudentID = om.StudentID
LEFT JOIN Exam.ExamType et on om.ExamID = et.ExamID
WHERE ai.BatchID = '103' AND ai.SemesterID = '21' and ac.Section = '8'
This produce result as in a picture:
But I want result like this since those two students were absent in that exam
Similarly if another Exam does exists for any of three student and other are absent same procedure should repeat
Use IsNULL() Function see below example
Declare #variable varchar(MAX)
set #variable = NULL
select IsNULL(#variable,0) as A
Let's have the following sample data (it is like your sample data, just inserted in one table):
DECLARE #DataSource TABLE
(
[StudentID] TINYINT
,[RowNo] TINYINT
,[FirstName] VARCHAR(12)
,[MiddleName] VARCHAR(12)
,[LastName] VARCHAR(12)
,[ExamID] TINYINT
,[ExamName] VARCHAR(18)
,[SubjectID] TINYINT
,[ObtainedMarksTheory] VARCHAR(12)
,[ObtainedPracticalMarks] VARCHAR(12)
);
INSERT INTO #DataSource ([StudentID], [RowNo], [FirstName], [MiddleName], [LastName], [ExamID], [ExamName], [SubjectID], [ObtainedMarksTheory], [ObtainedPracticalMarks])
VALUES (101, 1, 'FN_A', 'MN_A', 'LN_A', NULL, NULL, NULL, 'A', 'A')
,(102, 2, 'FN_B', 'MN_B', 'LN_B', 28, 'First Tem2072', 97, '74.00', '56.00')
,(103, 3, 'FN_C', 'MN_C', 'LN_C', NULL, NULL, NULL, 'A', 'A');
SELECT *
FROM #DataSource;
So, I am supposing we have details only for one exam here and the question is how to replace the NULL values of ExamID, ExamName and SubjectID columns using the existing values.
The solution is to use the MAX function with OVER clause:
SELECT [StudentID]
,[RowNo]
,[FirstName]
,[MiddleName]
,[LastName]
,MAX([ExamID]) OVER() AS [ExamID]
,MAX([ExamName]) OVER() AS [ExamName]
,MAX([SubjectID]) OVER() AS [SubjectID]
,[ObtainedMarksTheory]
,[ObtainedPracticalMarks]
FROM #DataSource;
Note, that it is better to use the OVER clause as you are not going to add GROUP BY clauses in your initial query. Also, if you have details for more exams you can use the PARTITION BY clause (if you have some column(s) to distinguish the NULL rows for each exam).

SQL script took too much time and throws a time out error

I am executing SQL script from my application. Using this script I am taking data from Product table and putting it into Table1. If I execute this script directly from SSMS it will take 00:01:27 minute. But using application it gives me an error: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Here is my script:
-- Deleting [Table1] table if exists before creating because we don't need to keep track of records
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'Table1'))
BEGIN
DROP TABLE [dbo].[Table1]
END
-- Creating [Table1] table
CREATE TABLE [dbo].[Table1]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[ProductId] [int] NOT NULL,
[MyStatus] [bit] NOT NULL,
[IsDeleted] [bit] NOT NULL,
[InTime] [datetime] NULL,
[StoreId] [int] NOT NULL,
[LanguageId] [int] NOT NULL,
PRIMARY KEY([Id])
);
DECLARE #pId int
DECLARE #sId int
Truncate Table [dbo].[Table1]
Insert Into [dbo].[Table1] (ProductId, MyStatus, IsDeleted, InTime, StoreId, LanguageId)
select distinct
p.Id, 1, 1, GETDATE(), s.Id, l.Id
from
[dbo].[Store] s, [dbo].[Product] p, [dbo].[Language] as l
left join
[dbo].[StoreMapping] sm on sm.EntityId = l.Id
where
(l.LimitedToStores = 1 and s.Id in (sm.StoreId)
and sm.EntityName = 'Language') or l.LimitedToStores = 0
DECLARE tempCursor CURSOR SCROLL FOR
select
p.Id, sm.StoreId
from
[dbo].[Product] as p
join
[dbo].[StoreMapping] as sm on p.Id = sm.EntityId
join
[dbo].[Store] as s on sm.StoreId=s.Id
where
p.LimitedToStores = 1 and sm.EntityName = 'Product'
OPEN tempCursor
FETCH FIRST FROM tempCursor INTO #pId, #sId
WHILE ##fetch_status = 0
BEGIN
UPDATE [dbo].[Table1]
SET IsDeleted = 0
WHERE ProductId = #pId AND StoreId = #sId
FETCH NEXT FROM tempCursor INTO #pId, #sId;
END
CLOSE tempCursor
UPDATE [dbo].[Table1]
SET IsDeleted = 0
where ProductId in (Select p.Id
from [dbo].[Product] as p
where p.LimitedToStores = 0)
UPDATE [dbo].[Table1]
SET IsDeleted = 1
where ProductId in (Select p.Id
from [dbo].[Product] as p
where p.Published = 0 OR p.Deleted = 1
OR p.VisibleIndividually = 0)
DEALLOCATE tempCursor
Can anyone help me to improve this script?
The part with the CURSOR and looping through it to set IsDeleted=0 in Table1 can be replaced with the following update statement:
UPDATE
[dbo].[Table1]
SET
IsDeleted=0
FROM
[dbo].[Product] AS p
JOIN [dbo].[StoreMapping] AS sm ON p.Id=sm.EntityId
JOIN [dbo].[Store] AS s ON sm.StoreId=s.Id
JOIN [dbo].[Table1] AS t1 ON t1.ProductId=p.Id AND t1.StoreId=s.Id
WHERE
p.LimitedToStores=1 AND
sm.EntityName ='Product'
The last two queries are better written with JOINs
UPDATE
[dbo].[Table1]
SET
IsDeleted=0
FROM
[dbo].[Table1] AS t1
JOIN [dbo].[Product] AS p ON
p.Id=t1.ProductId
WHERE
p.LimitedToStores=0
UPDATE
[dbo].[Table1]
SET
IsDeleted=1
FROM
[dbo].[Table1] AS t1
JOIN [dbo].[Product] AS p ON
p.Id=t1.ProductId
WHERE
p.Published=0 OR
p.Deleted=1 OR
p.VisibleIndividually=0

Using SQL MERGE with vars instead of a table

take the following table (a very simplified example):
CREATE TABLE [dbo].[tbl_Order_Lines] (
[LineID] [int] IDENTITY(1, 1) NOT NULL ,
[OrderID] [int] NOT NULL ,
[StockCode] [varchar](20) NOT NULL ,
[Quantity] [smallint] NOT NULL
)
I have a proc that controls insertions into this table, but at one point you come to the infamous "UPSERT" scenario.
Let’s assume my procedure has the following vars:
#OrderID INT ,
#StockCode VARCHAR(20) ,
#Quantity SMALLINT
I currently do as follows:
IF ( NOT EXISTS ( SELECT *
FROM [dbo].[tbl_Order_Lines]
WHERE [OrderID] = #OrderID
AND [StockCode] = #StockCode )
)
INSERT INTO [dbo].[tbl_Order_Lines]
( [OrderID] ,
[StockCode] ,
[Quantity]
)
VALUES ( #OrderID ,
#StockCode ,
#Quantity
)
ELSE
UPDATE [dbo].[tbl_Order_Lines]
SET Quantity = #Quantity
WHERE [OrderID] = #OrderID
AND [StockCode] = #StockCode
My intention is to do away with this old method and use the MERGE statement - however i'm struggling to get my head round the MERGE statement, this is what i have so far:
MERGE dbo.tbl_Order_Lines
USING (
VALUES
( #Quantity
) ) AS Source ( Quantity )
ON dbo.tbl_Order_Lines.OrderID = #OrderID AND StockCode = #StockCode
WHEN MATCHED THEN
UPDATE SET Quantity = source.Quantity
WHEN NOT MATCHED THEN
INSERT (
OrderID ,
StockCode ,
Quantity
) VALUES
( #OrderID ,
#StockCode ,
Source.Quantity
);
My Question(s):
My attempt at this MERGE seems to work - yet it looks VERY messy and confusing - is there a better way of writing this?
How would i modify this MERGE statement to DELETE matching rows (based on OrderID & StockCode) if #Quantity = 0
You could tighten up the last part by using the special $action keyword.
Case $action
When 'INSERT' Then 'OK_ADDED'
When 'UPDATE' Then 'OK_UPDATED'
When 'DELETE' Then 'OK_REMOVED'
End
$action
Is available only for the MERGE statement. Specifies a column of type nvarchar(10) in the OUTPUT clause in a MERGE statement that returns one of three values for each row: 'INSERT', 'UPDATE', or 'DELETE', according to the action that was performed on that row.
Output Clause.
OK, this is what i came up with:
MERGE dbo.tbl_Order_Lines
USING ( VALUES ( #Quantity ) ) AS Source ( Quantity )
ON dbo.tbl_Order_Lines.OrderID = #OrderID AND StockCode = #StockCode
WHEN MATCHED AND #Quantity > 0 THEN
UPDATE SET Quantity = source.Quantity
WHEN MATCHED AND #Quantity <= 0 THEN
DELETE
WHEN NOT MATCHED AND #Quantity > 0 THEN
INSERT (
OrderID ,
StockCode ,
Quantity
)
VALUES
( #OrderID ,
#StockCode ,
Source.Quantity
)
OUTPUT
COALESCE(Inserted.LineID, Deleted.LineID) AS ResultID ,
CASE WHEN Deleted.LineID IS NULL
AND Inserted.LineID IS NOT NULL THEN 'OK_ADDED'
WHEN Deleted.LineID IS NOT NULL
AND Inserted.LineID IS NOT NULL THEN 'OK_UPDATED'
WHEN Deleted.LineID IS NOT NULL
AND Inserted.LineID IS NULL THEN 'OK_REMOVED'
END AS ResultDesc
INTO #tbl_LineChanges ( ResultID, ResultDesc );
Would still love to know if there is a tider way of writing this!

how to insert multiple rows with check for duplicate rows in a short way

I am trying to insert multiple records (~250) in a table (say MyTable) and would like to insert a new row only if it does not exist already.
I am using SQL Server 2008 R2 and got help from other threads like SQL conditional insert if row doesn't already exist.
While I am able to achieve that with following stripped script, I would like to know if there is a better (short) way to do this as I
have to repeat this checking for every row inserted. Since we need to execute this script only once during DB deployment, I am not too much
worried about performance.
INSERT INTO MyTable([Description], [CreatedDate], [CreatedBy], [ModifiedDate], [ModifiedBy], [IsActive], [IsDeleted])
SELECT N'ababab', GETDATE(), 1, NULL, NULL, 1, 0
WHERE NOT EXISTS(SELECT * FROM MyTable WITH (ROWLOCK, HOLDLOCK, UPDLOCK)
WHERE
([InstanceId] IS NULL OR [InstanceId] = 1)
AND [ChannelPartnerId] IS NULL
AND [CreatedBy] = 1)
UNION ALL
SELECT N'xyz', 1, GETDATE(), 1, NULL, NULL, 1, 0
WHERE NOT EXISTS(SELECT * FROM [dbo].[TemplateQualifierCategoryMyTest] WITH (ROWLOCK, HOLDLOCK, UPDLOCK)
WHERE
([InstanceId] IS NULL OR [InstanceId] = 1)
AND [ChannelPartnerId] IS NULL
AND [CreatedBy] = 1)
-- More SELECT statements goes here
You could create a temporary table with your descriptions, then insert them all into the MyTable with a select that will check for rows in the temporary table that is not yet present in your destination, (this trick in implemented by the LEFT OUTER JOIN in conjunction with the IS NULL for the MyTable.Description part in the WHERE-Clause):
DECLARE #Descriptions TABLE ([Description] VARCHAR(200) NOT NULL )
INSERT INTO #Descriptions ( Description )VALUES ( 'ababab' )
INSERT INTO #Descriptions ( Description )VALUES ( 'xyz' )
INSERT INTO dbo.MyTable
( Description ,
CreatedDate ,
CreatedBy ,
ModifiedDate ,
ModifiedBy ,
IsActive ,
IsDeleted
)
SELECT d.Description, GETDATE(), 1, NULL, NULL, 1, 0
FROM #Descriptions d
LEFT OUTER JOIN dbo.MyTable mt ON d.Description = mt.Description
WHERE mt.Description IS NULL