3
\$\begingroup\$

This code iterates through the table @Table1 to get the Purchaser, PurchaserID, SaleID store the values in a variable, then queries a secondary table to get data for the specific purchaser. 2 different data sets (one for daily, one for weekly) checks if a folder for the purchaser already exists. If not, it creates then saves the .csv and emails it to the email address listed for the purchaser in @Table1.

The code works, but how would you improve it?

CREATE TABLE [dbo].[AI](
[ID] [int] IDENTITY(1,1) NOT NULL,
[saledate] [datetime] NULL,
[trn] [varchar](max) NULL,
[purchaser] [varchar](max) NULL,
[primaryaddress] [varchar](max) NULL,
[secondaryaddress] [varchar](max) NULL,
[city] [varchar](200) NULL,
[state] [varchar](50) NULL,
[zip] [varchar](10) NULL,
[purchaserid] [varchar](50) NULL,
[saleID] [varchar](50) NULL)
CREATE TABLE [dbo].[LI](
[ID] [int] IDENTITY(1,1) NOT NULL,
[saledate] [datetime] NULL,
[trn] [varchar](max) NULL,
[purchaser] [varchar](max) NULL,
[primaryaddress] [varchar](max) NULL,
[secondaryaddress] [varchar](max) NULL,
[city] [varchar](200) NULL,
[state] [varchar](50) NULL,
[zip] [varchar](10) NULL,
[itempurchased] [varchar](max) NULL,
[amtpurchased] [int] NULL,
[amtshipped] [int] NULL,
[purchaserid] [varchar](50) NULL,
[saleID] [varchar](50) NULL)
DECLARE @SendEmailTo VARCHAR(MAX), @Purchaser NVARCHAR(500), @AI nvarchar(4000), @body nvarchar(max);
DECLARE @emailbody varchar(MAX),@filedate varchar(500), @sql varchar(8000), @DateToAppend nvarchar(100);
DECLARE @LI nvarchar(4000), @PurchaserID varchar(50), @SaleID varchar(50);
DECLARE @chkdirectory as nvarchar(4000), @folder_exists as int, @EmailAttachment nvarchar(MAX);
Declare @Data1 Table (purchaser varchar(500), purchaserid varchar(50), saleid varchar(50), email varchar(100), active varchar(5))
INSERT INTO @Data1 (purchaser, purchaserid, saleid, email, active) VALUES
('Green', 'G12', '22', '12', '[email protected]', '1'), ('Red', 'R11', '10', '14', '[email protected]', '1')
Select distinct([purchaser]) purchaser,[purchaserid] purchaserid
,[saleID] saleID Into #holdingtable FROM @Data1 
WHERE [Active] = '1'
Order By [purchaser] ASC
DECLARE cursor1 CURSOR FOR
SELECT purchaser
,purchaserid
,saleid
FROM #holdingtable 
ORDER BY purchaser ASC
OPEN cursor1
FETCH NEXT FROM cursor1 INTO @Purchaser, @PurchaserID, @SaleID
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SendEmailTo = '';
Select @SendEmailTo = COALESCE(@SendEmailTo + ';', '') + [EmailAddress]
from @Data1
WHERE [purchaserid] = @PurchaserID
AND ISNULL([saleid], '') = ISNULL(@SaleID, '')
SET @SendEmailTo = case
 when @SendEmailTo LIKE ';%' then RIGHT(@SendEmailTo, LEN(@SendEmailTo)-1)
 ELSE @SendEmailTo
 END
SET @DateToAppend = REPLACE(CONVERT (CHAR(10), getdate(), 101),'/','');
if @SaleID >= 1
BEGIN
 Set @AI = 'C:\'+@Purchaser+'\AI_'+@DateToAppend+'.csv'
 SET @LI = 'C:\'+@Purchaser+'\LI_'+@DateToAppend+'.csv'
END
ELSE
BEGIN
 Set @AI = 'C:\'+@Purchaser+'\AI_'+@DateToAppend+'.csv'
 SET @LI = 'C:\'+@Purchaser+'\LI_'+@DateToAppend+'.csv'
END
if @SaleID >=1 
BEGIN
 SET @SQL = 'INSERT INTO [LI]([saledate],[trn],[purchaser],[primaryaddress],[secondaryaddress],[city],[state],[zip],[itempurchased],[amtpurchased],[amtshipped],[purchaserid],[saleID]) '
