Writing HTML out from a stored procedure and emailing it - sql

My boss insists I send him a daily status report "the hacker way" by outputing HTML from a stored procedure and sending the return to his email using Database mailer. My colleague and I both agree that we should be using SSRS for this matter but since we aren't the ones with the money we unfortunately have to get stuck doing it this way. I myself have never seen this done and am having some formatting issues getting my table to format with cells. Can anyone shed some light on how to get this to work?
BEGIN
DECLARE #tableHTML NVARCHAR(MAX) ;
SET #tableHTML =
N'<b>Shots Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST (
(SELECT 'Practice:' + PracticeName as Status, 'Aprima ID:'+ AprimaSiteID,
'Daily Count:' + CAST(Daily AS nvarchar(5)), 'Monthly Count:' + CAST(Monthly AS nvarchar(5))
FROM [CriticalKeyDatabase].[dbo].[ShotsManagement]
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>';
exec msdb.dbo.sp_send_dbmail #profile_name='aProfileName', #recipients='anEmail#Email.com', #body=#tableHTML, #subject='Daily Shots', #importance='High', #body_format = 'HTML'
END
GO
I am basing this off a previous employee he had who gave him the idea. I don't see any declarations for cells anywhere in his query. I am not the best at SQL either..
DECLARE #tableHTML NVARCHAR(MAX) ;
SET #tableHTML =
N'<b>Message Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Unsent Messages' as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101) and ReadyToSend = 1
UNION
SELECT 'Total Messages' as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)) A
ORDER BY StatusValue
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'<b>Sender Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Sender: ' + Requestor as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)
group by Requestor) A
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'<b>Recipient Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Recipient: ' + Recipient as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)
group by Recipient) A
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'<b>Event Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Event: ' + EventType as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)
group by EventType) A
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>';
exec msdb.dbo.sp_send_dbmail #profile_name='Localhost', #recipients='someemailAddresses#email.com', #body=#tableHTML, #subject='Daily Statistics', #importance='High', #body_format = 'HTML'
END
The previous employee's reports looked like this.
But mine are coming out like this.

