sp_send_dbmail attach files stored as varbinary in database - sql

I have a two part question relating to sending query results as attachments using sp_send_dbmail.
Problem 1: Only basic .txt files will open. Any other format like .pdf or .jpg are corrupted.
Problem 2: When attempting to send multiple attachments, I receive one file with all file names glued together.
I'm running SQL Server 2005 and I have a table storing uploaded documents:
CREATE TABLE [dbo].[EmailAttachment](
[EmailAttachmentID] [int] IDENTITY(1,1) NOT NULL,
[MassEmailID] [int] NULL, -- foreign key
[FileData] [varbinary](max) NOT NULL,
[FileName] [varchar](100) NOT NULL,
[MimeType] [varchar](100) NOT NULL
I also have a MassEmail table with standard email stuff. Here is the SQL Send Mail script. For brevity, I've excluded declare statements.
while ( (select count(MassEmailID) from MassEmail where status = 20 )>0)
begin
select #MassEmailID = Min(MassEmailID) from MassEmail where status = 20
select #Subject = [Subject] from MassEmail where MassEmailID = #MassEmailID
select #Body = Body from MassEmail where MassEmailID = #MassEmailID
set #query = 'set nocount on; select cast(FileData as varchar(max)) from Mydatabase.dbo.EmailAttachment where MassEmailID = '+ CAST(#MassEmailID as varchar(100))
select #filename = ''
select #filename = COALESCE(#filename+ ',', '') +FileName from EmailAttachment where MassEmailID = #MassEmailID
exec msdb.dbo.sp_send_dbmail
#profile_name = 'MASS_EMAIL',
#recipients = 'me#myemail.com',
#subject = #Subject,
#body =#Body,
#body_format ='HTML',
#query = #query,
#query_attachment_filename = #filename,
#attach_query_result_as_file = 1,
#query_result_separator = '; ',
#query_no_truncate = 1,
#query_result_header = 0;
update MassEmailset status= 30,SendDate = GetDate() where MassEmailID = #MassEmailID
end
I am able to successfully read files from the database so I know the binary data is not corrupted.
.txt files only read when I cast FilaData to varchar. But clearly original headers are lost. It's also worth noting that attachment file sizes are different than the original files. That is most likely due to improper encoding as well. So I'm hoping there's a way to create file headers using the stored mimetype, or some way to include file headers in the binary data?
I'm also not confident in the values of the last few parameters, and I know coalesce is not quite right, because it prepends the first file name with a comma. But good documentation is nearly impossible to find. Please help!

I don't think your going to be able to send binary data directly from SQL. There are a few posts out there that talk about this same issue. From the Microsoft documentation a text return to the query is formatted as a text file. Binary is formatted as a hexadecimal. Which as you have pointed out corrupts any file that isn't a text document.
I think you could still accomplish what you're trying to do however by first using BCP to export the binary data out to the file system, and then importing it back in via traditional file attachment methods made available to sendmail.
So something like this. (concept only - untested code)
DECLARE #OutputFileAndPath VarChar(500) = '\\Log_Files\MyFile.pdf '
DECLARE #sql VarChar(8000)
SELECT #sql = 'BCP "SELECT MyFile FROM [dbo].[MyTable]
WHERE PrimaryKey = 12345" queryout ' + #OutputFileAndPath +
' -S MyServer\MyInstance -T -fC:\Documents.fmt'
/* you could use a generic format file that would cover most formats */
EXEC xp_cmdshell #sql, NO_OUTPUT;
while ( (select count(MassEmailID) from MassEmail where status = 20 )>0)
begin
select #MassEmailID = Min(MassEmailID) from MassEmail where status = 20
select #Subject = [Subject] from MassEmail where MassEmailID = #MassEmailID
select #Body = Body from MassEmail where MassEmailID = #MassEmailID
exec msdb.dbo.sp_send_dbmail
#profile_name = 'MASS_EMAIL',
#recipients = 'me#myemail.com',
#subject = #Subject,
#body =#Body,
#body_format ='HTML',
#file_attachments = #OutputFileAndPath /* i.e. \\Log_Files\MyFile.pdf */
update MassEmailset status= 30,SendDate = GetDate() where MassEmailID = #MassEmailID
end

The sample below will imbed images into the < img > tag, which will work for jpeg, png, and such. It won't solve PDFs, but it does work with images, at least.
declare #eRecipient nvarchar(max) = 'me#example.com';
declare #eSubject nvarchar(max) = 'Testing!';
declare #FileName nvarchar(2000);
declare #MimeType nvarchar(200);
declare #attachText nvarchar(max);
declare #eBody nvarchar(max) = '<html><head><style>table, th, td {border-collapse: collapse;border: 1px solid black;} img {width: 100%; max-width: 640px;}</style></head>' +
'<body><h1>Data with pics!</h1><table>' +
'<tr><th>Some text</th><th>Some Pics</th></tr>';
declare c1 cursor for
select FileName,
MimeType,
/* MimeType should be something like 'image/jpeg' or 'image/png' */
cast('' as xml).value('xs:base64Binary(sql:column("FileData"))', 'varchar(max)') attachText
/* the above uses XML commands to convert the binary attachment to UUencoded text */
from EmailAttachment
order by 1;
open c1;
fetch next from c1 into #FileName, #MimeType, #attachText
while ##FETCH_STATUS = 0
begin
set #eBody = #eBody + '<tr><td>Filename: ' + #FileName +
'</td><td><img src="data:' + #contentType + ';base64,' +
#AttachText + '="></td></tr>';
/* note that the img tag contents the encoded image data, the mime type, and that it ends in an = sign. */
fetch next from c1 into #FileName, #MimeType, #attachText
end;
close c1;
deallocate c1;
set #eBody = #eBody + '</table></body></html>';
exec msdb.dbo.sp_send_dbmail
#recipients = #eRecipient, #body = #eBody, #subject = #eSubject, #body_format='HTML'

Related

SQL For Json path - auto inserted '\' if parameter content '/'

Declare #ContractCode nvarchar(100) = N'02/HĐLĐ-NGG';
Declare #DateStartWork date = '2020-06-01';
Declare #DeptID int = 2;
Declare #PositionID int = 4;
Declare #HRDecisionID int = 614
Select #ContractCode as ContractCode,
#DateStartWork as DateStartWork,
#DeptID as DeptID,
#HRDecisionID as HRDecisionID
For Json Path
Then the Result =
[{"ContractCode":"02\/HĐLĐ-NGG","DateStartWork":"2020-06-01","DeptID":2,"HRDecisionID":614}]
Instead of right value like:
[{"ContractCode":"02/HĐLĐ-NGG","DateStartWork":"2020-06-01","DeptID":2,"HRDecisionID":614}]
So please help me to answer Why it automatically inserted \ before '/' into ContractCode.

SQL stored procedure call failed with index out of range

I am trying to execute a stored procedure in SQL Server from my java code. This stored procedure is used in some other language and it was working fine from long back. Now I need to integrate it in my java app. I have 15 columns in my table. When I tried this in my java code, its throwing
com.microsoft.sqlserver.jdbc.SQLServerException: The index 11 is out of range
I also see "Error: 0, SQLState: S1093"
My stored procedure
ALTER PROCEDURE [dbo].[user_input_sp]
#Username VARCHAR(10)=NULL,
#UserKey VARCHAR(50)=NULL,
#ReqTime DATETIME=null,
#ResTime DATETIME=null,
#Req TEXT=null,
#Res TEXT=null,
#condition TEXT=null,
#ID INT=null,
#Address VARCHAR(8000) = NULL,
#Task VARCHAR(50) = NULL,
#Direction INT=0,
#Seq INT=0,
#RR BIT=0,
#Correction VARCHAR(8) = NULL,
#PendingTrans VARCHAR(50) = NULL,
#ForwardID VARCHAR(50) = NULL,
#Command VARCHAR(50) = NULL
AS
DECLARE #TSqno int
#IF #Direction=0
BEGIN
EXECUTE Basp_NewKey 'web_log', #TSqno OUTPUT
Insert into cbscne dbo WebServiceLog( Order, US_Name, US_Key , US_ReqD, US_ResD, US_Req , US_Res, cond, ID ,Address, Task, Command)
values (#TSqno, #Username, #UserKey, #ReqTime, #ResTime, #Req ,#Res, #condition, #ID ,#Address, #Task ,#Command)
END
.......
My java code
public vold addWebServiceLogRequest(UserInput cd){
sessionFactory = sqlServerEMFactory.unwrap(SessionFactory.class);
Session sessionForSave = sessionFactory.openSession();
sessionForSave.beginTransaction();
sessionForSave.doReturningWork(connection-> {
try(CallableStatement cstmt = connection.prepareCall("{call user_input_sp(?,?,?,?,?,?,?,?,?)}")) {
cstmt.setInt("indicator", 0);
cstmt.setString("Username",cd.getInterfaceName());
cstmt.setInt("User_code",cd.getCaseId());
cstmt.setTimestamp("RetrieveDate",cd.getRequestDate());
cstmt.setTimestamp("ReturnDate",cd.getResponseDate());
cstmt.setString("Req",cd.getRequestxml());
cstmt.setString("Res",cd.getResponsexml());
cstmt.setString("Task",cd.getTask());
cstmt.setString("flow",null);
cstmt.execute();
cstmt.close();
connection.close();
return null;
}
});
}

sp_executesql bug?

Why I get error when call my stored procedure using sp_executesql?
Doesn't works.
exec sp_executesql N'sp_clnt_regional_experts_territories',
N'#action nvarchar(3), #regional_expert_id int,#region_id int,#territory_id int',
#action = N'SEL',
#regional_expert_id = 2,
#region_id = -1,
#territory_id = -1
Procedure or function 'sp_clnt_regional_experts_territories' expects
parameter '#action', which was not supplied.
Works fine:
EXEC sp_clnt_regional_experts_territories
#action = N'SEL',
#regional_expert_id = 2,
#region_id = -1,
#territory_id = -1
Where the stored proc is:
ALTER PROCEDURE [dbo].[sp_clnt_regional_experts_territories]
#action NVARCHAR(3),
#regional_expert_id INT = -1,
#region_id INT = -1,
#territory_id INT = -1
Your sp_executesql is wrong .You need to format it like the one below
DECLARE #SQLString NVARCHAR(500)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #action nvarchar(3)
DECLARE #regional_expert_id int
DECLARE #region_id int
DECLARE #territory_id INT
Set #SQLString=N'EXEC sp_clnt_regional_experts_territories #action,
#regional_expert_id ,#region_id ,#territory_id'
Set #ParmDefinition='#action NVARCHAR(3),
#regional_expert_id INT ,
#region_id INT ,
#territory_id INT '
EXECUTE sp_executesql
#SQLString,
#ParmDefinition,
#action=N'SEL',
#regional_expert_id=2,
#region_id=-1,
#territory_id=-1
Instead of directly entering everything in one statement ,dissecting your code into different parts helps you find your problems easily .
the error is because you didn't include the parameters right after your stored procedure name, which is required by sp_executesql
exec sp_executesql N'sp_clnt_regional_experts_territories #action,#regional_expert_id ,#region_id ,#territory_id',
N'#action nvarchar(3), #regional_expert_id int,#region_id int,#territory_id int',
#action = N'SEL',
#regional_expert_id = 2,
#region_id = -1,
#territory_id = -1
sp_executesql says of #params:
Is one string that contains the definitions of all parameters that have been embedded in #stmt.
(Emphasis added)
So, any parameters you identify there must also be mentioned in #stmt, as the other answers have pointed out.

Stored procedure invocation with VARBINARY(MAX) parameters fails with strange error message?

I am trying to store encrypted data into my database (run by MS SQL 2005 Express) using stored procedure.
The stored procedure has the following signature:
ALTER PROCEDURE [dbo].[SaveData]
#FileName AS NVARCHAR(255),
#Data As VARBINARY(MAX),
#Key AS VARBINARY(MAX) = NULL,
#Final AS BIT = NULL
AS
....
In my code I have two variables of type byte[] with my data:
public byte[] EncryptedData { get; set; }
public byte[] EncryptedPassword { get; set; }
var cmd = new SqlCommand("SaveData", conn);
cmd.CommandTimeout = 0;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#FileName", FileName);
cmd.Parameters.Add(new SqlParameter("#Data", SqlDbType.VarBinary, EncryptedData.Length, ParameterDirection.Input, false, 0, 0, "", DataRowVersion.Current, (SqlBinary) EncryptedData));
cmd.Parameters.Add(new SqlParameter("#Key", SqlDbType.VarBinary, EncryptedPassword.Length, ParameterDirection.Input, false, 0, 0, "", DataRowVersion.Current, (SqlBinary)EncryptedPassword));
cmd.Parameters.AddWithValue("#Final", true);
Whenever I try to call ExecuteNonQuery() I get the following error message (sorry for German, I just wanted to provide the original text, I also provide translation below!):
Falsche Syntax in der Nähe von '¿'.
Bezeichner (beginnend mit '*è
¯äÅõ42‹áºªž(±Z¦ññú>¬ÕÕ]¥Qî)ÀÍE5 Fäø+ïµ1˜Z}»[–A[Åñ#­šužÿÄ}µ}
%ãVð>TÛ¨¡ªƒ·g·SWFW
Here is the English translation of the error message (done by me, so it might and will deviate from the canonic original error message):
Syntax error near '¿'.
Identifier (beginning with '*è
¯äÅõ42‹áºªž(±Z¦ññú>¬ÕÕ]¥Qî)ÀÍE5 Fäø+ïµ1˜Z}»[–A[Åñ#­šužÿÄ}µ}
%ãVð>TÛ¨¡ªƒ·g·SWFW
It seems as if the SQL string is parsed incorectly and some binary data is parsed as an identifier name.
I tried many different approaches here using .AddWithValue() and setting parameter properties one by one -- this doesn't help.
Any suggesions?
The solution was already pointed out in the comments, so I just answer the question here.
The problem was in using dynamic SQL (the concatenation of SQL strings) in the stored procedure, here is an example:
select #sql = 'insert into dbo.DMSFileData ('
...
select #sql = #sql + ') VALUES ('
select #sql = #sql + '''' + #FileName + ''''
select #sql = #sql + '''' + convert(varchar(max), #Data) + ''''
The correct solution here is to use parameters in concatenation:
select #sql = #sql + ', #filename'
select #sql = #sql + ', #filedata'
And then execute SQL using sp_executesql:
exec sp_executesql #sql, #params, #ForeignKey, #FileName, #Data

entity framework 4 POCO's stored procedure error - "The FunctionImport could not be found in the container"

Entity Framework with POCO Entities generated by T4 template. Added Function Import named it "procFindNumber" specified complex collection named it "NumberResult".
Here's what got generated in Context.cs file:
public ObjectResult<NumberResult> procFindNumber(string lookupvalue)
{
ObjectParameter lookupvalueParameter;
if (lookupvalue != null)
{
lookupvalueParameter = new ObjectParameter("lookupvalue", lookupvalue);
}
else
{
lookupvalueParameter = new ObjectParameter("lookupvalue", typeof(string));
}
return base.ExecuteFunction<NumberResult>("procFindNumber", lookupvalueParameter);
}
Here's the stored procedure:
ALTER PROCEDURE [dbo].[procFindNumber]
#lookupvalue varchar(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql nvarchar(MAX);
IF #lookupvalue IS NOT NULL AND #lookupvalue <> ''
BEGIN
SELECT #sql = 'SELECT dbo.HBM_CLIENT.CLIENT_CODE, dbo.HBM_MATTER.MATTER_NAME, dbo.HBM_MATTER.CLIENT_MAT_NAME
FROM dbo.HBM_MATTER INNER JOIN dbo.HBM_CLIENT ON dbo.HBM_MATTER.CLIENT_CODE = dbo.HBM_CLIENT.CLIENT_CODE
LEFT OUTER JOIN dbo.HBL_CLNT_CAT ON dbo.HBM_CLIENT.CLNT_CAT_CODE = dbo.HBL_CLNT_CAT.CLNT_CAT_CODE
LEFT OUTER JOIN dbo.HBL_CLNT_TYPE ON dbo.HBM_CLIENT.CLNT_TYPE_CODE = dbo.HBL_CLNT_TYPE.CLNT_TYPE_CODE
WHERE (LTRIM(RTRIM(dbo.HBM_MATTER.CLIENT_CODE)) <> '''')'
SELECT #sql = #sql + ' AND (dbo.HBM_MATTER.MATTER_NAME like ''%' + #lookupvalue + '%'')'
SELECT #sql = #sql + ' OR (dbo.HBM_MATTER.CLIENT_MAT_NAME like ''%' + #lookupvalue + '%'')'
SELECT #sql = #sql + ' ORDER BY dbo.HBM_MATTER.MATTER_NAME'
-- Execute the SQL query
EXEC sp_executesql #sql
END
END
In my WCF service I try to execute the stored procedure:
[WebGet(UriTemplate = "number/{value}/?format={format}")]
public IEnumerable<NumberResult> GetNumber(string value, string format)
{
if (string.Equals("json", format, StringComparison.OrdinalIgnoreCase))
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
}
using (var ctx = new MyEntities())
{
ctx.ContextOptions.ProxyCreationEnabled = false;
var results = ctx.procFindNumber(value);
return results.ToList();
}
}
Error message says "The FunctionImport ... could not be found in the container ..."
What am I doing wrong?
You need to qualify the function import with the container name.
E.g change this:
return base.ExecuteFunction<NumberResult>("procFindNumber", lookupvalueParameter);
to this:
return base.ExecuteFunction<NumberResult>("EntityContainerName.procFindNumber", lookupvalueParameter);
The entity container name is found on your EDMX - right click anywhere and do "Properties".
I had a similar problem with EF 4.1... and found that I would get this issue when the code was running in Release mode. In Debug mode, it works fine.