END
ELSE
BEGIN
 SET @SQL = 'INSERT INTO [LI]([saledate],[trn],[purchaser],[primaryaddress],[secondaryaddress],[city],[state],[zip],[itempurchased],[amtpurchased],[amtshipped],[purchaserid]) '
END
SET @SQL = @SQL + 'Select *, '+@PurchaserID+' '
if @SaleID >= 1
BEGIN
 SET @SQL = @SQL + ','+@SaleID+' '
END
SET @sql = @SQL + 'FROM seccompliance '
 + 'AND CAST(Crl.[pijad] AS NVARCHAR(100)) = '+@PurchaserID+' '
if @SaleID >= 1
BEGIN
 SET @sql = @sql + 'AND (CAST(ISNULL([cag].[Playja], '''') AS NVARCHAR(100)) = ISNULL('+@SaleID+', '''')) '
END
EXECUTE(@SQL)
if @SaleID >=1 
BEGIN
 SET @SQL = 'INSERT INTO [AI]([trn],[saledate],[purchaser],[primaryaddress],[secondaryaddress],[city],[state],[zip],[purchaserid],[saleID]) '
END
ELSE
BEGIN
 SET @SQL = 'INSERT INTO [AI]([trn],[saledate],[purchaser],[primaryaddress],[secondaryaddress],[city],[state],[zip],[purchaserid]) '
END
SET @SQL = @SQL + 'Select *, '+@PurchaserID+' '
if @SaleID >= 1
BEGIN
 SET @SQL = @SQL + ','+@SaleID+' '
END
 SET @sql = @SQL + 'FROM seccompliance '
 + 'AND CAST(Crl.[pijad] AS NVARCHAR(100)) = '+@PurchaserID+' '
if @SaleID >= 1
BEGIN
 SET @sql = @sql + 'AND (CAST(ISNULL([cag].[Playja], '''') AS NVARCHAR(100)) = ISNULL('+@SaleID+', '''')) '
END
EXECUTE(@SQL)
if @@ROWCOUNT >= 1
BEGIN
set @chkdirectory = 'C:\SaveThis\'+@Purchaser+'\';
declare @file_results table(file_exists int,file_is_a_directory int,parent_directory_exists int)
insert into @file_results(file_exists, file_is_a_directory, parent_directory_exists)
exec master.dbo.xp_fileexist @chkdirectory
select @folder_exists = file_is_a_directory
from @file_results
if @folder_exists = 0
 begin
 EXECUTE master.dbo.xp_create_subdir @chkdirectory
 end 
