Dynamic SQL running way too longer - sql

I am trying to run a dynamic SQL query that is supposed to load more than 200M records. I tried to load all the records at once but got 'System.OutOfMemory' Exception hence decided to break the time-interval into months.The following runs for more than an hour even though if I run it for just a month. I hard-coded the dates and ran it without the 'While' and it runs normally. Can someone check and let me know if there is some error in it.
Below is my query
DECLARE #FromDate VARCHAR(MAX)='',
#params nvarchar(max) = N'#StartDate Date out,#EndDate Date out,#FromDate date out,#ToDate date out',
#SQL NVARCHAR(MAX)=’’,
#ToDate VARCHAR(MAX)='',
#StartDate VARCHAR(MAX)=’’,
#EndDate VARCHAR(MAX)=’’;
SELECT #FromDate= DATEADD(DAY,-365, GETUTCDATE()), #ToDate = GETUTCDATE();
SELECT #StartDate=#FromDate, #EndDate=CAST(DATEADD(MONTH,1, #FromDate) AS DATE);
SET #SQL = '
WHILE ''' + #FromDate + ''' < ''' + #ToDate + '''
BEGIN
INSERT INTO dbo.Sales
(
ProductID,
SaleDate,
Quantity
)
Select
ProductID,
SaleDate,
Quantity
from
OPENQUERY(TeraData1,''
Select
PR_ID AS ProductID,
SL_Date AS SaleDate,
PR_QTY AS Quantity
FROM
Sales.Product
where
SaleDate BETWEEN ' + #StartDate + 'AND '+ #EndDate + '
'')
SET #FromDate =DATEADD(MONTH,1,'''+ #FromDate +''')
SET #StartDate =DATEADD(MONTH,1,'''+ #StartDate +''')
SET #EndDate =DATEADD(MONTH,1,'''+ #EndDate +''')
END’
EXECUTE sp_executesql #SQL, #params,
#FromDate=#FromDate,
#ToDate=#ToDate,
#StartDate = #StartDate out,
#EndDate = #EndDate out;

DECLARE #FromDate DATE
SET #FromDate = DATEADD(YEAR,-1,GETUTCDATE())
WHILE #FromDate < GETUTCDATE()
BEGIN
INSERT INTO dbo.Sales
(
ProductID,
SaleDate,
Quantity
)
Select
ProductID,
SaleDate,
Quantity
from
OPENQUERY(TeraData1,
Select
PR_ID AS ProductID,
SL_Date AS SaleDate,
PR_QTY AS Quantity
FROM
Sales.Product
where
SaleDate >= #FromDate AND SaleDate < DATEADD(MONTH,1,#FromDate)
)
SET #FromDate =DATEADD(MONTH,1,#FromDate)
END
It looks like you have way more variables/dates than you need and that you don't need dynamic SQL unless you are wanting to pass the linked server name or some other column dynamically.
Also stick with the appropriate datatype for the parameter instead of letting SQL infer the values you want.
Also your use of BETWEEN for the dates would have duplicated the certain dates because it would be inclusive, to fix limit 1 side of the comparison.
If you do want to do this dynamically keep the control flow in your non dynamic SQL and pass the parameter here is an example:
DECLARE #FromDate DATE
SET #FromDate = DATEADD(YEAR,-1,GETUTCDATE())
WHILE #FromDate < GETUTCDATE()
BEGIN
DECLARE #LinkedServerName NVARCHAR(MAX) = 'TeraData1'
DECLARE #ParamDef NVARCHAR(MAX) = '#FromDate DATE'
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = '
INSERT INTO dbo.Sales
(
ProductID,
SaleDate,
Quantity
)
Select
ProductID,
SaleDate,
Quantity
from
OPENQUERY(' + #LinkedServerName + ',
Select
PR_ID AS ProductID,
SL_Date AS SaleDate,
PR_QTY AS Quantity
FROM
Sales.Product
where
SaleDate >= #FromDate AND SaleDate < DATEADD(MONTH,1,#FromDate)
)'
EXECUTE sp_executesql #SQL, #PramDef, #FromDate = #FromDate
SET #FromDate =DATEADD(MONTH,1,#FromDate)
END

