SQL Query Optimization with million of records - sql

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.

Related

How to use CHECKSUM in a trigger

I'm trying to create my own logic for tables synchronization in SQL Server Express 2019. I was hoping that such simple task would work:
Have a Customers table
Have a Synchronization table
CREATE TABLE [dbo].[Synchronization]
(
[PK] [uniqueidentifier] NOT NULL,
[TableName] [nchar](50) NOT NULL,
[RecordPK] [uniqueidentifier] NOT NULL,
[RecordChecksum] [int] NOT NULL,
[RecordDate] [datetime] NOT NULL,
[RecordIsDeleted] [bit] NOT NULL
)
Have a trigger on Customers:
CREATE TRIGGER trgCustomers_INSERT
ON Customers
AFTER INSERT
AS
INSERT INTO Synchronization(PK, TableName, RecordPK, RecordChecksum,
RecordDate, RecordIsDeleted)
VALUES (NEWID(), 'Customers',
(SELECT PK FROM inserted),
(SELECT CHECKSUM(*) FROM inserted),
GETDATE(), 0)
... but I got an error about the SELECT CHECKSUM(*) FROM inserted part:
Cannot use CHECKSUM(*) in a computed column, constraint, default definition, or INSERT statement.
Is there any other way to add new Customer's CHECKSUM or some hash to the Synchronization table?
Don't use the VALUES syntax when inserting and you won't get an error using CHECKSUM while inserting.
Example:
declare #t table (val int)
-- works
insert into #t select checksum(*) from ( select ID from (select 1 as ID union select 2) b ) a
-- reproduce error
insert into #t
values
((select top 1 checksum(*) C from ( select ID from (select 1 as ID union select 2) b ) a))
Implementing the concept in your trigger:
CREATE TRIGGER trgCustomers_INSERT
ON Customers
AFTER INSERT
AS
begin
INSERT INTO Synchronization(PK, TableName, RecordPK, RecordChecksum,
RecordDate, RecordIsDeleted)
select NEWID() as PK,
'Customers' as TableName,
PK as RecordPK,
checksum(*) as RecordChecksum,
GETDATE() as RecordDate,
0 as RecordIsDeleted
from inserted
end

Multi Parent-Child Insertion