if @SaleID >= 1
BEGIN
 Select @sql = 'bcp "SELECT ''saledate'',''trn'',''purchaser'',''primaryaddress'',''secondaryaddress'',''city'',''state'',''zip'' UNION ALL SELECT CHAR(34) + CONVERT(varchar(10),[saledate],101) + CHAR(34),CHAR(34) + CAST([trn] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([purchaser] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([primaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([secondaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([city] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([state] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([zip] As VARCHAR(MAX))+CHAR(34) FROM [AI] WHERE purchaserID = ''' + @PurchaserID + ''' AND saleID = '''+@SaleID+'''" queryout "'
END
ELSE
BEGIN
 Select @sql = 'bcp "SELECT ''saledate'',''trn'',''purchaser'',''primaryaddress'',''secondaryaddress'',''city'',''state'',''zip'' UNION ALL SELECT CHAR(34) + CONVERT(varchar(10),[saledate],101) + CHAR(34),CHAR(34) + CAST([trn] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([purchaser] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([primaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([secondaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([city] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([state] As VARCHAR(MAX))+CHAR(34),CHAR(34) + CAST([zip] As VARCHAR(MAX))+CHAR(34) FROM [AI] WHERE purchaserID = ''' + @PurchaserID + '''" queryout "'
END 
SET @sql = @sql + @AI + '" -c -t, -T -S '+@@SERVERNAME
exec master..xp_cmdshell @sql
if @SaleID >= 1
BEGIN
 Select @sql = 'bcp "SELECT ''saledate'',''trn'',''purchaser'',''primaryaddress'',''secondaryaddress'',''city'',''state'',''zip'',''itempurchased'',''amtpurchased'',''amtshipped'' UNION ALL SELECT CHAR(34)+CONVERT(varchar(10),[saledate],101)+CHAR(34),CHAR(34)+CAST([trn] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([purchaser] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([primaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([secondaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([city] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([state] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([zip] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([itempurchased] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([amtpurchased] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([amtshipped] As VARCHAR(MAX)) FROM [LI] WHERE purchaserID = ''' + @PurchaserID + ''' AND saleID = '''+@SaleID+'''" queryout "'
END
ELSE
BEGIN
 Select @sql = 'bcp "SELECT ''saledate'',''trn'',''purchaser'',''primaryaddress'',''secondaryaddress'',''city'',''state'',''zip'',''itempurchased'',''amtpurchased'',''amtshipped'' UNION ALL SELECT CHAR(34)+CONVERT(varchar(10),[saledate],101)+CHAR(34),CHAR(34)+CAST([trn] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([purchaser] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([primaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([secondaryaddress] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([city] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([state] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([zip] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([itempurchased] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([amtpurchased] As VARCHAR(MAX))+CHAR(34),CHAR(34)+CAST([amtshipped] As VARCHAR(MAX)) FROM [LI] WHERE purchaserID = ''' + @PurchaserID + '''" queryout "'
END
SET @sql = @sql + @LI + '" -c -t, -T -S '+@@SERVERNAME
exec master..xp_cmdshell @sql
 SET @EmailAttachment = @AI+';'+@LI
SET @body = '<html>
 <body style="background: #e3e3e3;">
 Test Email Notification
 </body>
 </html>';
 EXEC msdb.dbo.sp_send_dbmail
 @profile_name = 'Test',
 @from_address = '[email protected]',
 @recipients= @SendEmailTo,
 @subject= 'System Generated Email', 
 @body_format = 'HTML',
 @body= @body,
 @file_attachments=@EmailAttachment 
END
FETCH NEXT FROM cursor1 INTO @Purchaser, @PurchaserID, @SaleID
END
CLOSE cursor1
DEALLOCATE cursor1
DROP TABLE #holdingtable
asked Nov 2, 2016 at 18:48
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Instead of creating a table via SELECT INTO, I always prefer to create them explicitly.

DROP TABLE IF EXISTS #HoldingTable;
CREATE TABLE #HoldingTable
(
 purchaser varchar(500) COLLATE DATABASE_DEFAULT NOT NULL,
 purchaserid varchar(50) COLLATE DATABASE_DEFAULT NOT NULL,
 saleid varchar(50) COLLATE DATABASE_DEFAULT NOT NULL
);
INSERT INTO #HoldingTable
( purchaser, purchaserid, saleid )
 SELECT DISTINCT
 D.purchaser,
 D.purchaserid,
 D.saleid
 FROM @Data1 D
 WHERE D.active = 1;

Unless you have a reason to actually order your input before inserting (e.g. it picks a better plan), just remove the ORDER BY.

You have unsupported string aggregation syntax here:

SELECT @SendEmailTo = COALESCE( @SendEmailTo + ';', '' ) + [EmailAddress]
 FROM @Data1
 WHERE [purchaserid] = @PurchaserID
 AND ISNULL( [saleid], '' ) = ISNULL( @SaleID, '' );

Instead, you should use STUFF:

SET @SendEmailTo = STUFF(( SELECT N';' + EmailAddress
 FROM @Data1
 WHERE [purchaserid] = @PurchaserID
 AND ( saleid = @SaleID
 OR ( saleid IS NULL
 AND @SaleID IS NULL ))
 FOR XML PATH( '' )),
 1,
 1,
 N'' );

This also removes the need to initialize it, or to remove trailing semi-colons. I also got rid of the gross ISNULL() = ISNULL().

Next, some of your other variables I turned into computed columns in your holding table, because it is a bit easier

DROP TABLE IF EXISTS #HoldingTable;
CREATE TABLE #HoldingTable
(
 purchaser varchar(500) COLLATE DATABASE_DEFAULT NOT NULL,
 purchaserid varchar(50) COLLATE DATABASE_DEFAULT NOT NULL,
 saleid varchar(50) COLLATE DATABASE_DEFAULT NULL,
 DateToAppend AS REPLACE( CONVERT( char(10), GETDATE(), 101 ), '/', '' ),
 AI AS CONCAT( N'C:\', purchaser, N'\AI_', REPLACE( CONVERT( char(10), GETDATE(), 101 ), '/', '' ), N'.csv' ),
 LI AS CONCAT( N'C:\', purchaser, N'\LI_', REPLACE( CONVERT( char(10), GETDATE(), 101 ), '/', '' ), N'.csv' ),
 ChkDirectory AS CONCAT( N'C:\SaveThis\', purchaser, N'\' )
);

From there, I looked at your actual insert statement. From here, I discovered a bug - you have a query of the form

INSERT INTO <<TableName>> (<<InsertList>>)
SELECT <<SelectList>>
 FROM <<TableName>> AND

That AND isn't valid; I assume you're trying to join something here, but the code isn't there.

You also have security issues here; you should never blindly concatenate a value with dynamic SQL, as you risk SQL injection. This is always the case, even if you think you can trust the input. Instead, you should parameterize your query and use sys.sp_executesql.

You actually don't need this to be dynamic at all, however.

INSERT INTO [LI]
( [saledate], [trn], [purchaser], [primaryaddress], [secondaryaddress], [city], [state], [zip], [itempurchased], [amtpurchased], [amtshipped], [purchaserid], [saleID] )
 SELECT [saledate],
 [trn],
 [purchaser],
 [primaryaddress],
 [secondaryaddress],
 [city],
 [state],
 [zip],
 [itempurchased],
 [amtpurchased],
 [amtshipped],
 @PurchaserID,
 CASE WHEN @SaleID >= 1 THEN @SaleID
 ELSE NULL END
 FROM seccompliance
 WHERE crl.pijad = @PurchaserID
 AND ( @SaleID >= 1
 OR cag.playja = @SaleID )
 OPTION( RECOMPILE );

Again, I don't know exactly what should be happening in there, but this should be close. A few other notes about this query:

  1. Don't use a bare SELECT *, use an explicit select list
  2. Instead of dyanamically including the @SaleId stuff, you can just put the logic in there
  3. When you have a predicate like CAST( ColumnName AS datatype ) = @ParameterValue, you will get much better performance if you do ColumnName = CAST( @ParameterValue AS datatype ) or ColumnName = @ParameterValue where @ParameterValue has already been cast to the appropriate type.
  4. The use of optional parameters (which is what @SaleID effectively is) can have a significant negative impact on plan choice and performance. You'll either need to recompile the query (what I opted to do) or continue using dynamic SQL to avoid that issue. For your use-case, RECOMPILE should be fine - you're already forcing recompiles of the query every iteration of the loop because it isn't parameterized.

There is nothing novel to say aobut the AI vs LI queries, so I'll move along.

When you create your bcp command, it would be much nicer to just give it a stored procedure, so you can avoid more dynamic SQL.

Lastly, I think a lot of the work you do in the CURSOR could be done in a set-based approach, then just use the CURSOR to send the mail. This will perform much better than your current procedure.

DECLARE @PurchaserList table
(
 PurchaserId varchar(50) NOT NULL,
 SaleId varchar(50) NULL
);
INSERT INTO [LI]
( [saledate], [trn], [purchaser], [primaryaddress], [secondaryaddress], [city], [state], [zip], [itempurchased], [amtpurchased], [amtshipped], [purchaserid], [saleID] )
OUTPUT Inserted.purchaserid, Inserted.saleid INTO @PurchaserList( PurchaserId, SaleId )
 SELECT [saledate],
 [trn],
 [purchaser],
 [primaryaddress],
 [secondaryaddress],
 [city],
 [state],
 [zip],
 [itempurchased],
 [amtpurchased],
 [amtshipped],
 @PurchaserID [purchaserid],
 CASE WHEN @SaleID >= 1 THEN @SaleID
 ELSE NULL END [saleid]
 FROM seccompliance
 WHERE crl.pijad = @PurchaserID
 AND ( @SaleID >= 1
 OR cag.playja = @SaleID );

From there, you are going to CURSOR over distinct purchaser and sale combinations (also, you should include the scope in your cursor definition):

DECLARE cursor1 CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
 SELECT DISTINCT
 HoldingTable.purchaserid,
 HoldingTable.purchaser,
 HoldingTable.saleid,
 HoldingTable.AI,
 HoldingTable.LI,
 HoldingTable.ChkDirectory
 FROM #HoldingTable HoldingTable
 INNER JOIN @PurchaserList PurchaserList
 ON HoldingTable.purchaserid = PurchaserList.PurchaserId
 AND ( HoldingTable.saleid = PurchaserList.SaleId
 OR ( HoldingTable.saleid IS NULL
 AND PurchaserList.SaleId IS NULL ))
 ORDER BY HoldingTable.purchaser ASC;

And then the body of your WHILE loop is largely the same, but without the INSERTs into the AI and LI tables

answered Oct 25, 2019 at 21:18
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.