All of the usual stuff about that being a bad practice aside, what your ex-coworker seems to be doing is leveraging SQL Server's native XML capabilities. You are not getting the "td" tags because they are not being assigned in your sub-query.
If you look at his queries, you will see the "td = ..." constructs. The reason they work is because the sub-query is being treated as XML (due to the FOR XML PATH construct) and thus the "td = " clauses are being mapped to XML nodes.
Try adding that to your code and see if you get the proper table cells...
BEGIN
DECLARE #tableHTML NVARCHAR(MAX) ;
SET #tableHTML =
N'<b>Shots Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST (
(SELECT td = 'Practice:' + PracticeName as Status, 'Aprima ID:'+ AprimaSiteID,
td = 'Daily Count:' + CAST(Daily AS nvarchar(5)), 'Monthly Count:' + CAST(Monthly AS nvarchar(5))
FROM [CriticalKeyDatabase].[dbo].[ShotsManagement]
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>';
exec msdb.dbo.sp_send_dbmail #profile_name='aProfileName', #recipients='anEmail#Email.com', #body=#tableHTML, #subject='Daily Shots', #importance='High', #body_format = 'HTML'
END
GO

This will htmlify and email the contents of a #temp table (including column names). All of the styling is done with CSS, so just change #style if you want it to look different. If you want to include multiple tables, you can modify it to add #table2, #table3, etc.
Example use:
SELECT *
INTO #email_data
FROM <yadda yadda>
EXEC [dbo].[email_table]
#tablename = '#email_data'
,#recipients = 'me#company.com; boss#company.com;'
,#subject = 'TPS Reports'
Scripts
CREATE PROCEDURE [dbo].[table_to_html] (
#tablename sysname,
#html xml OUTPUT,
#order varchar(4) = 'ASC'
) AS
BEGIN
DECLARE
#sql nvarchar(max),
#cols nvarchar(max),
#htmlcols xml,
#htmldata xml,
#object_id int = OBJECT_ID('[tempdb].[dbo].'+QUOTENAME(#tablename));
IF #order <> 'DESC' SET #order = 'ASC';
SELECT #cols = COALESCE(#cols+',','')+QUOTENAME([name])+' '+#order
FROM tempdb.sys.columns
WHERE object_id = #object_id
ORDER BY [column_id];
SET #htmlcols = (
SELECT [name] AS [th]
FROM tempdb.sys.columns
WHERE object_id = #object_id
ORDER BY [column_id] FOR XML PATH(''),ROOT('tr')
);
SELECT #sql = COALESCE(#sql+',','SELECT #htmldata = (SELECT ')+'ISNULL(LTRIM('+QUOTENAME([name])+'),''NULL'') AS [td]'
FROM tempdb.sys.columns
WHERE object_id = #object_id
ORDER BY [column_id];
SET #sql = #sql + ' FROM '+QUOTENAME(#tablename)+' ORDER BY '+#cols+' FOR XML RAW(''tr''), ELEMENTS)';
EXEC sp_executesql #sql, N'#htmldata xml OUTPUT', #htmldata OUTPUT
SET #html = (SELECT #htmlcols,#htmldata FOR XML PATH('table'));
END
GO
CREATE PROCEDURE [dbo].[email_table] (
#tablename sysname,
#recipients nvarchar(max),
#subject nvarchar(max) = '',
#order varchar(4) = 'ASC'
) AS
BEGIN
IF OBJECT_ID('[tempdb].[dbo].'+QUOTENAME(#tablename)) IS NULL RAISERROR('Table does not exist. [dbo].[email_table] only works with temporary tables.',16,1);
DECLARE #style varchar(max) = 'table {border-collapse:collapse;} td,th {white-space:nowrap;border:solid black 1px;padding-left:5px;padding-right:5px;padding-top:1px;padding-bottom:1px;} th {border-bottom-width:2px;}';
DECLARE #table1 xml;
EXEC [dbo].[table_to_html] #tablename, #table1 OUTPUT, #order;
DECLARE #email_body AS nvarchar(max) = (
SELECT
(SELECT
#style AS [style]
FOR XML PATH('head'),TYPE),
(SELECT
#table1
FOR XML PATH('body'),TYPE)
FOR XML PATH('html')
);
EXEC msdb.dbo.sp_send_dbmail
#recipients = #recipients,
#subject = #subject,
#body = #email_body,
#body_format = 'html';
END
GO

Related

Run query for multiple tables and columns

I have the following code, I need to run this code through over 200 tables, can I create a list and a for loop in the variable to go through all the tables and fetch the results for each? Also, how can I include the table name for each row in this query? I also need to go through a list on the column but I can't figure that out from the table list.
DECLARE #table AS VARCHAR(100)
DECLARE #column as varchar(50)
set #table = 'x'
set #column = 'y'
declare #query as varchar(max)
set #query = ' SELECT CAST(MONTH(' + #column + ') AS VARCHAR(2)) + ''-'' + CAST(YEAR(' + #column + ') AS VARCHAR(4)) as date, count(*) as SRC_CNT
FROM ' + #table +
' WHERE ' + #column + ' >= ''2018-01-01'' AND ' + #column + '< ''2021-12-01''
group BY CAST(MONTH(' + #column + ') AS VARCHAR(2)) + ''-'' + CAST(YEAR(' + #column + ') AS VARCHAR(4))
order by date;'
exec(#query)
Rather than loop, I would create a dynamic batch, with all the statements you need, which you can then execute. You can use your best friend to debug the statement(s) if needed:
DECLARE #Column sysname = N'y',
#DateFrom date = '20180101',
#DateTo date = '20211201';
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SQL = STRING_AGG(N'SELECT RIGHT(CONVERT(varchar(10),' + QUOTENAME(#Column) + N',105),7) AS [Date],' + #CRLF +
--N' N' + QUOTENAME(t.[name],'''') + N' AS TableName,' + #CRLF + --Uncomment this line if you need it.
N' COUNT(*) AS SRC_CNT' + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + #CRLF +
N'WHERE ' + QUOTENAME(#Column) + N' >= #DateFrom AND ' + QUOTENAME(#Column) + N' < #DateTo' + #CRLF +
N'GROUP BY RIGHT(CONVERT(varchar(10),' + QUOTENAME(#Column) + N',105),7)' + #CRLF +
N'ORDER BY [date];',#CRLF)
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE EXISTS (SELECT 1
FROM sys.columns c
WHERE c.object_id = t.object_id
AND c.[name] = #Column);
--PRINT #SQL; --Your best friend
EXEC sys.sp_executesql #SQL, N'#DateFrom date, #DateTo date', #DateFrom, #DateTo;
Given tables and sample data:
CREATE TABLE dbo.x(y date, r int);
CREATE TABLE dbo.y(y date, r int);
CREATE TABLE dbo.z(y date, r int);
INSERT dbo.x(y,r) VALUES('20180105',5),
('20180107',6),('20180509',7);
INSERT dbo.y(y,r) VALUES('20180905',5),
('20181007',6),('20181009',7);
INSERT dbo.z(y,r) VALUES('20180605',5),
('20180607',6),('20180609',7);
This dynamic SQL can be generated:
DECLARE #col sysname = N'y',
#StartDate date = '20180101',
#EndDate date = '20211201';
DECLARE #sql nvarchar(max) = N'',
#base nvarchar(max) = N'SELECT [Table Name] = $tblQ$,
[date] = DATEFROMPARTS(YEAR($col$), MONTH($col$), 1),
SRC_CNT = COUNT(*)
FROM dbo.$tbl$ WHERE $col$ >= #s AND y < #e
GROUP BY YEAR($col$), MONTH($col$)';
SELECT #sql = STRING_AGG(REPLACE(REPLACE(REPLACE
(#base, N'$tblQ$', QUOTENAME(t.name, char(39))
),N'$tbl$',QUOTENAME(t.name)), N'$col$', #col), N'
UNION ALL
') + ' ORDER BY [date];'
FROM sys.tables AS t
WHERE EXISTS (SELECT 1 FROM sys.columns
WHERE [object_id] = t.[object_id]
AND name = #col);
PRINT #sql;
EXEC sys.sp_executesql #sql,
N'#s date, #e date',
#StartDate, #EndDate;
Which produces a query like this (only tables that actually have that column name, could be made even safer by making sure they're using a date/time type):
SELECT [Table Name] = 'x',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[x] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y)
UNION ALL
SELECT [Table Name] = 'y',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[y] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y)
UNION ALL
SELECT [Table Name] = 'z',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[z] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y) ORDER BY [date];
That generates output like this:
Table Name
date
SRC_CNT
x
2018-01-01
2
x
2018-05-01
1
z
2018-06-01
3
y
2018-09-01
1
y
2018-10-01
2
Example db<>fiddle
If you really want yyyy-MM instead of yyyy-MM-dd on the output, you can just change this line in the declaration of #base:
[date] = CONVERT(char(7), DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 120),
And I realized the requirement was MM-yyyy, in which case:
[date] = RIGHT(CONVERT(char(10),
DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 105), 7),
I usually do the following for tables:
SELECT TABLE_NAME
FROM [<DATABASE_NAME>].INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
You said you could handle columns from there.

Operand type clash: date is incompatible with int While selecting rows

ALTER PROCEDURE [dbo].[Expense_monthly_report] #start_date VARCHAR(100),
#end_date VARCHAR(100)
AS
BEGIN
DECLARE #SQLQuery AS NVARCHAR(max)
DECLARE #PivotColumns AS NVARCHAR(max)
SET nocount ON;
SELECT #PivotColumns = COALESCE(#PivotColumns + ',', '')
+ Quotename( date_of_expp)
FROM (SELECT DISTINCT Month(date_of_exp) AS date_of_expp
FROM [dbo].[tbl_exp_details]
WHERE date_of_exp >= #start_date
AND date_of_exp <= #end_date) AS PivotExample
SET #SQLQuery = N'SELECT p.part_id,' + #PivotColumns
+ ',total_amount FROM (SELECT part_id,month(date_of_exp) as month_of_exp,exp_amount FROM [dbo].[tbl_exp_details]) as tbl PIVOT( sum(exp_amount) FOR month_of_exp IN ('
+ #PivotColumns
+ ')) AS P join (SELECT part_id,sum(exp_amount) as total_amount FROM [dbo].[tbl_exp_details] where date_of_exp>='
+ #start_date + ' and date_of_exp<=' + #end_date
+ ' group by part_id) as la on p.part_id=la.part_id'
EXEC Sp_executesql #SQLQuery
END
I am getting error
date incompatible with int
. Help me to find out the problem.
You have not delimited the date values in the dynamic query.
So
date_of_exp<=' + #end_date
becomes
date_of_exp<= 2017-07-19
which evaluates to this, and int won't implicitly convert to date
date_of_exp <= 1991
The correct way to use Sp_executesql and fix this is:
SET #SQLQuery = N'SELECT p.part_id,' + #PivotColumns
+ ',total_amount FROM (SELECT part_id,month(date_of_exp) as month_of_exp,exp_amount FROM [dbo].[tbl_exp_details]) as tbl PIVOT( sum(exp_amount) FOR month_of_exp IN ('
+ #PivotColumns
+ ')) AS P join (SELECT part_id,sum(exp_amount) as total_amount FROM [dbo].[tbl_exp_details] where date_of_exp>= #start_date and date_of_exp<=#end_date '
+ ' group by part_id) as la on p.part_id=la.part_id'
EXEC Sp_executesql #SQLQuery,
N'#start_date date, #end_date date',
#start_date,
#end_date

Comma seperated values to SQL IN STATEMENT

If i have a comma separated list of values:
A1,B2,B3
How do i pass this into a variable and then form it into an SQL IN statement.
DECLARE #DATE AS VARCHAR(50)
SELECT #DATE = CONVERT(VARCHAR(8), Getdate(), 112)
--PRINT #DATE
DECLARE #TIME AS VARCHAR(50)
--PRINT TIME
SELECT #TIME = Replace(CONVERT(VARCHAR(5), Getdate(), 108), ':', '')
DECLARE #ID AS VARCHAR(50)
SELECT #ID = Replace(W0128001, 32322, 32323, 3232323, 2323232, ',', ',')
--PRINT #ID
DECLARE #QUERY NVARCHAR(MAX);
SET #QUERY = 'SELECT * INTO BACKUPTABLE_' + #DATE + #TIME
+ '
FROM TABLE
WHERE ID IN (' + '''' + #ID + ''')'
--EXEC #query
PRINT #QUERY
I have tried to do a replace above but i want it so that an end user can PASTE into the values and my script will take care of the commas and properly form it. It should also strip out the last commas from the end.
My output needs to read:
SELECT * INTO BACKUPTABLE_201606061503
FROM TABLE
WHERE ID IN ('W0128001','32322','32323','3232323','2323232')
For one thing, you don't surround it with single quotes:
SET #QUERY = 'SELECT * INTO BACKUPTABLE_' + #DATE + #TIME + '
FROM TABLE
WHERE ID IN (' + #ID + ')';
There are other ways to pass comma-delimited values to a SQL statement, including using a split() function or XML.
CREATE PROCEDURE [dbo].[CreateBackupTable]
#ID varchar(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #DATE VARCHAR(50)= CONVERT(VARCHAR(8), Getdate(), 112);
DECLARE #TIME VARCHAR(50) = Replace(CONVERT(VARCHAR(5), Getdate(), 108), ':', '')
declare #xml xml,#SQL NVARCHAR(MAX);
set #xml = N'<root><r>' + replace(#ID,',','</r><r>') + '</r></root>'
SET #SQL = N' SELECT * INTO ' + QUOTENAME('BACKUPTABLE_' + #DATE + #TIME)
+ N' from TableName '
+ N' where ID IN (
select r.value(''.'',''varchar(max)'') as item
from #xml.nodes(''//root/r'') as records(r)
)'
exec sp_executesql #sql
, N'#ID varchar(100), #xml XML'
, #ID
, #Xml
END

Employee Monthly Attendance Using SQL Server

I have two tables, one is Tab_Users containing user details and the other is Tab_CardEntry containing their card entries. I need to get their minimum and maximum card entry time for each user on a daily basis.
Here is the SQL query I used
SELECT
A.Name,
ISNULL(CONVERT(VARCHAR, MIN(B.DATED), 108), 'OFF') LOGEDIN,
ISNULL(CONVERT(VARCHAR, MAX(B.DATED), 108), 'OFF') LOGEDOUT
FROM TAB_USERS A
LEFT OUTER JOIN (
SELECT
USERNAME,
DATED
FROM TAB_CARDENTRY
WHERE CONVERT(VARCHAR, DATED, 106) = CONVERT(VARCHAR, '02 Jan 2014', 106)
) B ON A.USERNAME = B.USERNAME
GROUP BY A.Name
ORDER BY 1
I need this type of output
Here is a Stored Proc that you can try
CREATE PROCEDURE usp_GetTimes #StartDate DATETIME, #EndDate DATETIME = NULL
AS
BEGIN
--Make sure that only 31 days of data is used otherwise the temp table will be too big
IF #EndDate IS NULL OR DATEDIFF(dd, #StartDate, #EndDate) > 31
SET #EndDate = DATEADD(dd, 31, #StartDate)
SELECT UserID, CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, DateTimeIn))) ForDate, DateTimeIn, DateTimeOut
INTO #Times
FROM
(
SELECT DISTINCT MIN(a.DATED) OVER (PARTITION BY a.USERNAME, CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, DATED)))) DateTimeIn,
MAX(a.DATED) OVER (PARTITION BY a.USERNAME, CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, DATED)))) DateTimeOut,
USERNAME AS UserID
FROM TAB_CARDENTRY a
WHERE DATED BETWEEN #StartDate and #EndDate + 1
) a
SELECT DISTINCT ForDate INTO #Dates FROM #Times
--Select * from #Times order by 1
--Select * from #Dates
DECLARE #SQLstring VARCHAR(MAX)
SET #SQLstring = 'CREATE TABLE ##Test (UserID varchar(10), '
DECLARE #ForDate DATETIME
WHILE 0 < (SELECT COUNT(*) FROM #Dates)
BEGIN
SET ROWCOUNT 1
SELECT #ForDate = ForDate FROM #Dates order by ForDate
SET ROWCOUNT 0
SET #SQLstring = #SQLstring + '[Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] datetime, [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] datetime,' + CHAR(13) + CHAR(10)
DELETE #Dates WHERE ForDate = #ForDate
END
SET #SQLstring = SUBSTRING(#SQLSTRING, 1, LEN(#SQLSTRING) - 3) + ')'
EXEC (#SQLstring)
CREATE INDEX idx1 on ##Test(UserID)
DECLARE #UserID VARCHAR(10),
#DateIn DATETIME,
#DateOut DATETIME
WHILE 0 < (SELECT COUNT(*) FROM #Times)
BEGIN
SET ROWCOUNT 1
SELECT #UserID = UserID, #DateIn = DateTimeIn, #DateOut = DateTimeOut, #ForDate = ForDate from #Times
SET ROWCOUNT 0
SET #SQLString = 'IF NOT EXISTS(SELECT 1 FROM ##Test WHERE UserID = ''' + #UserID + ''')' + CHAR(13) + CHAR(10) +
' INSERT ##Test (UserID, [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '], [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + ']) ' +
' SELECT ''' + #UserID + ''', ''' + CONVERT(VARCHAR(20), #DateIn, 113) + ''', ''' + CONVERT(VARCHAR(20), #DateOut, 113) + '''' + CHAR(13) + CHAR(10) +
'ELSE' + CHAR(13) + CHAR(10) +
' UPDATE ##Test SET [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), #DateIn, 113) + ''', ' + CHAR(13) + CHAR(10) +
' [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), #DateOut, 113) + ''' WHERE UserID = ''' + #UserID + ''''
EXEC (#SQLstring)
DELETE #Times WHERE UserID = #UserID AND ForDate = #ForDate
END
SELECT * FROM ##Test ORDER BY 1
DROP TABLE ##Test
END
EDIT
I've looked at the query again and you might not like the output, so you can change a part of the SP to look like this:
The part that creates the temp table
WHILE 0 < (SELECT COUNT(*) FROM #Dates)
BEGIN
SET ROWCOUNT 1
SELECT #ForDate = ForDate FROM #Dates order by ForDate
SET ROWCOUNT 0
SET #SQLstring = #SQLstring + '[Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] varchar(20), [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] varchar(20),' + CHAR(13) + CHAR(10)
DELETE #Dates WHERE ForDate = #ForDate
END
And the part that inserts the data into the table
WHILE 0 < (SELECT COUNT(*) FROM #Times)
BEGIN
SET ROWCOUNT 1
SELECT #UserID = UserID, #DateIn = DateTimeIn, #DateOut = DateTimeOut, #ForDate = ForDate from #Times
SET ROWCOUNT 0
SET #SQLString = 'IF NOT EXISTS(SELECT 1 FROM ##Test WHERE UserID = ''' + #UserID + ''')' + CHAR(13) + CHAR(10) +
' INSERT ##Test (UserID, [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '], [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + ']) ' +
' SELECT ''' + #UserID + ''', ''' + CONVERT(VARCHAR(20), ISNULL(#DateIn, '''OFF''', 108) + ''', ''' + CONVERT(VARCHAR(20), ISNULL(#DateOut, '''OFF'''), 108) + '''' + CHAR(13) + CHAR(10) +
'ELSE' + CHAR(13) + CHAR(10) +
' UPDATE ##Test SET [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), ISNULL(#DateIn, '''OFF'''), 108) + ''', ' + CHAR(13) + CHAR(10) +
' [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), ISNULL(#DateOut, '''OFF''', 108) + ''' WHERE UserID = ''' + #UserID + ''''
EXEC (#SQLstring)
DELETE #Times WHERE UserID = #UserID AND ForDate = #ForDate
END

Conversion failed when converting date and/or time from character string

I am struggling with this query which returns the error: Conversion failed when converting date and/or time from character string.
This is a common error judging from my google searches, but nothing I've tried so far works. I've tried casting #startdate as datetime and varchar and leaving it alone, as in the below example.
I've also tried using convert against the fieldname and the parameter name, although admittedly, I may just be getting the syntax wrong.
ALTER PROCEDURE [dbo].[customFormReport]
(
#formid int,
#startdate DATETIME
)
AS
BEGIN
SET NOCOUNT ON
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(fieldname) from FormResponse WHERE FormID = #formid AND value IS NOT NULL FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT FormID, FormSubmissionID,' + #cols + ' from
(
SELECT FormID, FormSubmissionID, fieldname, value
FROM FormResponse WHERE FormID = ' + CAST(#formid AS VARCHAR(25)) + ' AND createDate > ' + #startdate + '
) x
pivot
(
max(value)
for fieldname in (' + #cols + ')
) p '
execute(#query)
edit: the query works except when I add the bit causing the error:
' AND createDate > ' + #startdate + '
The problem is you are attempting to concatenate a datetime to your varchar sql string. You need to convert it:
convert(varchar(10), #startdate, 120)
So the full code will be:
ALTER PROCEDURE [dbo].[customFormReport]
(
#formid int,
#startdate DATETIME
)
AS
BEGIN
SET NOCOUNT ON
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(fieldname) from FormResponse WHERE FormID = #formid AND value IS NOT NULL FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT FormID, FormSubmissionID,' + #cols + ' from
(
SELECT FormID, FormSubmissionID, fieldname, value
FROM FormResponse
WHERE FormID = ' + CAST(#formid AS VARCHAR(25)) + '
AND createDate > ''' + convert(varchar(10), #startdate, 120) + '''
) x
pivot
(
max(value)
for fieldname in (' + #cols + ')
) p '
execute(#query)
When you dynamically build the SQL Statement, the date value needs to be wrapped in single quotes. Whenever building a dynamic statement, do a SELECT #query and make sure the results look correct.
For your example, you would need to have 'WHERE createdate > ''' + covert(varchar(10), #startdate, 111) + '''
That would output: WHERE createdate > '2013/05/29'
Without the single quotes you would have: WHERE createdate > 2013/05/29