I'm trying to write a stored procedure to pull information from an XML string and use it to create multiple parent-child relationships. I am trying to push this XML into actual database tables. Basically, the local client will send an XML file to the database and store it as a string. I then need to pull the information out of that string and update the appropriate tables. If this was just a Table-A to Table-B, this wouldn't be so difficult. The problem I'm running into is it need to go from Table-A to Table-B to Table-C to Table-D where applicable. Below is a sample XML:
<RunRecordFile>
<Competition>
<Name>Daily</Name>
<StartDate>11/9/2015 12:40:07 AM</StartDate>
<Runs>
<Id>123</Id>
<Name>Daily Run</Name>
<RunDate>11/9/2015 12:40:07 AM</RunDate>
<CompetitionId>1</CompetitionId>
<RunRecords>
<Id>001</Id>
<Number>007</Number>
<ElapsedTime>23.007</ElapsedTime>
<RunId>123</RunId>
</RunRecords>
</Runs>
<Runs>
<Id>456</Id>
<Name>Daily Run</Name>
<RunDate>11/9/2015 12:47:07 AM</RunDate>
<CompetitionId>1</CompetitionId>
<RunRecords>
<Id>002</Id>
<Number>700</Number>
<ElapsedTime>23.707</ElapsedTime>
<RunId>456</RunId>
<RunRecordSpecialty>
<Id>1</Id>
<Handicap>17</Handicap>
<TeamPoints>50000</TeamPoints>
<RunRecordId>002</RunRecordId>
</RunRecordSpecialty>
</RunRecords>
</Runs>
</Competition>
</RunRecordFile>
I've attempted to use a DECLARED table to hold each of the created Primary Keys and to use SQL OUTPUT in order to gather those. When I run my SQL I'm getting (0) Rows Updated. Here's what I've tried in SQL:
CREATE PROC [dbo].[RaceFilePush]
AS
DECLARE #CompetitionIdMapping TABLE ( CompetitionId bigint )
DECLARE #RunIdMapping TABLE ( RunId bigint )
DECLARE #RunRecordIdMapping TABLE ( RunRecordId bigint )
BEGIN
DECLARE #rrXML AS XML
DECLARE #rrfId AS BIGINT
SET #rrfId = (SELECT TOP 1 Id FROM RunRecordFile WHERE Submitted IS NULL)
SET #rrXML = (SELECT TOP 1 RaceFile FROM RunRecordFile WHERE Id = #rrfId)
BEGIN TRAN Competitions
BEGIN TRY
INSERT INTO Competition (
Name
,StartDate
)
OUTPUT INSERTED.Id INTO #CompetitionIdMapping(CompetitionId)
SELECT
xCompetition.value('(Name)[1]', 'varchar(225)') AS Name
,xCompetition.value('(StartDate)[1]', 'datetime') AS StartDate
,#rrfId AS RunRecordFileId
FROM
#rrXML.nodes('/RunRecordFile/Competition') AS E(xCompetition)
INSERT INTO Run (
Name
,RunDate
,CompetitionId
)
OUTPUT INSERTED.Id INTO #RunIdMapping(RunId)
SELECT
xRuns.value('(Name)[1]','varchar(80)') AS Name
,xRuns.value('(RunDate)[1]','datetime') AS RunDate
,(SELECT CompetitionId FROM #CompetitionIdMapping)
FROM
#rrXML.nodes('/RunRecordFile/Competition/Runs') AS E(xRuns)
INSERT INTO RunRecord (
Number
,ElapsedTime
,RunId
)
OUTPUT INSERTED.Id INTO #RunRecordIdMapping(RunRecordId)
SELECT
xRunRecords.value('(Number)[1]','varchar(10)') AS Number
,xRunRecords.value('(ElapsedTime)[1]','numeric(10,5)') AS ElapsedTime
,(SELECT RunId FROM #RunIdMapping)
FROM
#rrXML.nodes('/RunRecordFile/Competition/Runs/RunRecords') AS E(xRunRecords)
INSERT INTO RunRecordSpecialty (
Handicap
,TeamPoints
,RunRecordId
)
SELECT
xRunRecordSpecialty.value('(Handicap)[1]','numeric(10,5)') AS Handicap
,xRunRecordSpecialty.value('(TeamPoints)[1]','numeric(10,5)') AS TeamPoints
,(SELECT RunRecordId FROM #RunRecordIdMapping)
FROM
#rrXML.nodes('/RunRecordFile/Competition/Runs/RunRecordSpecialty') AS E(xRunRecordSpecialty)
UPDATE RunRecordFile SET Submitted = GETDATE() WHERE Id = #rrfId
COMMIT TRAN Competitions
END TRY
BEGIN CATCH
ROLLBACK TRAN Competitions
END CATCH
END
With this SQL you get the whole thing into a flat declared table #tbl:
Remark: I placed the XML from your question into a variable called #xml. Adapt this to your needs...
DECLARE #tbl TABLE (
[Competition_Name] [varchar](max) NULL,
[Competition_StartDate] [datetime] NULL,
[Run_Id] [int] NULL,
[Run_Name] [varchar](max) NULL,
[Run_RunDate] [datetime] NULL,
[Run_CompetitionId] [int] NULL,
[RunRecords_Id] [int] NULL,
[RunRecords_Number] [int] NULL,
[RunRecords_ElapsedTime] [float] NULL,
[RunRecords_RunId] [int] NULL,
[RunRecordSpecialty_Id] [int] NULL,
[RunRecordSpecialty_Handicap] [int] NULL,
[RunRecordSpecialty_TeamPoints] [int] NULL,
[RunRecordSpecialty_RunRecordId] [int] NULL
);
INSERT INTO #tbl
SELECT Competition.value('Name[1]','varchar(max)') AS Competition_Name
,Competition.value('StartDate[1]','datetime') AS Competition_StartDate
,Run.value('Id[1]','int') AS Run_Id
,Run.value('Name[1]','varchar(max)') AS Run_Name
,Run.value('RunDate[1]','datetime') AS Run_RunDate
,Run.value('CompetitionId[1]','int') AS Run_CompetitionId
,RunRecords.value('Id[1]','int') AS RunRecords_Id
,RunRecords.value('Number[1]','int') AS RunRecords_Number
,RunRecords.value('ElapsedTime[1]','float') AS RunRecords_ElapsedTime
,RunRecords.value('RunId[1]','int') AS RunRecords_RunId
,RunRecordSpecialty.value('Id[1]','int') AS RunRecordSpecialty_Id
,RunRecordSpecialty.value('Handicap[1]','int') AS RunRecordSpecialty_Handicap
,RunRecordSpecialty.value('TeamPoints[1]','int') AS RunRecordSpecialty_TeamPoints
,RunRecordSpecialty.value('RunRecordId[1]','int') AS RunRecordSpecialty_RunRecordId
FROM #xml.nodes('/RunRecordFile/Competition') AS A(Competition)
OUTER APPLY Competition.nodes('Runs') AS B(Run)
OUTER APPLY Run.nodes('RunRecords') AS C(RunRecords)
OUTER APPLY RunRecords.nodes('RunRecordSpecialty') AS D(RunRecordSpecialty)
;
SELECT * FROM #tbl
If you need generated IDs, you just add the columns to #tbl and write them there, either "on the flow" or afterwards wiht UPDATE statement.
It should be easy to work through this flat table, select just the needed data level with DISTINCT and insert the rows, then the next level and so on...
Good luck!

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

SQL SCOPE_IDENTITY or OUTPUT with data from another table

I'm trying to get the ID of each row inserted from a mass insert and create a mapping table with an ID from my temp table #InsertedClients. I'm using the same temp table for the initial insert but the PDATClientID is not part of the insert. I can't seem to figure out how to do this correctly. When I inserting into the Client_Mapping table using SCOPE_IDENTITY it only grabs the ID from the first insert and puts it along with all of my PDATClientIDs. After doing a lot of Googling I believe I should be using OUTPUT, but can't seem to figure how to put the INSERTED.CLID for each record together with each PDATClientID. I should also say that I can't use a cursor there are too rows. Any help is greatly appreciated!
IF NOT EXISTS (
SELECT ID
FROM sysobjects
WHERE id = object_id(N'[Client_Mapping]')
AND OBJECTPROPERTY(id, N'IsUserTable') = 1
)
CREATE TABLE [Client_Mapping] (
ClientMappingID INT Identity(1, 1) NOT NULL
,PDATClientID INT NOT NULL
,CLID INT NOT NULL
,AUDITDATE DATETIME NOT NULL
,CONSTRAINT [PK_Client_Mapping] PRIMARY KEY (ClientMappingID)
)
-- Begin Inserts
SELECT
First_Name
,Middle_Name
,Last_Name
,CONVERT(DATETIME, Date_Of_Birth) AS DOB
,a.Address_Line as Address1
,a.Address_Line_2 as Address2
,a.Zip_Code as ZipCode -- ??? Do I need to account for zipcode + 4
,REPLACE(REPLACE(REPLACE(cphp.Number_Or_Address, '(', ''), ')', '-'), ' ', '') AS HomePhone
,REPLACE(REPLACE(REPLACE(cpcp.Number_Or_Address, '(', ''), ')', '-'), ' ', '') AS CellPhone
,cpem.Number_Or_Address AS EMAIL
,c.Client_ID as PDATClientID
,c.Action AS [ClientAction]
,ca.Action as [ClientAddressAction]
,a.Action AS [AddressAction]
,cphp.Action AS [HomePhoneAction]
,cpcp.Action AS [CellPhoneAction]
,cpem.Action AS [EmailAction]
INTO #InsertClients
FROM Client c
LEFT JOIN Client_Address ca ON ca.Client_ID = c.Client_ID
LEFT JOIN Address a ON a.Address_ID = ca.Address_ID
LEFT JOIN Client_Phone cphp ON cphp.Client_ID = c.Client_ID
AND cphp.Phone_Email_Type = 'HP'
AND cphp.Start_Date = (
SELECT MAX(Start_Date)
FROM Client_Phone
WHERE Client_ID = c.Client_ID
)
LEFT JOIN Client_Phone cpcp ON cpcp.Client_ID = c.Client_ID
AND cpcp.Phone_Email_Type = 'CP'
AND cpcp.Start_Date = (
SELECT MAX(Start_Date)
FROM Client_Phone
WHERE Client_ID = c.Client_ID
)
LEFT JOIN Client_Phone cpem ON cpem.Client_ID = c.Client_ID
AND cpem.Phone_Email_Type = 'EM'
AND cpem.Start_Date = (
SELECT MAX(Start_Date)
FROM Client_Phone
WHERE Client_ID = c.Client_ID
)
where c.action ='I'
BEGIN TRY
BEGIN TRAN
INSERT INTO [dbo].[Clients] (
[FName]
,[MiddleInitial]
,[LName]
,[DOB]
,[Address1]
,[Address2]
,[ZipCode]
,[HomePhone]
,[CellPhone]
,[Email]
,[AuditStaffID]
,[AuditDate]
,[DateCreated]
)
Select
First_Name
,CASE when Middle_Name = '' THEN NULL ELSE Middle_Name END
,Last_Name
,DOB
,Address1
,Address2
,ZipCode
,HomePhone
,CellPhone
,EMail
,496 AS [AuditStaffID]
,CURRENT_TIMESTAMP AS [AuditDate]
,CURRENT_TIMESTAMP AS [DateCreated]
FROM
#InsertClients
Where
[ClientAction] = 'I'
INSERT INTO [Client_Mapping]
(
PDATClientID
,CLID
,AUDITDATE
)
SELECT
PDATClientID
,SCOPE_IDENTITY()
,CURRENT_TIMESTAMP
FROM #InsertClients
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
SELECT ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage
END CATCH
you have a hell of a code in your question I can explain you in few line how you can retrieve all the inserted ids.
Yes SCOPE_IDENTITY() gives you the last IDENTITY value generated by the identity column. In you case you will need to use a table variable to get all the generated Identity values during your insert
/* Declare a table Vairable */
DECLARE #NewValues TABLE (IDs INT);
/* In Your Insert Statement use OUTPUT clause */
INSERT INTO Table_Name (Column1,Column2,Column3,....)
OUTPUT inserted.Identity_Column INTO #NewValues (IDs)
SELECT Column1,Column2,Column3,....
FROM Table_Name2
/* Finally Select values from your table variable */
SELECT * FROM #NewValues
Your Query
In your case your insert statement should look something like ...
INSERT INTO [dbo].[Clients] (
[FName]
,[MiddleInitial]
,[LName]
,[DOB]
,[Address1]
,[Address2]
,[ZipCode]
,[HomePhone]
,[CellPhone]
,[Email]
,[AuditStaffID]
,[AuditDate]
,[DateCreated]
)
OUTPUT inserted.[FName], inserted.[MiddleInitial] ,.... INTO #TableVarible(First_Name,[MiddleInitial].....)
Select
First_Name
,CASE when Middle_Name = '' THEN NULL ELSE Middle_Name END
,Last_Name
,DOB
,Address1
,Address2
,ZipCode
,HomePhone
,CellPhone
,EMail
,496 AS [AuditStaffID]
,CURRENT_TIMESTAMP AS [AuditDate]
,CURRENT_TIMESTAMP AS [DateCreated]
FROM
#InsertClients
Where
[ClientAction] = 'I'
Solved the problem by using this example found here: http://sqlserverplanet.com/tsql/match-identity-columns-after-insert. The example from that site is below.
-- Drop temp tables if they already exist
IF OBJECT_ID('TempDB..#source', 'U') IS NOT NULL DROP TABLE #source;
IF OBJECT_ID('TempDB..#target', 'U') IS NOT NULL DROP TABLE #target;
IF OBJECT_ID('TempDB..#xref', 'U') IS NOT NULL DROP TABLE #xref;
GO
-- Create the temp tables
CREATE TABLE #source (id INT PRIMARY KEY IDENTITY(1, 1), DATA INT);
CREATE TABLE #target (id INT PRIMARY KEY IDENTITY(1, 1), DATA INT);
CREATE TABLE #xref (ROW INT PRIMARY KEY IDENTITY(1, 1), source_id INT, target_id INT);
GO
-- If xref table is being reused, make sure to clear data and reseed by truncating.
TRUNCATE TABLE #xref;
-- Fill source table with dummy data (even numbers)
INSERT INTO #source (DATA)
SELECT 2 UNION SELECT 4 UNION SELECT 6 UNION SELECT 8;
GO
-- Fill target table with dummy data (odd numbers) to simulate existing records
INSERT INTO #target (DATA)
SELECT 1 UNION SELECT 3 UNION SELECT 5;
GO
-- Insert source table data into target table. IMPORTANT: Inserted data must be sorted by the source table's primary key.
INSERT INTO #target (DATA)
OUTPUT INSERTED.id INTO #xref (target_id)
SELECT DATA
FROM #source
ORDER BY id ASC;
GO
-- Update the xref table with the PK of the source table. This technique is used for data not captured during the insert.
;WITH src AS (SELECT id, ROW = ROW_NUMBER() OVER (ORDER BY id ASC) FROM #source)
UPDATE x
SET x.source_id = src.id
FROM #xref AS x
JOIN src ON src.ROW = x.ROW;
GO
-- Verify data
SELECT * FROM #source;
SELECT * FROM #target;
SELECT * FROM #xref;
GO
-- Clean-up
IF OBJECT_ID('TempDB..#source', 'U') IS NOT NULL DROP TABLE #source;
IF OBJECT_ID('TempDB..#target', 'U') IS NOT NULL DROP TABLE #target;
IF OBJECT_ID('TempDB..#xref', 'U') IS NOT NULL DROP TABLE #xref;
GO

Multiple Table Insert with Merge?

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