Date and Table name as parameter in Dynamic SQL - sql

I'm Trying to create a stored procedure that will allow me to pick a start date and end date to get data from and to have a variable table name to write this data to.
I would like to pass in the two dates and the table name as parameters in the stored procedure. Here is that part I'm stuck on. I took out the stored procedure to try and get this working. this way I can see the lines the error is on.
DECLARE #MinDateWeek DATETIME
SELECT #MinDateWeek= DATEADD(WEEK, DATEDIFF(WEEK,0,GETDATE()), -7)
DECLARE #MaxDateWeek DATETIME
SELECT #MaxDateWeek= DATEADD(WEEK, DATEDIFF(WEEK,0,GETDATE()),0)
DECLARE #SQLCommand NVARCHAR(MAX)
SET #SQLCommand = ' --ERROR ON THIS LINE
-- Getting how much space is used in the present
DECLARE #Present Table (VMName NVARCHAR(50), UseSpace float(24))
INSERT INTO #Present
SELECT VMName
,SUM(CapacityGB-FreeSpaceGB)
FROM VMWareVMGuestDisk
GROUP BY VMName;
-- Getting how much space was used at the reference date
DECLARE #Past Table (VMName NVARCHAR(50), UseSpace float(24))
INSERT INTO #Past
SELECT VMName
,SUM(CapacityGB-FreeSpaceGB)
FROM VMWareVMGuestDisk
WHERE Cast([Date] AS VARCHAR(20))= '''+CAST(#MinDateWeek AS varchar(20))+'''
GROUP BY VMName;
--Inserting the average growth(GB/DAY) between the 2 dates in a Temporary Table
CREATE TABLE #TempWeek (VMName NVARCHAR(50)
, CapacityGB float(24)
, GrowthLastMonthGB float(24)
, FreeSpace FLOAT(24) )
INSERT INTO #TempWeek
SELECT DISTINCT V.VMName
,SUM(V.CapacityGB)
,SUM(((W1.UseSpace-W2.UseSpace)/(DATEDIFF(DAY,'''+CONVERT(VARCHAR(50),#MaxDateWeek)+''','''+CONVERT(VARCHAR (50),#MaxDateWeek)+'''))))
,SUM(V.FreeSpaceGb)
FROM VMWareVMGuestDisk AS V
LEFT JOIN
#Present AS W1
ON
V.VMName=W1.VMName
LEFT JOIN
#Past AS W2
ON
W1.VMName=W2.VMName
WHERE (CONVERT(VARCHAR(15),Date))='''+CONVERT(VARCHAR(50),#MaxDateWeek)+'''
GROUP BY V.VMName;
-- Checking if there is already data in the table
TRUNCATE TABLE SAN_Growth_Weekly;
--insert data in permanent table
INSERT INTO SAN_Growth_Weekly (VMName,Datacenter,Cluster,Company,DaysLeft,Growth, Capacity,FreeSpace,ReportDate)
SELECT DISTINCT
G.VMName
,V.Datacenter
,V.Cluster
,S.Company
, DaysLeft =
CASE
WHEN G.GrowthLastMonthGB IS NULL
THEN ''NO DATA''
WHEN (G.GrowthLastMonthGB)<=0
THEN ''UNKNOWN''
WHEN (G.FreeSpace/G.GrowthLastMonthGB)>0 AND (G.FreeSpace/G.GrowthLastMonthGB) <=30
THEN ''Less then 30 Days''
WHEN (G.FreeSpace/G.GrowthLastMonthGB)>30 AND (G.FreeSpace/G.GrowthLastMonthGB)<=60 THEN ''Less then 60 Days''
WHEN (G.FreeSpace/G.GrowthLastMonthGB)>60 AND (G.FreeSpace/G.GrowthLastMonthGB)<=90
THEN ''Less then 90 Days''
WHEN (G.FreeSpace/G.GrowthLastMonthGB)>90 AND (G.FreeSpace/G.GrowthLastMonthGB)<=180 THEN ''Less then 180 Days''
WHEN (G.FreeSpace/G.GrowthLastMonthGB)>180 AND (G.FreeSpace/G.GrowthLastMonthGB)<=365 THEN ''Less then 1 Year''
ELSE ''Over 1 Year''
END
,G.GrowthLastMonthGB
,G.CapacityGB
,G.FreeSpace
,'''+#MaxDateWeek+'''
FROM #tempWeek AS G
RIGHT JOIN VMWareVMGuestDisk AS V
ON V.VMName = G.VMName COLLATE SQL_Latin1_General_CP1_CI_AS
LEFT JOIN Server_Reference AS S
ON G.VMName COLLATE SQL_Latin1_General_CP1_CI_AS=S.[Asset Name]
WHERE '''+CONVERT(VARCHAR(50),#MaxDateWeek)+'''= CONVERT(VARCHAR(50),V.Date);'
EXEC sp_executesql #SQLCommand;
The error I get is
Conversion failed when converting date and/or time from character
string.
Thanks for the help.

