0

I'm relatively new to T-SQL and am having some trouble getting my import MERGE stored procedure to run. I'm sure it's a simple mistake but I've been reading article after article trying to find my mistake.

I'll give you all an overview of what it is I'm doing (or trying to do) so that it all makes sense. The purpose of this application is to view a list of members that have received a marketing email and recently purchased a service/product based on that email.

Lets call this application "MktRep" which was created in C# MVC 5 - ASP.NET using Visual Studios 2017 community edition. For the DB, I'm using SQL Server Management Studios 2014. Continuing on.

MktRep has two main views/pages (which actually have multiple partial views):

  1. One page to import data
  2. One page to view the data

Ok so let's continue. Now, when the user imports data, they provide the following information:

  • CSV file with the account numbers of the members
  • The date the mailer was sent
  • The type of mailer that was sent (we have 2 different types currently)

Once the CSV file is imported, we convert that into XML format, then pass that data along with the other collected data, to the database to INSERT/UPDATE/MERGE via stored procedure.

Now from the database side, we have multiple tables (and databases) that are providing specific account details and have a new table for all of the imported data.

Originally, I was taking the imported values, comparing the account number, product booked date, and mailed date and storing the matches in this new table. However, I realized this was a bad idea because if the member booked/purchased a service after the upload date, that data would be lost and there could potentially be discrepancies. So, I wait to do the comparison until the "View Report" page.

So here's where I'm having issues.

When we import account data, we DO NOT want to have duplicate records. However, we DO want to update the letter type and mailed date IF the record already exists.

After reading a TON of different articles about using UPDATE/INSERT, MERGE, and everything in between, I decided I would try using MERGE.

Currently, whenever there is more than 1000 records in the XML, it takes almost a minute for the query to run. Any ideas on how to trim down that time?

For the sake of testing the code, I'm running it as a regular SQL query but the variables defined at the top would normally be the variables passed from the web app.

SQL Code:

BEGIN
DECLARE 
    @maildate date = '04-01-2017',
    @lettertype varchar(10) = 'ALR',
    @xmldata varchar(max) = '<data>
                                <rec><mem> 400117788 </mem></rec>
                                <rec><mem> 122244455 </mem></rec>
                                <rec><mem> 188642145 </mem></rec>
                                <rec><mem> 348865442 </mem></rec>
                                <rec><mem> 199754621 </mem></rec>
                                <rec><mem> 955421234 </mem></rec>
                                <rec><mem> 754136845 </mem></rec>
                                <rec><mem> 946612164 </mem></rec>
                                <rec><mem> 102446785 </mem></rec>
                                <rec><mem> 478310246 </mem></rec></data>'

/*----------------------------*/
/*-- GET MEM DATA FROM XML --*/
/*----------------------------*/

DECLARE @memData TABLE 
(
    mem varchar(9)
)

DECLARE @num int

EXEC sp_xml_preparedocument @num OUTPUT, @xmldata

INSERT INTO @memData

SELECT * FROM OPENXML (@num, '/data/rec',2)

WITH (mem varchar(9))

EXEC sp_xml_removedocument @num


/*-------------------------------*/
/*-- INSERT DATA INTO TEMP TBL --*/
/*-------------------------------*/
DECLARE @tempData TABLE
(
    account varchar(10),
    mem varchar(9),
    maildate date,
    lettertype varchar(5)
)

INSERT into @tempData (account, mem, lettertype, maildate)

SELECT

    s.account, s.mem, @lettertype, @maildate

FROM
    [tsqldb\sqldb].sumdb.dbo.tblShareFile s

INNER JOIN [tsqldb\sqldb].sumdb.dbo.tblServiceFile l

    ON l.account = s.account

INNER JOIN @memData d

    ON d.mem = s.mem

WHERE

    s.AccFlag != 80

    AND s.AccType = '0' 


/*----------------------------------*/
/*-- CHECK FOR DUPS & INSERT DATA --*/
/*----------------------------------*/

MERGE INTO dbo.tblMktRep AS TARGET

USING @tempData AS source

ON source.account = target.Account

    WHEN NOT MATCHED BY TARGET THEN
        INSERT (Account, mem, lettertype, MailedDate)
        VALUES (source.account, source.mem, source.lettertype, source.maildate)

    WHEN MATCHED AND (target.MailedDate < source.maildate) THEN 
        UPDATE 
        SET 
            target.lettertype = source.lettertype,
            target.MailedDate = source.maildate;

UPDATE: I figured out the error I was getting was because of the "BEGIN". Once I removed that, it ran fine. Now the only issue is why it's taking so long to run.

UPDATE: Now I'm currently getting the following error:

Msg 8672, Level 16, State 1, Line 67
The MERGE statement attempted to UPDATE or DELETE the same row more than once. This happens when a target row matches more than one source row. A MERGE statement cannot UPDATE/DELETE the same row of the target table multiple times. Refine the ON clause to ensure a target row matches at most one source row, or use the GROUP BY clause to group the source rows.

I've read other posts and articles but still didn't quite find a solution:

SQL Server MERGE + Joining other tables

one from msdn

one from sql sunday

one from technet

one from sqljana

Rocket Risa
  • 383
  • 2
  • 16

2 Answers2

2

I didn't see an END clause in your statement. Once I add an END, it parses fine on my system.

Having a BEGIN without a closing END will generate a syntax error at the end of the last statement.

After Update to Question

Try changing your table variable into a temp table with a clustered index on Account. Table Variables do not generally perform very well with large volumes of data.

To address the multi-row update, you need to ensure that your source only has each account once, since that is your match key. If the account is in the source multiple times, it may fire the update clause for the same row multiple times. Since your source maildate is a variable, you should be able to solve this with a SELECT DISTINCT into your Source Table

Wes H
  • 4,186
  • 2
  • 13
  • 24
  • Yeah, I removed the BEGIN and it ran. Now the only issue is that it's taking about 45 seconds to run 1000 records. – Rocket Risa Jun 22 '17 at 20:17
  • Glad to hear you got it working, but you shouldn't remove the content from your original question. Now my answer looks irrelevant to the updated question. – Wes H Jun 22 '17 at 20:19
  • I added an update section to the post. Added code back. – Rocket Risa Jun 22 '17 at 20:21
0

My friend... you have a BEGIN, with no END...

Daniel Schmidt
  • 401
  • 5
  • 5