I am using an SQL script to build the body of an email with values from a database to pass to a stored procedure, however I am getting an 'incorrect syntax near '+' error on the following line:
#body = 'Item number ' + #ref + 'is due calibration on ' + convert(nvarchar(20), #next) + '. <br><br>Please take the necessary action to ensure the item is calibrated before this time. <br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + #ref
ref is an nvarchar(50)
next is a date
body is an nvarchar(max)
Can anyone explain where i'm going wrong? Thanks!
EDIT - Full query and procedure
Sorry for the pastebin links, couldn't get the indentation right in the editor on here.
Query is HERE
Procedure is HERE
I would suggest you declare a variable, put the contents in the variable and then set the #Body param with that variable:
declare #MailBody nvarchar(max)
declare
#out_desc nvarchar(max),
#out_mesg nvarchar(50)
declare #ref nvarchar(50),
#next date
declare c1 cursor read_only
for
select [calibration_no], [description], [next_calibration]
from calibration.dbo.item
open c1
fetch next from c1 into
#ref, #next
while ##fetch_status = 0
begin
if datepart(month,#next) <= dateadd(month, 1, (datepart(month, getdate())))
begin
SET #MailBody = 'Item number ' + STR(#ref) + 'is due calibration on ' + STR(convert(nvarchar(20), #next))
+ '. <br><br>Please take the necessary action to ensure the item is calibrated before this time.
<br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + STR(#ref)
exec calibration.dbo.spSendMail #recipients = 'xxxxxxxx',
#subject = 'Test Email',
#from = 'xxxxxxxx',
#body = #MailBody
#output_mesg = #out_mesg output,
#output_desc = #out_desc output
print #out_mesg
print #out_desc
end
fetch next from c1 into
#ref, #next
end
close c1
deallocate c1
For what it is worth, the following worked fine for me in SQL Server:
declare #body nvarchar(max)
declare #ref nvarchar(50)
declare #next date
select #ref = 'test'
select #next = getdate()
select #body = 'Item number ' + #ref + 'is due calibration on ' + convert(nvarchar(20), #next) + '. <br><br>Please take the necessary action to ensure the item is calibrated before this time. <br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + #ref
select #body
To be on the safe side, I always wrap the variable with a STR()
This manages to avoid the incorrect syntax error
So your code would read...
#body = 'Item number ' + STR(#ref) + 'is due calibration on ' + STR(convert(nvarchar(20), #next)) + '. <br><br>Please take the necessary action to ensure the item is calibrated before this time. <br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + STR(#ref)
Related
This doesn't seem to have been answered anywhere (although very similar cases have been answered)...
I have an issue where I am trying to update a column's value in a table within a stored procedure. However, I pass more than one table to this stored procedure and some tables have a certain column and others don't. Thus I need to check if the column exists before I run this update. Now, because it's in a stored procedure, SQL seems to be parsing the entire chunk of code up front and complains that this column doesn't exist.
Code:
IF COL_LENGTH(''DBName' + #date+ '..' + #TableName + #date+''', ''ColumnName' + #specifictocolumn + 'restofcolumnname'') IS NOT NULL
update DBName' + #date+ '..' + #TableName + #date+ ' set ColumnName' + #specifictocolumn + 'restofcolumnname = 0
Alternatively
IF EXISTS(SELECT 1 FROM sys.columns WHERE Name = N''ColumnName' + #specifictocolumn + 'restofcolumnname '' AND Object_ID = Object_ID(N''DBName' + #date+ '..' + #TableName + #date+'''))
update DBName' + #date+ '..' + #TableName + #date+ ' set ColumnName' + #specifictocolumn + 'restofcolumnname = 0
Both of these give the error (column name removed for IP purposes):
Msg 207, Level 16, State 1, Line 6
Invalid column name 'ColumnName'.
There is a question on stack overflow called "Disable TSQL script check" that I looked at, but they suggest that you call the check of the column outside of the dynamic sql and then only execute if it passes the check. This won't work for me because part of the if-statement has variables in it that need to be in dynamic sql.
You can still split the dynamic SQL in 2 parts:
check if the column exists
do the actual update when 1. returns true
you'll probably want to use sp_executesql for this and an OUTPUT parameter.
Something along the lines of this:
DECLARE #sql nvarchar(max),
#result int
SELECT #sql = 'SELECT #col_length = COL_LENGTH(''DBName' + #date + '..' + #TableName + #date + ''', ''ColumnName' + #specifictocolumn + 'restofcolumnname'')'
EXEC sp_executesql #stmt = #sql,
#params = N'#col_length int OUTPUT',
#col_length = #result OUTPUT
IF #result IS NOT NULL
BEGIN
EXEC ('update DBName' + #date+ '..' + #TableName + #date+ ' set ColumnName' + #specifictocolumn + 'restofcolumnname = 0')
END
Or you could go 'dynamic inside dynamic', but will become a mess very quickly.
I need some help on this. I use this code to export SQL queries to html tables (got code from Convert a SQL query result table to an HTML table for email)
-- Description: Turns a query into a formatted HTML table. Useful for emails.
-- Any ORDER BY clause needs to be passed in the separate ORDER BY parameter.
-- =============================================
CREATE PROC [dbo].[spQueryToHtmlTable]
(
#query nvarchar(MAX), --A query to turn into HTML format. It should not include an ORDER BY clause.
#orderBy nvarchar(MAX) = NULL, --An optional ORDER BY clause. It should contain the words 'ORDER BY'.
#html nvarchar(MAX) = NULL OUTPUT --The HTML output of the procedure.
)
AS
BEGIN
SET NOCOUNT ON;
IF #orderBy IS NULL BEGIN
SET #orderBy = ''
END
SET #orderBy = REPLACE(#orderBy, '''', '''''');
DECLARE #realQuery nvarchar(MAX) = '
DECLARE #headerRow nvarchar(MAX);
DECLARE #cols nvarchar(MAX);
SELECT * INTO #dynSql FROM (' + #query + ') sub;
SELECT #cols = COALESCE(#cols + '', '''''''', '', '''') + ''['' + name + ''] AS ''''td''''''
FROM tempdb.sys.columns
WHERE object_id = object_id(''tempdb..#dynSql'')
ORDER BY column_id;
SET #cols = ''SET #html = CAST(( SELECT '' + #cols + '' FROM #dynSql ' + #orderBy + ' FOR XML PATH(''''tr''''), ELEMENTS XSINIL) AS nvarchar(max))''
EXEC sys.sp_executesql #cols, N''#html nvarchar(MAX) OUTPUT'', #html=#html OUTPUT
SELECT #headerRow = COALESCE(#headerRow + '''', '''') + ''<th>'' + name + ''</th>''
FROM tempdb.sys.columns
WHERE object_id = object_id(''tempdb..#dynSql'')
ORDER BY column_id;
SET #headerRow = ''<tr>'' + #headerRow + ''</tr>'';
SET #html = ''<table border="1">'' + #headerRow + #html + ''</table>'';
';
EXEC sys.sp_executesql #realQuery, N'#html nvarchar(MAX) OUTPUT', #html=#html OUTPUT
END
GO
The code works perfect, but has one problem formatting float values.
For example:
SELECT Name, Weight FROM Products
The query returns something like this when executed from Management Studio:
Name1 | 1073,822
Name2 | 179,554
When I use the stored procedure to export this to html table, then I get the results like this:
Name1 | 1.073822000000000e+003
Name2 | 1.795540000000000e+002
Don't know exactly how to change the stored procedure to adapt it in order to avoid this wrong formatting on float values.
Any help on this would be appreciated.
you are using cast to show the values in the html-table:
SET #cols = ''SET #html = CAST(( SELECT '' + #cols + '' FROM #dynSql ' + #orderBy + ' FOR XML PATH(''''tr''''), ELEMENTS XSINIL) AS nvarchar(max))''
MS-Help tells you more, but basically this is the automatic behavior of cast with a float value.
You should try to already convert your float-fields to the desired format in the query you pass, using the STR() function, see str function description
Many thanks for your reply
EDIT: If I take out the casting to varchar(max), the result seems to be the same.
But I've just tried to do something like this:
SELECT Name, CAST(Weight AS varchar(max)) FROM Products
And I still can see the right formatted values:
Name1 | 1073.82
Name2 | 179.554
Otherwise, I use this SP to be called from many sites and processes. Is not possible to check every case and try to cast the float fields.
I need a solution to be implemented at the SP level.
I've tried to use the STR() function, so changed the line:
SET #cols = ''SET #html = CAST(( SELECT '' + #cols + '' FROM #dynSql ' + #orderBy + ' FOR XML PATH(''''tr''''), ELEMENTS XSINIL) AS nvarchar(max))''
To
SET #cols = ''SET #html = STR(( SELECT '' + #cols + '' FROM #dynSql ' + #orderBy + ' FOR XML PATH(''''tr''''), ELEMENTS XSINIL))''
But then I'm getting the error: Error converting data type nvarchar to float.
Not sure if this is what you mentioned.
Would be a way to check the field type in the SP, and use a different casting for the float cases?
How could I do it?
Regards,
Can anyone please tell me what am I doing wrong here? I am trying to queryout to a flat file based on the data I manipulated. I keep getting an error with the code below.
Appreciate all your help!
Can anyone please tell me what am I doing wrong here? I am trying to queryout to a flat file based on the data I manipulated. I keep getting an error with the code below.
Appreciate all your help!
declare variables....
select #effDate = effective_date from c_flt_src
if convert(varchar(10), #effDate, 112) = convert (varchar(10), GETDATE(),112)
set #sqleffdate = convert(varchar(10), #effDate, 112)
begin
select #all = store from c_flt_src where store = 'ALL'
if ##ROWCOUNT = 1 and #all = 'ALL'
select #action = action from c_flt_src
if #action = 'INCREASE' or #action = 'DECREASE'
set #actiontyp = 'UPDATE_STORE'
else if #action = 'REPLACE'
set #actiontyp = 'RESET_STORE'
select #a = a_amt from c_flt_src
select #b = b_amt from c_flt_src
begin
declare #c as cursor
set #c = CURSOR fast_forward for select distinct top 3 store from store_loc where store <> 0 order by store
open #c
fetch next from #c into #store
while ##FETCH_STATUS = 0
begin
--process data here
set #sql = #sqlaction + '|' + #actiontyp + '|' + #store + '|' + #a + '|' + #b
set #sql1 = '"'+'D:\file.'+#sqleffdate+'.9999999.'+REPLICATE ('0', 3-LEN(#store)) + #store +'.deltaloc' + '"'
select #sql1
declare #s varchar(max)
set #s = ''''+'bcp "select ''''' + #sql + '''''"' + ' queryout ' + #sql1+ ' -c -t "|" -T'+''''
select #s
execute master.dbo.xp_cmdshell #s
fetch next from #c into #store
end
close #c
deallocate #c
end
end
Try to replace declaration of #s to varchar(4000) or varchar(8000) not varchar(max)
declare #s varchar(4000)
xp_cmdshell (Transact-SQL)
command_string
Is the string that contains a command to be passed to the operating
system. command_string is varchar(8000) or nvarchar(4000), with no
default.
Is there a way I can convert a SQL Server 2008 Table to HTML table text, without knowing the structure of the table first?
I tried this:
USE [Altiris]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spCustomTable2HTML] (
#TABLENAME NVARCHAR(500),
#OUTPUT NVARCHAR(MAX) OUTPUT,
#TBL_STYLE NVARCHAR(1024) = '',
#HDR_STYLE NVARCHAR(1024) = '')
AS
-- #exec_str stores the dynamic SQL Query
-- #ParmDefinition stores the parameter definition for the dynamic SQL
DECLARE #exec_str NVARCHAR(MAX)
DECLARE #ParmDefinition NVARCHAR(500)
--We need to use Dynamic SQL at this point so we can expand the input table name parameter
SET #exec_str= N'
DECLARE #exec_str NVARCHAR(MAX)
DECLARE #ParmDefinition NVARCHAR(500)
--Make a copy of the original table adding an indexing columnWe need to add an index column to the table to facilitate sorting so we can maintain the
--original table order as we iterate through adding HTML tags to the table fields.
--New column called CustColHTML_ID (unlikely to be used by someone else!)
--
select CustColHTML_ID=0,* INTO #CustomTable2HTML FROM ' + #TABLENAME + '
--Now alter the table to add the auto-incrementing index. This will facilitate row finding
DECLARE #COUNTER INT
SET #COUNTER=0
UPDATE #CustomTable2HTML SET #COUNTER = CustColHTML_ID=#COUNTER+1
-- #HTMLROWS will store all the rows in HTML format
-- #ROW will store each HTML row as fields on each row are iterated through
-- using dymamic SQL and a cursor
-- #FIELDS will store the header row for the HTML Table
DECLARE #HTMLROWS NVARCHAR(MAX) DECLARE #FIELDS NVARCHAR(MAX)
SET #HTMLROWS='''' DECLARE #ROW NVARCHAR(MAX)
-- Create the first HTML row for the table (the table header). Ignore our indexing column!
SET #FIELDS=''<tr ' + #HDR_STYLE + '>''
SELECT #FIELDS=COALESCE(#FIELDS, '' '','''')+''<td>'' + name + ''</td>''
FROM tempdb.sys.Columns
WHERE object_id=object_id(''tempdb..#CustomTable2HTML'')
AND name not like ''CustColHTML_ID''
SET #FIELDS=#FIELDS + ''</tr>''
-- #ColumnName stores the column name as found by the table cursor
-- #maxrows is a count of the rows in the table, and #rownum is for marking the
-- ''current'' row whilst processing
DECLARE #ColumnName NVARCHAR(500)
DECLARE #maxrows INT
DECLARE #rownum INT
--Find row count of our temporary table
SELECT #maxrows=count(*) FROM #CustomTable2HTML
--Create a cursor which will look through all the column names specified in the temporary table
--but exclude the index column we added (CustColHTML_ID)
DECLARE col CURSOR FOR
SELECT name FROM tempdb.sys.Columns
WHERE object_id=object_id(''tempdb..#CustomTable2HTML'')
AND name not like ''CustColHTML_ID''
ORDER BY column_id ASC
--For each row, generate dymanic SQL which requests the each column name in turn by
--iterating through a cursor
SET #rowNum=0
SET #ParmDefinition=N''#ROWOUT NVARCHAR(MAX) OUTPUT,#rowNum_IN INT''
While #rowNum < #maxrows
BEGIN
SET #HTMLROWS=#HTMLROWS + ''<tr>''
SET #rowNum=#rowNum +1
OPEN col
FETCH NEXT FROM col INTO #ColumnName
WHILE ##FETCH_STATUS=0
BEGIN
--Get nth row from table
--SET #exec_str=''SELECT #ROWOUT=(select top 1 ['' + #ColumnName + ''] from (select top '' + cast(#rownum as varchar) + '' * from #CustomTable2HTML order by CustColHTML_ID ASC) xxx order by CustColHTML_ID DESC)''
SET #exec_str=''SELECT #ROWOUT=(select ['' + #ColumnName + ''] from #CustomTable2HTML where CustColHTML_ID=#rowNum_IN)''
EXEC sp_executesql
#exec_str,
#ParmDefinition,
#ROWOUT=#ROW OUTPUT,
#rowNum_IN=#rownum
SET #HTMLROWS =#HTMLROWS + ''<td>'' + #ROW + ''</td>''
FETCH NEXT FROM col INTO #ColumnName
END
CLOSE col
SET #HTMLROWS=#HTMLROWS + ''</tr>''
END
SET #OUTPUT=''''
IF #maxrows>0
SET #OUTPUT= ''<table ' + #TBL_STYLE + '>'' + #FIELDS + #HTMLROWS + ''</table>''
DEALLOCATE col
'
DECLARE #ParamDefinition nvarchar(max)
SET #ParamDefinition=N'#OUTPUT NVARCHAR(MAX) OUTPUT'
--Execute Dynamic SQL. HTML table is stored in #OUTPUT which is passed back up (as it's
--a parameter to this SP)
EXEC sp_executesql #exec_str,
#ParamDefinition,
#OUTPUT=#OUTPUT OUTPUT
RETURN 1
but when I execute the procedure
DECLARE #HTML NVARCHAR(MAX)
EXEC SpCustomTable2HTML 'Users', #HTML OUTPUT
SELECT #HTML
it keeps returning null.
Any ideas?
This SQL Fiddle DEMO shows your problem. When ALL the columns in ALL rows have values, you get a proper HTML table. When ANY NULLs exist, it turns the entire thing into NULL because
NULL + <any> = NULL
To fix it, simply change line 90 to handle nulls, i.e.
SET #HTMLROWS =#HTMLROWS + '''' + ISNULL(#ROW,'''') + ''''
The fixed SQL Fiddle DEMO
I realise it's been a while (to say the least) since this question was active but I thought I would post a few comments on this thread, as it turned up in a recent search.
Apologies if this (unintentionally) annoys the question asker but I believe the approach being used is both inefficient and difficult to understand - and therefore maintain.
There's no need to copy the database data before using it to generate the HTML table. It's just my humble opinion, but using dynamic SQL to generate dynamic SQL is also counter-intuitive.
Furthermore, database data values that contain HTML tags (or, worse still, malformed HTML tags) need to be escaped, so that they can be rendered correctly. For example, database data such as "value > 10" needs to generate the HTML "<td>value > 10</td>".
The following code addresses all of the above points, by using the built-in FOR XML clause:
CREATE PROCEDURE dbo.spCustomTable2HTML
#TABLENAME nvarchar(500),
#TBL_STYLE nvarchar(1024) = '',
#HDR_STYLE nvarchar(1024) = '',
#OUTPUT nvarchar(MAX) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
-- Declare variables
DECLARE #Columns nvarchar(MAX) = '',
#Data nvarchar(MAX),
#SQL nvarchar(MAX);
-- Snapshot columns (to force use of tempdb)
IF OBJECT_ID('tempdb.dbo.##spCustomTable2HTMLColumns') IS NOT NULL
BEGIN
DROP TABLE ##spCustomTable2HTMLColumns;
END
SET #SQL =
'SELECT TOP 0 *
INTO ##spCustomTable2HTMLColumns
FROM ' + #TABLENAME;
EXEC sp_executesql #SQL;
-- Build header row
SET #OUTPUT = (SELECT name AS td
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID('tempdb.dbo.##spCustomTable2HTMLColumns')
ORDER BY column_id
FOR XML RAW(''), ELEMENTS);
SET #OUTPUT += '</tr>'
-- Build column list
SELECT #Columns += '[' + name + '] AS td,'
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID('tempdb.dbo.##spCustomTable2HTMLColumns')
ORDER BY column_id;
SET #Columns = LEFT(#Columns, LEN(#Columns) - 1); -- Strip trailing comma
-- Delete columns snapshot
DROP TABLE ##spCustomTable2HTMLColumns;
-- Build data rows
SET #SQL =
'SET #Data = CONVERT(varchar(MAX),
(SELECT ' + #Columns +
' FROM ' + #TABLENAME +
' FOR XML RAW (''tr''), ELEMENTS XSINIL))';
EXEC sp_executesql #SQL, N'#Data NVARCHAR(MAX) OUTPUT', #Data = #Data OUTPUT;
SET #Data = REPLACE(#Data, ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"', ''); -- Remove XSI namespace
SET #Data = REPLACE(#Data, ' xsi:nil="true"', ''); -- Remove XSI attributes
SET #OUTPUT += #Data;
-- Prefix table/row headers
SET #OUTPUT = REPLACE(#OUTPUT, ' ', ' '); -- Use non-breaking spaces
SET #OUTPUT = REPLACE(#OUTPUT, '</tr>', '</tr>' + CHAR(13) + CHAR(10)); -- Add new line per row (to improve rendering in Microsoft Outlook)
SET #OUTPUT = '<table ' + #TBL_STYLE + '>' +
'<tr ' + #HDR_STYLE + '>' +
#OUTPUT +
'</table>';
END
EDIT: Database names have been modified for simplicity
I'm trying to get some dynamic sql in place to update static copies of some key production tables into another database (sql2008r2). The aim here is to allow consistent dissemination of data (from the 'static' database) for a certain period of time as our production databases are updated almost daily.
I am using a CURSOR to loop through a table that contains the objects that are to be copied into the 'static' database.
The prod tables don't change that frequently, but I'd like to make this somewhat "future proof" (if possible!) and extract the columns names from INFORMATION_SCHEMA.COLUMNS for each object (instead of using SELECT * FROM ...)
1) From what I have read in other posts, EXEC() seems limiting, so I believe that I'll need to use EXEC sp_executesql but I'm having a little trouble getting my head around it all.
2) As an added extra, if at all possible, i'd also like to exclude some columns for particular tables (structures vary slightly in the 'static' database)
here's what i have so far.
when executed, #colnames returns NULL and therefore #sql returns NULL...
could someone guide me to where i might find a solution?
any advice or help with this code is much appreciated.
CREATE PROCEDURE sp_UpdateRefTables
#debug bit = 0
AS
declare #proddbname varchar(50),
#schemaname varchar(50),
#objname varchar(150),
#wherecond varchar(150),
#colnames varchar(max),
#sql varchar(max),
#CRLF varchar(2)
set #wherecond = NULL;
set #CRLF = CHAR(10) + CHAR(13);
declare ObjectCursor cursor for
select databasename,schemaname,objectname
from Prod.dbo.ObjectsToUpdate
OPEN ObjectCursor ;
FETCH NEXT FROM ObjectCursor
INTO #proddbname,#schemaname,#objname ;
while ##FETCH_STATUS=0
begin
if #objname = 'TableXx'
set #wherecond = ' AND COLUMN_NAME != ''ExcludeCol1'''
if #objname = 'TableYy'
set #wherecond = ' AND COLUMN_NAME != ''ExcludeCol2'''
--extract column names for current object
select #colnames = coalesce(#colnames + ',', '') + QUOTENAME(column_name)
from Prod.INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = + QUOTENAME(#objname,'') + isnull(#wherecond,'')
if #debug=1 PRINT '#colnames= ' + isnull(#colnames,'null')
--replace all data for #objname
--#proddbname is used as schema name in Static database
SELECT #sql = 'TRUNCATE TABLE ' + #proddbname + '.' + #objname + '; ' + #CRLF
SELECT #sql = #sql + 'INSERT INTO ' + #proddbname + '.' + #objname + ' ' + #CRLF
SELECT #sql = #sql + 'SELECT ' + #colnames + ' FROM ' + #proddbname + '.' + #schemaname + '.' + #objname + '; '
if #debug=1 PRINT '#sql= ' + isnull(#sql,'null')
EXEC sp_executesql #sql
FETCH NEXT FROM ObjectCursor
INTO #proddbname,#schemaname,#objname ;
end
CLOSE ObjectCursor ;
DEALLOCATE ObjectCursor ;
P.S. i have read about sql injection, but as this is an internal admin task, i'm guessing i'm safe here!? any advice on this is also appreciated.
many thanks in advance.
You have a mix of SQL and dynamic SQL in your query against information_schema. Also QUOTENAME isn't necessary in the where clause and will actually prevent a match at all, since SQL Server stores column_name, not [column_name], in the metadata. Finally, I'm going to change it to sys.columns since this is the way we should be deriving metadata in SQL Server. Try:
SELECT #colnames += ',' + name
FROM Prod.sys.columns
WHERE OBJECT_NAME([object_id]) = #objname
AND name <> CASE WHEN #objname = 'TableXx' THEN 'ExcludeCol1' ELSE '' END
AND name <> CASE WHEN #objname = 'TableYy' THEN 'ExcludeCol2' ELSE '' END;
SET #colnames = STUFF(#colnames, 1, 1, '');