Are you forgetting to enclose your Group By in the dynamic sql?:
ALTER PROCEDURE SAN_DISK_GROWTH
#MaxDateWeek DATETIME ,
#MinDateWeek DATETIME
AS
BEGIN
DECLARE #SQLCommand NVARCHAR(MAX)
SELECT #SQLCommand = '
DECLARE #Present Table (VMName NVARCHAR(50), UseSpace float(24))
INSERT INTO #Present
SELECT VMName
,SUM(CapacityGB - FreeSpaceGB)
FROM VMWareVMGuestDisk
WHERE CONVERT(VARCHAR(15),Date) = '''
+ CONVERT(VARCHAR(50), #MaxDateWeek) + ''' GROUP BY VMName;'
END

Try specifying your date/time values as parameters to the dynamic SQL query. In other words, instead of converting the dates to a varchar, use parameters in the query:
WHERE #MaxDateWeek = V.Date;
And pass the parameters on the call to sp_executesql like so:
EXEC sp_executesql #SQLCommand,
'#MindateWeek datetime, #MaxDateWeek datetime',
#MinDateWeek = #MinDateWeek,
#MaxDateWeek = #MaxDateWeek
Then you won't have to convert your dates to strings.
Note that this does not work for dynamic table names or column names. Those need to be concatenated together as part of the dynamic SQL itself.
For example, if you had a table name variable like this:
declare #TableName sysname
set #TableName = 'MyTable'
And you wanted the dynamic SQL to retrieve data from that table, then you would need to build your FROM statement like this:
set #SQLCommand = N'SELECT ...
FROM ' + #TableName + N' WHERE...
This build the name into the SQL like so:
'SELECT ... FROM MyTable WHERE...'

Related

Dynamic SQL Procedure with Pivot displaying counts based on Date Range

