I have the following table:
CREATE TABLE [dbo].[MyTable](
[Day_ID] [nvarchar](50) NULL,
[SAS] [nvarchar](50) NULL,
[STAMP] [datetime] NULL DEFAULT (getdate())
)
which contains this data:
'2017_12_06_01','Red'
'2017_12_06_02','Yellow'
'2017_12_06_03','Green'
'2017_12_06_04','Blue'
I also have a SP which read all the data from MyTable and send it in the body of an e-mail message.
SET NOCOUNT ON;
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'MyMailProfile'
,#recipients = 'me#account.com'
,#subject = 'This is the object'
,#body = 'This is the body:'
,#body_format = 'TEXT'
,#query = 'SET NOCOUNT ON select [Day_ID],[SAS] from MyTable SET NOCOUNT OFF'
,#attach_query_result_as_file = 0
,#query_result_separator = '|'
,#exclude_query_output = 1
,#query_result_no_padding = 0
,#query_result_header = 0
,#append_query_error=1
Everything works but my problem is that in the body the results appear like these:
2017_12_06_01 |Red
2017_12_06_02 |Yellow
2017_12_06_03 |Green
2017_12_06_04 |Blue
In other words SQL Server know that the columns could be 50 characters long so they fill the space that doesn't contain characters with spaces. This is very boring if you consider that happen also if you write numeric values into a column, for example, NUMERIC.
I've tried with LTRIM and RTRIM but nothing changed.
I am using SQL Server 2005.
The parameter #query_result_no_padding should allow you to do this.
Change it from:
#query_result_no_padding = 0
to
#query_result_no_padding = 1
[ #query_result_no_padding ] #query_result_no_padding The type is bit.
The default is 0. When you set to 1, the query results are not padded,
possibly reducing the file size.If you set #query_result_no_padding to
1 and you set the #query_result_width parameter, the
#query_result_no_padding parameter overwrites the #query_result_width
parameter. + In this case no error occurs. If you set the
#query_result_no_padding to 1 and you set the #query_no_truncate
parameter, an error is raised.
Source: https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-send-dbmail-transact-sql
You need to run two queries - first to compute the actual lengths, the second to retrieve the data for the email. Obviously, this can have a high cost.
Something like:
declare #maxDay int
declare #maxSAS int
select #maxDay = MAX(LEN(RTRIM(Day_ID))),#maxSAS = MAX(LEN(RTRIM(SAS))) from MyTable
declare #sql varchar(max)
set #sql = 'SET NOCOUNT ON select CONVERT(nvarchar(' + CONVERT(varchar(10),#maxDay) +
'),[Day_ID]) as Day_ID,CONVERT(nvarchar(' + CONVERT(varchar(10),#maxSAS) +
'),[SAS]) as SAS from MyTable SET NOCOUNT OFF'
And then use #sql in you sp_send_dbmail call.
I've tried with LTRIM and RTRIM but nothing changed.
It wouldn't. Each column in a result set has a single fixed type. The input to your e.g. RTRIM() calls is an nvarchar(50). Without knowing the contents of all rows, what possible data type can SQL Server derive for the output from that RTRIM() using expression? It can only still be nvarchar(50) and so that's still the type of the column in the result set. For all the server nows, there could be a row containing 50 non-whitespace characters and the result still needs to show that.
Related
In table users I have a column username of datatype varchar(50). The table has no records. I insert a new record with A for the username. The following returns what I would expect:
SELECT username, LEN(username)
FROM users
WHERE id = 1 -- returns: A, 1
So far so good.
Now I update table users from a trigger on another table, using the value from CONTEXT_INFO():
set #context = cast('B' as varbinary(128))
set CONTEXT_INFO #context
update some_other_table
set x = 'y'
where id = 97
In the trigger for some_other_table I do:
DECLARE #context VARCHAR(128)
SELECT
#context = CAST(CONTEXT_INFO() AS VARCHAR(128))
FROM
master.dbo.SYSPROCESSES
WHERE
SPID = ##SPID
DECLARE #user VARCHAR(50) = LEFT(#context, 50)
UPDATE users
SET username = LTRIM(RTRIM(#user))
WHERE id = 1
The username is correctly set to "B", but the following now returns 50:
SELECT
username, LEN(username)
FROM
users
WHERE
id = 1 -- returns: B, 50
The solution, when populating the context, is to do:
set #context = cast('B' + replicate(' ', 126) as varbinary(128))
But why do I need to do this?
When I don't pad the CONTEXT_INFO with spaces what is happening that updating using its value will cause the resulting length to be 50 (even if I ltrim and rtrim the single character value before updating)?
And why must I pad my CONTEXT_INFO to 127 bytes total, not 128? For every character over 127, 1 character is truncated from the value originally set on CONTEXT_INFO
Note: ANSI_PADDING is enabled
In your code:
declare #user varchar(50) = left(#context, 50)
UPDATE users set username = ltrim(rtrim(#user)) WHERE id = 1
you defined #user to be 50 characters. Since the CONTEXT_INFO itself is 128 bytes, the contents of #user will be the letter B padded by 49 null CHAR(0) characters. LTRIM() and RTRIM() will not remove null characters, which are not whitespace, so they have no effect on #user.
If you want to remove the NULL character you can try this (assuming you are using SQL Server 2005 or later):
UPDATE users SET username = REPLACE(#user, char(0), '')
I know this question is old, but there are two ways of retreiving CONTEXT_INFO: either directly with CONTEXT_INFO(), or from sys.dm_exec_sessions. The latter is not padded with '0' characters.
declare #c varbinary(128)
select #c = convert(varbinary(128), 'Test context')
set CONTEXT_INFO #c
go
-- option 1: padded with '0'
declare #value varchar(128)
select #value = convert(varchar(128), CONTEXT_INFO())
select #value context, len(#value) length
go
-- option 2: NOT padded with '0'
declare #value varchar(128)
select #value = convert(varchar(128), context_info)
from sys.dm_exec_sessions
where session_id = ##SPID
select #value context, len(#value) length
go
Query result.
context length
--------------- -----------
Test context 128
(1 row affected)
context length
--------------- -----------
Test context 12
(1 row affected)
Alternatively, as of SQL Server 2016 you can also use SESSION_CONTEXT(), which allows you to specify key-value pairs, instead of a single binary blob.
I have a field that stores value like >=99.35 (storing goals in table) and lets say the actual data value is 78. I then need to compare if goal met or not. How can I accomplish this?
I tried to put that in a #sql variable which will say like:
Select case when 78>=99.35 then 1 else 0 end
but how can I execute this #sql to get the value 1 or 0 in a field of a table?
DECLARE #sql VARCHAR(1000);
DECLARE #Result INT;
SET #Result = 78;
SET #sql = 'SELECT CASE WHEN ' + #Result + ' >=99.35 THEN 1 ELSE 0 END'
EXEC sp_executesql #sql
Using sp_executesql is more likely to cache the query plan if you are going to be repeatedly calling this SQL statement.
DECLARE #ActualValue INT = 100
DECLARE #SQLQuery nVARCHAR(MAX) = ''
SET #SQLQuery = ( SELECT 'SELECT CASE WHEN '+CONVERT(VARCHAR,#ActualValue)+ YourColumn+ 'THEN 1 ELSE 0 END AS ResultValue' FROM YourTable )
EXECUTE sp_executesql #SQLQuery
A solution using sp_executesql as described in the other answers will work fine, but if you're going to be executing the contents of a table as part of a dynamic SQL statement, then you need to be very careful about what can be stored in that field. Do the goals you're storing always consist of a single operator and a target value? If so, it wouldn't be hard to store the two separately and process them with a static query. Something like:
declare #SampleData table
(
[ActualValue] decimal(11, 2),
[Operator] varchar(2),
[ReferenceValue] decimal(11, 2)
);
insert #SampleData values
(100, '>=', 98.25),
(100, '<=', 98.25),
(100, 'G', 98.25);
select
[ActualValue],
[Operator],
[ReferenceValue],
[GoalMet] = case [Operator]
when '>=' then case when [ActualValue] >= [ReferenceValue] then 1 else 0 end
when '<=' then case when [ActualValue] <= [ReferenceValue] then 1 else 0 end
/*...other operators here if needed...*/
else null
end
from
#SampleData;
This is a bit more verbose but perhaps a bit safer as well. Maybe it's usable for your general case and maybe it's not; I just thought I'd throw it out there as an alternative.
DECLARE
#countFlag INT,
#maxNum INT = 5,
#addingName NVARCHAR(6)
SET #countFlag = 1
WHILE (#countFlag <= #maxNum)
BEGIN
SET #addingName = 'name' + CAST(#countFlag AS NVARCHAR(2))
ALTER TABLE TableName
ADD
#addingName NVARCHAR(30)
SET #countFlag = #countFlag + 1
END
========================================================
This is called at the beginning of a set of procedures. #maxNum is actually passed in based on a question to the operator and changes the 'shape' of an existing db to include more columns. I would like to have the resulting column names be something like "name1" "name2" etc. but I am getting an "Incorrect syntax near '#addingName'" after the ADD statement when I execute it. What am I doing wrong here?
You cannot do it in that way, you should compose the query dynamically and execute it with Exec:
DECLARE #sqlCommand varchar(200)
SET #sqlCommand = 'ALTER TABLE TableName ADD ' + #addingName + ' NVARCHAR(30) '
EXEC (#sqlCommand)
I use the #query perimeter in sp_send_dbmail to send out an e-mail with a list of warnings (the warnings are returned by the #query perimeter). The #query perimeter is listed as text in the e-mail. All of the records returned from the #query perimeter and displayed in the e-mail has a line break between them. ie. If I have 4 records, I would have 8 lines because of line breaks.
How do I turn the line breaks off? I read the the msdn article for sp_send_dbmail, but it didn't mention any attributes that could be changed that would affect the line breaks.
Code:
BEGIN
EXEC MSDB.DBO.sp_send_dbmail
#PROFILE_NAME = 'Alerts',
#RECIPIENTS = #MAIL,
#SUBJECT = #NEWSUBJECT,
#BODY = #NEWBODY,
#QUERY =
'SET NOCOUNT ON
DECLARE #HEXSTRING AS VARCHAR(100)
SET #HEXSTRING = (SELECT HEXADECIMAL_STRING FROM mydb.dbo.statusupdates
WHERE MACHINE_ID = ''1111'' AND DATEDIFF(MI, TIME_DATE_RECEIVED, GETDATE()) <= 60)
SELECT [Warning_Description] FROM mydb.DBO.BINARYTOTABLE(mydb.DBO.HEXTOBINARY(#HEXSTRING)) AS ABB1
JOIN mydb.DBO.WarningMessages ON mydb.DBO.WarningMessages.[Bit_Offset] = ABB1.BITPLACE
WHERE BITVALUE = 1 AND ALERT_LEVEL = ''WARNING''',
#QUERY_RESULT_HEADER = 0,
#ATTACH_QUERY_RESULT_AS_FILE = 0;
END
Old post, but probably worth checking #query_result_width parameter of sp_send_dbmail
See http://technet.microsoft.com/en-us/library/ms190307(v=sql.110).aspx
If the data width in a column exceeds #query_result_width (default of 256) a new line will be added.
Steve
I have always used CHAR(13)+CHAR(10) to create line breaks in tsql. You will have to replace it by ' '.
I ended up putting everything into the #BODY parameter of SP_SEND_DBMAIL. Query results from a SELECT statement could be concatenated with regular text into a string and you can use HTML to format the resulting text.
try this and see what you get ...
#query = REPLACE(#query,CHR(13),'')
What I need is to search for a string in a specific column (datatype: text) of a table and replace it with another text.
For example
Id | Text
-----------------------------
1 this is test
2 that is testosterone
If I chose to replace test with quiz, results should be
this is quiz
that is quizosterone
What I've tried so far?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[SearchAndReplace]
(
#FindString NVARCHAR(100)
,#ReplaceString NVARCHAR(100)
)
AS
BEGIN
SET NOCOUNT ON
SELECT CONTENT_ID as id, CONTENT_TEXT, textptr(CONTENT_TEXT) as ptr, datalength(CONTENT_TEXT) as lng
INTO #newtable6 FROM HTML_CONTENTS
DECLARE #COUNTER INT = 0
DECLARE #TextPointer VARBINARY(16)
DECLARE #DeleteLength INT
DECLARE #OffSet INT
SELECT #TextPointer = TEXTPTR(CONTENT_TEXT)
FROM #newtable6
SET #DeleteLength = LEN(#FindString)
SET #OffSet = 0
SET #FindString = '%' + #FindString + '%'
WHILE (SELECT COUNT(*)
FROM #newtable6
WHERE PATINDEX(#FindString, CONTENT_TEXT) <> 0) > 0
BEGIN
SELECT #OffSet = PATINDEX(#FindString, CONTENT_TEXT) - 1
FROM #newtable6
WHERE PATINDEX(#FindString, CONTENT_TEXT) <> 0
UPDATETEXT #newtable6.CONTENT_TEXT
#TextPointer
#OffSet
#DeleteLength
#ReplaceString
SET #COUNTER = #COUNTER + 1
END
select #COUNTER,* from #newtable6
drop table #newtable6
SET NOCOUNT OFF
I get the error:
Msg 7116, Level 16, State 4, Procedure SearchAndReplace, Line 31
Offset 1900 is not in the range of available LOB data.
The statement has been terminated.
Thank you
If you can't change your column types permanently, you can cast them on the fly:
ALTER PROC [dbo].[SearchAndReplace]
(#FindString VARCHAR(100),
#ReplaceString VARCHAR(100) )
AS
BEGIN
UPDATE dbo.HTML_CONTENTS
SET CONTENT_TEXT = cast (REPLACE(cast (CONTEXT_TEXT as varchar(max)), #FindString, #ReplaceString) as TEXT)
END
The datatype TEXT is deprecated and should not be used anymore - exactly because it's clunky and doesn't support all the usual string manipulation methods.
From the MSDN docs on text, ntext, image:
ntext, text, and image data types will
be removed in a future version of
MicrosoftSQL Server. Avoid using these
data types in new development work,
and plan to modify applications that
currently use them. Use nvarchar(max),
varchar(max), and varbinary(max)
instead.
My recommendation: convert that column to VARCHAR(MAX) and you should be fine after that!
ALTER TABLE dbo.HTML_CONTENTS
ALTER COLUMN CONTEXT_TEXT VARCHAR(MAX)
That should do it.
When your column is VARCHAR(MAX), then your stored procedures becomes totally simple:
ALTER PROC [dbo].[SearchAndReplace]
(#FindString VARCHAR(100),
#ReplaceString VARCHAR(100) )
AS
BEGIN
UPDATE dbo.HTML_CONTENTS
SET CONTENT_TEXT = REPLACE(CONTEXT_TEXT, #FindString, #ReplaceString)
END
Two observations on the side:
it would be helpful to have a WHERE clause in your stored proc, in order not to update the whole table (unless that's what you really need to do)
you're using TEXT in your table, yet your stored procedure parameters are of type NVARCHAR - try to stick to one set - either TEXT/VARCHAR(MAX) and regular VARCHAR(100) parameters, or then use all Unicode strings: NTEXT/NVARCHAR(MAX) and NVARCHAR(100). Constantly mixing those non-Unicode and Unicode strings is a mess and causes lots of conversions and unnecessary overhead