Related

SQL Server: fetching a variable name from a table and assigning value inside the stored procedure

I have some variable name stored in a table, which I need to populate in a stored procedure based on a condition.
For example:
Query: select column1 from TestTable
Output of Query: #FromDate
Now inside the stored procedure, I have the following:
DECLARE #FromDate DATE = '2022-06-01'
DECLARE #QueryResult Varchar(50);
DECLARE #SQLCommand Varchar(50);
SELECT #QueryResult = column1
FROM TestTable
SET #SQLCommand = 'SELECT * FROM emp WHERE joindate >= ''' + #QueryResult + ''';'
EXEC (#SQLCommand);
Now I am expecting that result should be all the employee whose joindate >= '2022-06-01'. Or in other words, I am expecting to use #FromDate variable to fetch data. But when i run query, I get the following error:
Msg 137, Level 15, State 2, Line 1
Must declare the scalar variable "#FromDate"
When I run:
print #SQLCommand;
I get:
select * from emp where joindate >= '#FromDate';
While I am expecting that #FromDate value should be populated here at run time.
Will be thankful for any help regarding this.
Update: actually, there is a loop inside my sp, which fetches the data from table (data contains variable names to be used in the stored procedure in different logic I) like for a particular record: I need to add 20 days in #fromdate, and for another record I need to add 30 days. Now when my loop will run, it will fetch either dateadd(day, 20, #fromdate) or dateadd(day, 30, #fromdate) from table based on where clause and then I need to fill in the value of #fromdate (this is parametrise variable) and fetch the results accordingly.
Update 2:
Please see below my code
USE [GBI_archive]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_Process_Data]
(#StartDate DATE = NULL,
#EndDate DATE = NULL)
AS
DECLARE #FromDate DATE = ISNULL(#StartDate, DATEADD(DAY, 1, EOMONTH(GETDATE(), -1)));
DECLARE #ToDate DATE = ISNULL(#EndDate, GETDATE());
DECLARE #CalculationMethodFromDate VARCHAR(255);
DECLARE #SelectStatement VARCHAR(255);
DECLARE #TableIntoStatement VARCHAR(255);
DECLARE #FromStatement VARCHAR(255);
DECLARE #SQLCommand VARCHAR(255);
DECLARE cursor_product CURSOR FOR
SELECT calculation_method_from_date
FROM [dbo].[Calculation_Method_Configuration];
-- Here output can be DATEADD(DAY, -6, #FromDate) or DATEADD(DAY, -14, #FromDate) or so on
OPEN cursor_product;
FETCH NEXT FROM cursor_product INTO #CalculationMethodFromDate
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #CalculationMethodFromDate
SET #SelectStatement = 'SELECT CURRENT_TIMESTAMP, * ';
SET #TableIntoStatement = 'INTO [dbo].[Table_For_Function_Output]';
SET #FromStatement = 'FROM [dbo].[EmployeeData] where joindate >= ''' + #CalculationMethodFromDate + ''';'
-- SET #SQLCommand = concat (#SelectStatement , ' ', #TableIntoStatement , ' ', #FromStatement);
PRINT #SQLCommand;
EXEC (#SQLCommand);
FETCH NEXT FROM cursor_product INTO #CalculationMethodFromDate,
END;
CLOSE cursor_product;
DEALLOCATE cursor_product;
GO
Now for anyone iteration of loop, print #SQLCommand shows this (if #CalculationMethodFromDate = 'DATEADD(DAY, -6, #FromDate)') :
SELECT CURRENT_TIMESTAMP, * INTO [dbo].[Table_For_Function_Output] FROM [dbo].[EmployeeData] where joindate >= 'DATEADD(DAY, -6, #FromDate)';
and exec command throws this error:
Msg 137, Level 15, State 2, Line 1
Must declare the scalar variable "#FromDate"
But if I am passing #FromDate = '2022-06-07' as parameter to this sp, my expectations for print #SQLCommand shows is:
SELECT CURRENT_TIMESTAMP, * INTO [dbo].[Table_For_Function_Output] FROM [dbo].[EmployeeData] where joindate >= '2022-06-01';
In short: #FromDate variable coming from database at runtime, should be assigned a value from stored procedure.
You don't need a cursor here, you just need to build one big UNION ALL statement. And you need to pass the #FromDate and #ToDate into the dynamic SQL.
CREATE OR ALTER PROCEDURE [dbo].[sp_Process_Data]
#StartDate DATE = NULL,
#EndDate DATE = NULL
AS
DECLARE #FromDate DATE = ISNULL(#StartDate, DATEADD(DAY, 1, EOMONTH(GETDATE(), -1)));
DECLARE #ToDate DATE = ISNULL(#EndDate, GETDATE());
DECLARE SQLCommand nvarchar(max) = (
SELECT STRING_AGG(N'
SELECT CURRENT_TIMESTAMP, e.*
FROM dbo.EmployeeData e
where e.joindate >= ' + CAST(cm.calculation_method_from_date AS nvarchar(max))
, '
UNION ALL ')
FROM dbo.Calculation_Method_Configuration cm
);
PRINT #SQLCommand;
EXEC sp_executesql
#SQLCommand,
N'#FromDate DATE, #ToDate DATE',
#FromDate = #FromDate,
#ToDate = #ToDate;
go
The design itself is questionable. You should really just have a column which tells you how many days to add, then you can just do
SELECT CURRENT_TIMESTAMP, e.*
FROM dbo.EmployeeData e
JOIN dbo.Calculation_Method_Configuration cm
ON e.joindate >= DATEADD(day, -cm.days, #FromDate);
well actually you could simply use sp_executesql for this.
Simplified sample:
-- demo table:
SELECT DATEADD(day, -7, GETDATE()) [Date] INTO [#demo] UNION ALL SELECT DATEADD(day, 1, GETDATE());
-- demo:
DECLARE #CalculationMethodFromDate NVARCHAR(MAX) = N'DATEADD(DAY, -6, #FromDate)';
DECLARE #FromDate DATE = GETDATE();
DECLARE #SQL NVARCHAR(MAX) = N'SELECT * FROM [#demo] WHERE [Date] >= '+#CalculationMethodFromDate+N';';
EXEC sp_executesql #SQL, N'#FromDate DATE', #FromDate=#FromDate;
--cleanup
drop table [#demo];

Dates returned as columns in SQL Select

My user will submit a FromDate and a ToDate. What I want to happen is to select the dates that fall in between these dates, which I have accomplished with the script below. The dates will by dynamic.
DECLARE #fromDateParam DATETIME = '2022-01-24 00:00:00.000'
DECLARE #toDateParam DATETIME = '2022-01-29 00:00:00.000'
;WITH fnDateNow(DayOfDate) AS
(
SELECT #fromDateParam AS TransactionDate
UNION ALL
SELECT DayOfDate + 1
FROM fnDateNow
WHERE DayOfDate < #toDateParam
)
SELECT fnDateNow.DayOfDate AS TransactionDate
FROM fnDateNow
This returns that dates as rows. What I am looking for is a way to make these dates return as the columns for a different script.
This table is called DailyTransactionHeader and it has a column [TransactionDate] and another one called [Amount].
There is the probability that their is not a DailyTransactionHeader with the specified Date for this I am looking to return 0.
So I am trying to have the data look like this (I formatted the date) There would be more than one row, but I just wanted to show an example of what I am trying to accomplish.
I appreciate any help,
Thanks
You can do it using dynamic sql. For example:
CREATE PROCEDURE [GET_DATE_TABLE]
(
#FROMDATE DATETIME,
#TODATE DATETIME
)
AS
DECLARE #PDATE DATETIME
DECLARE #SQL VARCHAR(MAX)
DECLARE #SEP VARCHAR(10)
SET #PDATE = #FROMDATE
SET #SQL = 'SELECT '
SET #SEP = ''
WHILE #PDATE < #TODATE
BEGIN
SET #SQL = #SQL + #SEP + 'NULL as [' + CONVERT(VARCHAR, CONVERT(DATE, #PDATE)) + ']'
SET #PDATE = #PDATE + 1
SET #SEP = ', '
END;
EXEC(#SQL)
Test Example:
DECLARE #fromDateParam DATETIME = '2022-01-24 00:00:00.000'
DECLARE #toDateParam DATETIME = '2022-01-29 00:00:00.000'
exec dbo.GET_DATE_TABLE #fromDateParam, #toDateParam

Varchar and Date Incompatible SQL

I have written the below SQL and trying to do it using variables.
--Select DB
USE [gkretail_master]
--Declare Varibles
DECLARE #StartDate DATE;
DECLARE #EndDate DATE;
DECLARE #SQL VARCHAR(8000);
--Set Variables
SET #StartDate = '2018-05-23 00:00:00.000';
SET #EndDate = '2018-05-23 23:59:00.000'
SET #SQL =
'SELECT
RETAIL_STORE_NUMBER AS ''Store Number'',
DESCRIPTION_1 AS ''Store Name'',
CAST(SUM(BRUTTOGES) AS DECIMAL(18,2)) AS ''Total Taken''
FROM [GKRETAIL_MASTER].[GK_BONKOPF]
JOIN [gkretail_master].[GK_STORE_DATA] ON [GK_BONKOPF].[ID_BSNGP] = [GK_STORE_DATA].[ID_BSNGP]
WHERE
AKTDAT > '+ #StartDate +'
AND
AKTDAT < '+ #EndDate +'
GROUP BY [GK_BONKOPF].[ID_BSNGP], DESCRIPTION_1, RETAIL_STORE_NUMBER';
EXECUTE(#SQL);
It however returns the error:
The data types varchar and date are incompatible in the add operator.
I have done some googling and tried to resolve it using both CAST and convert but it flags up more errors.
Any ideas?
Exactly. That is one important reason why you should pass parameters using sp_executesql:
SET #SQL =
'SELECT
RETAIL_STORE_NUMBER AS ''Store Number'',
DESCRIPTION_1 AS ''Store Name'',
CAST(SUM(BRUTTOGES) AS DECIMAL(18,2)) AS ''Total Taken''
FROM [GKRETAIL_MASTER].[GK_BONKOPF]
JOIN [gkretail_master].[GK_STORE_DATA] ON [GK_BONKOPF].[ID_BSNGP] = [GK_STORE_DATA].[ID_BSNGP]
WHERE
AKTDAT > #StartDate
AND
AKTDAT < #EndDate
GROUP BY [GK_BONKOPF].[ID_BSNGP], DESCRIPTION_1, RETAIL_STORE_NUMBER';
EXEC sp_executesql #SQL,
N'#StartDate date, #EndDate date',
#StartDate = #StartDate, #EndDate = #EndDate;

Conversion date error in SQL

When executing the below dynamic SQL statement, I get an error:
Conversion failed when converting date and/or time from character string
[ReturnDate] is defined as datetime and #FromDate and #ToDate parameters are being passed as type datetime as well. What am I missing ?
Set #SQLString =
'Select
[ID], [ReturnDate], [PolicyNumber]
From
Bil_ReturnsRepository
Where
(' + #PolicyNumber + ' is null or PolicyNumber = (' + #PolicyNumber + '))
and (ReturnDate) >= Convert(date, ' + #FromDate + '))
and (ReturnDate) <= Convert(date, ' + #ToDate + '))
and PaymentAmount > 0.00'
Presumably, you are using SQL Server. If so, learn to use sp_executesql. One of its powers is the ability to pass in parameters:
Set #SQLString = '
Select [ID], [ReturnDate], [PolicyNumber]
From Bil_ReturnsRepository
Where (#PolicyNumber is null or PolicyNumber = #PolicyNumber) and
(ReturnDate >= #p_FromDate) and
(ReturnDate <= #p_ToDate) and
PaymentAmount > 0.00
';
declare #p_fromdate date;
declare #p_todate date;
select #p_fromdate = convert(date, #fromdate),
#p_todate = convert(date, #todate);
exec sp_executesql #sql,
N'#p_fromdate date, #p_todate date, #policynumber int',
#p_fromdate=#p_fromdate, #p_todate=#p_todate, #policynumber=#policynumber;
The variables #p_fromdate and #p_todate are not necessary if #fromdate and #todate already have the correct types.
You cannot concatenate string with datetime, I would suggest you to
use sp_executesql to parameterize the dynamic sql
declare #SQLString nvarchar(max)--should be nvarchar
Set #SQLString =
'Select
[ID]
,[ReturnDate]
,[PolicyNumber]
From Bil_ReturnsRepository
Where
(#PolicyNumber is null or PolicyNumber = #PolicyNumber) and
ReturnDate >= #FromDate and
ReturnDate <= #ToDate and
PaymentAmount > 0.00'
exec sp_executesql #SQLString,
N'#FromDate datetime, #ToDate datetime, #PolicyNumber int',
#FromDate, #ToDate, #PolicyNumber
By this way your query is much safer and cleaner without so many string concatenation.
Note : use appropriate datatype for #PolicyNumber in sp_executesql

SQL Server stored procedure optional parameters, include all if null

I have this stored procedure:
ALTER PROCEDURE [dbo].[GetCalendarEvents]
(#StartDate datetime,
#EndDate datetime,
#Location varchar(250) = null)
AS
BEGIN
SELECT *
FROM Events
WHERE EventDate >= #StartDate
AND EventDate <= #EndDate
AND (Location IS NULL OR Location = #Location)
END
Now, I have the location parameter, what I want to do is if the parameter is not null then include the parameter in where clause. If the parameter is null I want to completely ignore that where parameter and only get the result by start and end date.
Because when I'm doing this for example:
EXEC GetCalendarEvents '02/02/2014', '10/10/2015', null
I'm not getting any results because there are other locations which are not null and since the location parameter is null, I want to get the results from all the locations.
Any idea how can I fix this?
ALTER PROCEDURE [dbo].[GetCalendarEvents]
( #StartDate DATETIME,
#EndDate DATETIME,
#Location VARCHAR(250) = NULL
)
AS
BEGIN
SELECT *
FROM events
WHERE EventDate >= #StartDate
AND EventDate <= #EndDate
AND Location = ISNULL(#Location, Location )
END
If a NULL column is a possibility, then this would work.
ALTER PROCEDURE [dbo].[GetCalendarEvents]
( #StartDate DATETIME,
#EndDate DATETIME,
#Location VARCHAR(250) = NULL
)
AS
BEGIN
IF ( #loc IS NULL )
BEGIN
SELECT *
FROM events
WHERE EventDate >= #StartDate
AND EventDate <= #EndDate
END
ELSE
BEGIN
SELECT *
FROM events
WHERE EventDate >= #StartDate
AND EventDate <= #EndDate
AND Location = #Location
END
END
As having an 'OR' clause should be reasonably avoided due to possible performance issues.
The part in the WHERE clause should then read
AND (#Location IS NULL OR Location=#Location)
Try this
SELECT *
FROM Events
WHERE EventDate >= #StartDate
AND EventDate <= #EndDate
AND Location = Case When LEN(#Location) > 0 Then #Location Else Location End
It can be easily done with a dynamic sql query.
ALTER PROCEDURE [dbo].[GetCalendarEvents]
(#StartDate datetime,
#EndDate datetime,
#Location varchar(250) = null)
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX);
DECLARE #PARAMETER_DEFIINITION NVARCHAR(MAX);
DECLARE #WHERE_PART NVARCHAR(MAX);
SET #PARAMETER_DEFIINITION =' #StartDate DATETIME, #EndDate DATETIME, #Location VARCHAR(250) '
SET #SQL ='SELECT *
FROM Events
WHERE EventDate >= #StartDate
AND EventDate <= #EndDate '
IF #Location IS NOT NULL
BEGIN
SET #WHERE_PART = ' AND Location = #Location '
END
SET #SQL = #SQL + #WHERE_PART
EXEC SP_EXECUTESQL #SQL, #PARAMETER_DEFIINITION, #StartDate, #EndDate, #Location
END
Query will be dynamically created according to the parameters. In here if #location is null then it will not add to the where part.
If you want more on writing dynamic queries please refer this article. http://codingpulse.blogspot.com/2015/02/dynamic-sql-in-stored-procedure-part-1.html