I have a table which contains multiple user entries.
I want to pull counts of user entries based on date range passed to a stored procedure.
start date: 11/9/2017
end date: 11/11/2017
However the response needs to be dynamic based on amount of days in the date range.
Here is a desired format:
Now that you have provided examples, I have updated my answer which provides you with a solution based on the data you have provided.
Note that you are able to change the date range and the query will update accordingly.
Bare in mind that this SQL query is for SQL Server:
create table #tbl1 (
[UserId] int
,[UserName] nvarchar(max)
,[EntryDateTime] datetime
);
insert into #tbl1 ([UserId],[UserName],[EntryDateTime])
values
(1,'John Doe','20171109')
,(1,'John Doe','20171109')
,(1,'John Doe','20171110')
,(1,'John Doe','20171111')
,(2,'Mike Smith','20171109')
,(2,'Mike Smith','20171110')
,(2,'Mike Smith','20171110')
,(2,'Mike Smith','20171110')
;
-- declare variables
declare
#p1 date
,#p2 date
,#diff int
,#counter1 int
,#counter2 int
,#dynamicSQL nvarchar(max)
;
-- set variables
set #p1 = '20171109'; -- ENTER THE START DATE IN THE FORMAT YYYYMMDD
set #p2 = '20171111'; -- ENTER THE END DATE IN THE FORMAT YYYYMMDD
set #diff = datediff(dd,#p1,#p2); -- used to calculate the difference in days
set #counter1 = 0; -- first counter to be used in while loop
set #counter2 = 0; -- second counter to be used in while loop
set #dynamicSQL = 'select pivotTable.[UserId] ,pivotTable.[UserName] as [Name] '; -- start of the dynamic SQL statement
-- to get the dates into the query in a dynamic way, you need to do a while loop (or use a cursor)
while (#counter1 < #diff)
begin
set #dynamicSQL += ',pivotTable.[' + convert(nvarchar(10),dateadd(dd,#counter1,#p1),120) + '] '
set #counter1 = (#counter1 +1)
end
-- continuation of the dynamic SQL statement
set #dynamicSQL += ' from (
select
t.[UserId]
,t.[UserName]
,cast(t.[EntryDateTime] as date) as [EntryDate]
,count(t.[UserId]) as [UserCount]
from #tbl1 as t
where
t.[EntryDateTime] >= ''' + convert(nvarchar(10),#p1,120) + ''' ' +
' and t.[EntryDateTime] <= ''' + convert(nvarchar(10),#p2,120) + ''' ' +
'group by
t.[UserId]
,t.[UserName]
,t.[EntryDateTime]
) as mainQuery
pivot (
sum(mainQuery.[UserCount]) for mainQuery.[EntryDate]
in ('
;
-- the second while loop which is used to create the columns in the pivot table
while (#counter2 < #diff)
begin
set #dynamicSQL += ',[' + convert(nvarchar(10),dateadd(dd,#counter2,#p1),120) + ']'
set #counter2 = (#counter2 +1)
end
-- continuation of the SQL statement
set #dynamicSQL += ')
) as pivotTable'
;
-- this is the easiet way I could think of to get rid of the leading comma in the query
set #dynamicSQL = replace(#dynamicSQL,'in (,','in (');
print #dynamicSQL -- included this so that you can see the SQL statement that is generated
exec sp_executesql #dynamicSQL; -- this will run the generate dynamic SQL statement
drop table #tbl1;
Let me know if that's what you were looking for.
If you are using MySQL this will make what you want:
SELECT UserID,
UserName,
SUM(Date = '2017-11-09') '2017-11-09',
SUM(Date = '2017-11-10') '2017-11-10',
SUM(Date = '2017-11-11') '2017-11-11'
FROM src
GROUP BY UserID
If you are using SQL Server, you could try it with PIVOT:
SELECT *
FROM
(SELECT userID, userName, EntryDateTime
FROM t) src
PIVOT
(COUNT(userID)
FOR EntryDateTime IN (['2017-11-09'], ['2017-11-10'], ['2017-11-11'])) pvt

SQL Feed Query results into Union All Statement

I have a COTS application database that creates a new table every week that follows a specific naming convention and always contains the same columns. I have written a query to select the tables within a certain time range:
DECLARE #today VARCHAR(8)
SET #today = CONVERT(VARCHAR(8),GETDATE(),112)
DECLARE #monthold VARCHAR(8)
SET #monthold = CONVERT(VARCHAR(8),dateadd(day,-31,#today),112)
SELECT name
FROM sys.tables
WHERE name <= 'Event_Audit'+ #today AND name >= 'Event_Audit'+ #monthold
AND name like 'Event_Audit%'
ORDER BY name DESC
Now I am looking for a way to have a query call each of the tables that is selected to aggregate the data. This will be used for SSRS reporting.
Something like this where table1, table2, etc. through all the included tables will get populated (whether it is 4, 5, or more):
SELECT *
FROM table1
UNION ALL
SELECT *
FROM table2
UNION ALL
...
ORDER BY EVENT_DATE DESC
I can't create views, new tables, or make any changes to existing tables.
Poor design will lead to hard work every time, this one is no exception. I'm guessing by now it's too late to give up the idea of creating the same table over and over again for each month.
You do not need to create a new or temporary table to execute sp_executeSql, you only need to generate the sql script:
DECLARE #today char(8) = CONVERT(char(8),GETDATE(),112),
#monthold char(8) = CONVERT(char(8),dateadd(day,-31,GETDATE()),112),
#sql nvarchar(max) = ''
SELECT #Sql = #Sql + ' UNION ALL SELECT * FROM ' + name
FROM sys.tables
WHERE name <= 'Event_Audit'+ #today AND name >= 'Event_Audit'+ #monthold
AND name like 'Event_Audit%'
ORDER BY name DESC
SET #SQL = STUFF(#SQL, 1, 11, '') + -- remove the first UNION ALL
' ORDER BY EVENT_DATE DESC' -- add the ORDER BY
PRINT #Sql
--EXEC sp_executeSql #Sql
Once you've printed the #Sql and checked it's ok, unremark the EXEC line and run your script.

Slow MSSQL stored procedure in processes excel files with only 30,000 rows

I have a web applciation with an iterface that users can uplaod files on. The data form the excel file is collected, concatenated and passed to
a stored procedure which process and returns data.
A brief explanation of the stored procedure.
The stored Procedure collects the string, break it down using a delimeter and stores it in a temp variable table.
Another process is run trough the temp table, where a count is done to find the exact match count and approximate match count by comparing each string
agains a view which contains
all the names to compare against for each row in the first
An exact match count is where the eact string is found in the view for example.. (Bobby Bolonski )
An approximate match is done using a levenshtein distance algorithm database function with a frequency of 2.
temo table #temp1.
The result (name, exactmatch count and approximate match count) are stored in the final temp table.
a select statement is run on the last temp table to return all the data to the application..
MY problem is that, when i passed huge files like and excel file with 27000 names. IT took like 2 hours to process and return data from the database.
I have checked both servers where the application is on and where the database is on.
On the application server. Both memory and cpu usage are less than 15 %
On the database server. both memory and cpu usage are also less than 15 %.
Am looking for advice on what improvements i can do to make the process faster.
Below is the copy of the stored procedure as it is doing all the work and returning the results to the web application.
CREATE PROCEDURE [dbo].[FindMatch]
#fullname varchar(max),#frequency int,
#delimeter varchar(max) AS
set #frequency = 2
declare #transID bigint
SELECT #transID = ABS(CAST(CAST(NEWID() AS VARBINARY(5)) AS Bigint))
DECLARE #exactMatch int = 99
DECLARE #approximateMatch int = 99
declare #name varchar(50)
DECLARE #TEMP1 TABLE (fullname varchar(max),approxMatch varchar(max), exactmatch varchar(max))
DECLARE #ID varchar(max)
--declare a temp table
DECLARE #TEMP TABLE (ID int ,fullname varchar(max),approxMatch varchar(max), exactmatch varchar(max))
--split and store the result in the #temp table
insert into #TEMP (ID,fullname) select * from fnSplitTest(#fullname, #delimeter)
--loop trough the #temp table
WHILE EXISTS (SELECT ID FROM #TEMP)
BEGIN
SELECT Top 1 #ID = ID FROM #TEMP
select #name = fullname from #TEMP where id = #ID
--get the exact match count of the first row from the #temp table and so on until the loop ends
select #exactMatch = count(1) from getalldata where replace(name,',','') COLLATE Latin1_general_CI_AI = #name COLLATE Latin1_general_CI_AI
--declare temp #TEMP3
DECLARE #TEMP3 TABLE (name varchar(max))
--insert into #temp 3 only the data that are similar to our search name so as not to loop over all the data in the view
INSERT INTO #TEMP3(name)
select name from getalldata where SOUNDEX(name) LIKE SOUNDEX(#name)
--get the approximate count using the [DEMLEV] function.
--this function uses the Damerau levenshtein distance algorithm to calculate the distinct between the search string
--and the names inserted into #temp3 above. Uses frequency 2 so as to eliminate all the others
select #approximateMatch = count(1) from #TEMP3 where
dbo.[DamLev](replace(name,',',''),#name,#frequency) <= #frequency and
dbo.[DamLev](replace(name,',',''),#name,#frequency) > 0 and name != #name
--insert into #temp1 at end of every loop results
insert into #TEMP1 (fullname,approxMatch, exactmatch) values(#name,#approximateMatch,#exactMatch)
insert into FileUploadNameInsert (name) values (#name + ' ' +cast(#approximateMatch as varchar) + ' ' + cast(#exactMatch as varchar) + ', ' + cast(#transID as varchar) )
DELETE FROM #TEMP WHERE ID= #ID
delete from #TEMP3
END
--Return all the data stored in #temp3
select fullname,exactmatch,approxMatch, #transID as transactionID from #TEMP1
GO
In my opinion,
Use Openrowset to directly read the records into a pre-defined, properly indexed table of your database.
Now, perform your operations using this table at back-end using pre-defined Stored Procedures.
It should take around 15 minutes for 30,000 rows.

How to replace text in a string with values from a column in sql [duplicate]

This question already has an answer here:
How to replace a string with values from columns in a table in SQL
(1 answer)
Closed 9 years ago.
Exp Major Start
__________________________________________________________
| |
'My names are W.Major and W.Start' | Hal | Bark
___________________________________|________|_________________
'W.Major is a doctor' | Mark | Slope
___________________________________|________|_______________
Hi All suppose I have the table above in SQL server management studio
and for any text in the Exp column I want to replace W.Major with the value in the Major column and wherever there is a W.Start I want to replace it with the value in the Start column.
Do you know what type of SP I have to write to get this accomplished?
Well you can use a Dynamic SQL and UNPIVOT to get table with all unit replacements and then you run a while loop to replace expressions one by one till you get your result.
I am sure there can be better techniques but here is my solution.
Please mark it as answer for my effort :)
IF OBJECT_ID ('tempdb.dbo.#temptable') IS NOT NULL DROP TABLE #temptable
CREATE TABLE #tempTable ( ID INT IDENTITY(1, 1), [Exp] nvarchar(4000) not NULL, replacementWord nvarchar(50) not null, Wordvalue nvarchar(50) not null, flag bit null, ReplacedExp nvarchar(4000) null)
DECLARE #query VARCHAR(4000)
DECLARE #queryRWords VARCHAR(2000)
SELECT #queryRWords =
STUFF((select DISTINCT '],['+ LTRIM(C.name) from sys.Columns C INNER JOIN sys.objects O on O.object_id=C.object_id where O.name='yourtable' and O.type_desc='USER_TABLE' and C.name not like 'Exp' ORDER BY '],['+LTRIM(C.name) FOR XML PATH('') ),1,2,'') + ']'
SET #query='INSERT INTO #tempTable(Exp, replacementWord, WordValue) select [Exp], [replacementWord],[WordValue] FROM (SELECT [Exp], [major],[start] FROM [yourtable]) p
UNPIVOT([Wordvalue] FOR [replacementWord] IN ('+#queryRWords+'))AS unpvt'
EXECUTE(#query)
UPDATE #tempTable SET ReplacedExp=[Exp]
DECLARE #ExpCount INT
SELECT #ExpCount= COUNT(*) FROM #tempTable
WHILE #ExpCount >0
BEGIN
IF((SELECT [Exp] From #tempTable where Id=#ExpCount)<>(SELECT [Exp] from #tempTable where Id=(#ExpCount-1)))
BEGIN
UPDATE #tempTable SET FLAG=1, ReplacedExp= REPLACE(ReplacedExp, CAST('W.'+replacementWord AS VARCHAR), WordValue) FROM #tempTable WHERE Id=#ExpCount
END
ELSE
BEGIN
UPDATE #tempTable SET ReplacedExp= REPLACE(ReplacedExp, CAST('W.'+replacementWord AS VARCHAR), WordValue) FROM #tempTable WHERE Id=#ExpCount
UPDATE #tempTable SET ReplacedExp= (SELECT ReplacedExp FROM #temptable where Id=#ExpCount) WHERE Id=(#ExpCount-1)
END
SET #ExpCount=#ExpCount-1
END
UPDATE #tempTable
SET flag=1 where Id=1
SELECT ReplacedExp FROM #tempTable where flag=1
Here is the sample TSQL where you can have a good start
SELECT Exp, Major, Start
, Replace(Replace(Exp, 'W.Major', Major), 'W.Start', Start) As Result
FROM [Your Table Name]

TSQL: Using a Table in a Variable in a Function

I'm trying to do a select from a table that will need to be in a variable. I'm working with tables that are dynamically created from an application. The table will be named CMDB_CI_XXX, where XXX will be an integer value based on a value in another table. The ultimate goal is to get the CI Name from the table.
I've tried passing the pieces that make up the table name to a function and string them together and then return the name value, but I'm not allowed to use an EXEC statement in a function.
This is what I want to execute to get the name value back:
Select [Name] from 'CMDB_CI_' + C.CI_TYPE_ID + Where CI_ID = c.CI_ID
This is the code in the SP that I'd like to use the function in to get the name value:
SELECT
CI_ID,
C.CI_TYPE_ID,
CI_CUSTOM_ID,
STATUS,
CI_TYPE_NAME,
--(Select [Name] from CMDB_CI_ + C.CI_TYPE_ID + Where CI_ID = c.CI_ID)
FROM [footprints].[dbo].[CMDB50_CI_COMMON] c
join [footprints].[dbo].[CMDB50_CI_TYPE] t
on c.CI_TYPE_ID = t.CI_TYPE_ID
where status <> 'retired'
order by CI_TYPE_NAME
I'm not sure what to do with this. Please help?
Thanks,
Jennifer
-- This part would be a SP parameter I expect
DECLARE #tableName varchar(100)
SET #tableName = 'CMDB_CI_508'
-- Main SP code
DECLARE #sqlStm VARCHAR(MAX)
SET #sqlStm = 'SELECT *
FROM '+ #tableName
EXEC (#sqlStm)
Fiddle http://sqlfiddle.com/#!3/436a7/7
First off, yes, I know it's a bad design. I didn't design it. It came with the problem tracking software that my company bought for our call center. So I gave up altogether on the approach I was going for and used a cursor to pull all the the names from the various tables into one temp table and then used said temp table to join to the original query.
ALTER Proc [dbo].[CI_CurrentItems]
As
Declare #CIType nvarchar(6)
Declare #Qry nvarchar(100)
/*
Create Table Temp_CI
( T_CI_ID int,
T_CI_Type_ID int,
T_Name nvarchar(400)
)
*/
Truncate Table Temp_CI
Declare CI_Cursor Cursor For
select distinct CI_TYPE_ID FROM [footprints].[dbo].[CMDB50_CI_COMMON]
where STATUS <> 'Retired'
Open CI_Cursor
Fetch Next from CI_Cursor into #CIType
While ##FETCH_STATUS = 0
BEGIN
Set #Qry = 'Select CI_ID, CI_Type_ID, Name from Footprints.dbo.CMDB50_CI_' + #CIType
Insert into Temp_CI Exec (#Qry)
Fetch Next from CI_Cursor into #CIType
END
Close CI_Cursor
Deallocate CI_Cursor
SELECT CI_ID,
C.CI_TYPE_ID,
CI_CUSTOM_ID,
STATUS,
CI_TYPE_NAME,
T_Name
FROM [footprints].[dbo].[CMDB50_CI_COMMON] c
JOIN [footprints].[dbo].[CMDB50_CI_TYPE] t
ON c.CI_TYPE_ID = t.CI_TYPE_ID
JOIN Temp_CI tc
ON c.CI_ID = tc.T_CI_ID
AND t.CI_TYPE_ID = tc.T_CI_TYPE_ID
WHERE STATUS <> 'retired'
ORDER BY CI_TYPE_NAME