SQL Server 2014 takes off leading zeroes when making Excel file. . . but. - sql-server

This sp_send_dbmail script works in one of our processes. It attaches an Excel file filled with whatever the query is. It knows to do this because of the extension on the file's name (.xls).
However, it changes a varchar(50) field into a number field, and removes the leading zeroes. This is a known annoyance dealt with in a million ways that won't work for my process.
EXEC msdb.dbo.sp_send_dbmail
#profile_name = #profileName
,#recipients = #emailRecipientList
,#subject = #subject
,#importance = #importance
,#body = #emailMsg
,#body_format = 'html'
,#query = #QuerySQL
,#execute_query_database = #QueryDB
,#attach_query_result_as_file = 1
,#query_attachment_filename = #QueryExcelFileName
,#query_result_header = 1
,#query_result_width = #QueryWidth
,#query_result_separator = #QuerySep
,#query_result_no_padding = 1
Examples of problem below: this simple query changes the StringNumber column from varchar to number in Excel, and removes the zeroes.
SELECT [RowID],[Verbage], StringNumber FROM [dbo].[tblTestStringNumber]
In SQL Server (desired format):
After in Excel (leading zeroes missing):
Now, there might be a way. I only say this because in SQL Server 2016 results pane, if you right click in upper left hand corner, it gives the option of "Open in Excel"
And. . . . drum roll . . . the dataset opens in Excel and the leading zeroes are still there!

If you start a number with a single quote (') in Excel, it will interpret it as a string, so a common solution is to change the query to add one in:
SELECT [RowID]
,[Verbage]
, StringNumber = '''' + [StringNumber]
FROM [dbo].[tblTestStringNumber]
And Excel will usually not display the single quote because it knows that it's a way to cast to type string.

#JustJohn I think it will work fine:
SELECT [RowID]
,[Verbage]
, '="' + [StringNumber]+ '"' StringNumber
FROM [dbo].[tblTestStringNumber]

Related

For xml path query returns less records SQLSERVER

I need to generate the following XML structure from my SQL server view for four inspection type. The XML structure is as given below.
==
<Form FormIdentifier="Major Approvals (EPIC:EPIC Test)" CompanyCode="EPIC:EPIC Test" CompanyName="EPIC Test" VesselCode="EPBARN" SubmittedDate="2019-05-22T08:00:00" Status="Submitted" ApprovedDate="" ImoNumber="9251121">
<VesselName>EPIC BARNES</VesselName>
<VoyageNo></VoyageNo>
<IMO_Number>9251121</IMO_Number>
<SIRE>
<SIRERow>
<Company__Terminal2>SHELL</Company__Terminal2>
<Last_Inspected>2019-05-22T12:00:00+00:00</Last_Inspected>
<No_of_Obs>1</No_of_Obs>
<Risk_Rating>2</Risk_Rating>
<ve_Screening_Yes_No>3</ve_Screening_Yes_No>
<Comments>4</Comments>
<Expiry_Date>2019-05-23T12:00:00+00:00</Expiry_Date>
<Planned_Date>2019-05-31T12:00:00+00:00</Planned_Date>
<Planned_Port>SINGAPORE</Planned_Port>
<Plannng_Comments>5</Plannng_Comments>
<Observations_closed_out2>6</Observations_closed_out2>
</SIRERow>
</SIRE>
<Non_SIRE>
<Non_SIRERow>
<Company__Terminal1>BP</Company__Terminal1>
<Last_Inspected1>2019-05-01T12:00:00+00:00</Last_Inspected1>
<No_of_Obs1>1</No_of_Obs1>
<Risk_Rating1>2</Risk_Rating1>
<ve_Screening_Yes_No1>3</ve_Screening_Yes_No1>
<Comments1>4</Comments1>
<Expiry_Date1>2019-05-22T12:00:00+00:00</Expiry_Date1>
<Planned_Date1>2019-05-31T12:00:00+00:00</Planned_Date1>
<Planned_Port1>KERTEH</Planned_Port1>
<Planning_Comments>5</Planning_Comments>
<Observations_closed_out1>6</Observations_closed_out1>
</Non_SIRERow>
</Non_SIRE>
<Additional_Screening>
<Additional_ScreeningRow>
<Company__Terminal>EXXON</Company__Terminal>
<Last_Inspected2>2019-05-01T12:00:00+00:00</Last_Inspected2>
<No_of_Obs2>1</No_of_Obs2>
<Risk_Rating2>2</Risk_Rating2>
<ve_Screening_Yes_No2>3</ve_Screening_Yes_No2>
<Comments2>4</Comments2>
<Expiry_Date2>2019-05-22T12:00:00+00:00</Expiry_Date2>
<Planned_Date_>2019-05-31T12:00:00+00:00</Planned_Date_>
<Planned_Port2>OSAKA</Planned_Port2>
<Planning_Comments1>5</Planning_Comments1>
<Observations_closed_out>6</Observations_closed_out>
</Additional_ScreeningRow>
</Additional_Screening>
</Form>
=====
I have the written following query using for XML path and TYPE to create the above XML file.
it works fine as far as the syntax is concerned but never returns exact no of records. for one or two records it works fine and for more records, it either not generated fully or shows fewer records
DECLARE #vsl AS varchar(50) = 'Sea Fortune 1';
DECLARE #imo AS varchar(50) = '9293741';
SELECT 'Major Approvals (EPIC:EPIC Test)' AS "#FormIdentifier",
'EPIC:EPIC Test' AS "#CompanyCode",
'EPIC Test' AS "#CompanyName",
'' AS "#VesselCode",
GETDATE() AS "#SubmittedDate",
'Submitted' AS "#Status",
'' AS "#ApprovedDate",
#imo AS "#ImoNumber",
#vsl AS VesselName,
'' AS VoyageNo,
#imo AS IMONO,
(SELECT otmajorname AS "SIRERow/Company__Terminal2",
inspectedOn AS "SIRERow/Last_Inspected",
tobs AS "SIRERow/No_of_Obs2",
riskrating AS "SIRERow/Risk_Rating2",
pscreen AS "SIRERow/ve_Screening_Yes_No2",
comment AS "SIRERow/Comments2",
ApprovalTo AS "SIRERow/Expiry_Date",
plplandate AS "SIRERow/Planned_Date",
plport AS "SIRERow/Planned_Port",
remark AS "SIRERow/Plannng_Comments",
openobs AS "SIRERow/Observations_closed_out2"
FROM RptXMLepic
WHERE vtIMONo = #imo
AND InspType = 'SIRE'
FOR XML PATH('SIRE'), TYPE),
(SELECT otmajorname AS "Non_SIRERow/Company__Terminal2",
inspectedOn AS "Non_SIRERow/Last_Inspected",
tobs AS "Non_SIRERow/No_of_Obs2",
riskrating AS "Non_SIRERow/Risk_Rating2",
pscreen AS "Non_SIRERow/ve_Screening_Yes_No2",
comment AS "Non_SIRERow/Comments2",
ApprovalTo AS "Non_SIRERow/Expiry_Date",
plplandate AS "Non_SIRERow/Planned_Date",
plport AS "Non_SIRERow/Planned_Port",
remark AS "Non_SIRERow/Plannng_Comments",
openobs AS "Non_SIRERow/Observations_closed_out2"
FROM RptXMLepic
WHERE vtIMONo = #imo
AND InspType = 'NON- SIRE'
FOR XML PATH('Non_SIRE'), TYPE),
(SELECT otmajorname AS "CDIRow/Company__Terminal2",
inspectedOn AS "CDIRow/Last_Inspected",
tobs AS "CDIRow/No_of_Obs2",
riskrating AS "CDIRow/Risk_Rating2",
pscreen AS "CDIRow/ve_Screening_Yes_No2",
comment AS "CDIRow/Comments2",
ApprovalTo AS "CDIRow/Expiry_Date",
plplandate AS "CDIRow/Planned_Date",
plport AS "CDIRow/Planned_Port",
remark AS "CDIRow/Plannng_Comments",
openobs AS "CDIRow/Observations_closed_out2"
FROM RptXMLepic
WHERE vtIMONo = #imo
AND InspType = 'CDI'
FOR XML PATH('CDI'), TYPE),
(SELECT otmajorname AS "Addional_ScreeningRow/Company__Terminal2",
inspectedOn AS "Addional_ScreeningRow/Last_Inspected",
tobs AS "Addional_ScreeningRow/No_of_Obs2",
riskrating AS "Addional_ScreeningRow/Risk_Rating2",
pscreen AS "Addional_ScreeningRow/ve_Screening_Yes_No2",
comment AS "Addional_ScreeningRow/Comments2",
ApprovalTo AS "Addional_ScreeningRow/Expiry_Date",
plplandate AS "Addional_ScreeningRow/Planned_Date",
plport AS "Addional_ScreeningRow/Planned_Port",
remark AS "Addional_ScreeningRow/Plannng_Comments",
openobs AS "Addional_ScreeningRow/Observations_closed_out2"
FROM RptXMLepic
WHERE vtIMONo = #imo
AND InspType = 'Screen'
FOR XML PATH('Addional_Screening'), TYPE)
FOR XML PATH('Form');
GO
my data lies in the view RptXMLepic.
would you please help me where exactly the problem lies.
It was my mistake only . the query is perfect after a lot of searching on the net I found nothing. actually what I was doing just copying the XML output from the query window and pasting on some noteapd++ editor. But whole XML text was not getting copied. so finally I have clicked on on the output and it was blah blah! I mean it opened pretty well in SQL management studio only with all the data. sorry guys I am pretty relaxed now ..thanks

Correct usage of Dynamic SQL in SQL Server

I’m using static SQL for 99% of the time, but a recent scenario led me to write a dynamic SQL and I want to make sure I didn’t miss anything before this SQL is released to production.
The tables’ names are a combination of a prefix, a 2 letters variable and a suffix and column name is a prefix + 2 letters variable.
First I’ve checked that #p_param is 2 letters length and is “whitelisted”:
IF (LEN(#p_param) = 2 and (#p_param = ‘aa’ or #p_param = ‘bb’ or #p_param = ‘cc’ or #p_param = ‘dd’ or #p_param = ‘aa’)
BEGIN
set #p_table_name = 'table_' + #p_param + '_suffix';
set #sql = 'update ' + QUOTENAME(#p_table_name) + ' set column_name = 2 where id in (1,2,3,4);';
EXEC sp_executesql #sql;
--Here I’m checking the second parameter that I will create the column name with
IF (LEN(#p_column) = 2 and (#p_column = 'ce' or #p_column = 'pt')
BEGIN
Set #column_name = 'column_name_' + #p_column_param;
set #second_sql = 'update ' + QUOTENAME(#p_table_name) + ' set ' +
QUOTENAME(#column_name) + ' = 2 where id in (#p_some_param);';
EXEC sp_executesql #second_sql, N'#p_some_param NVARCHAR(200)', #p_some_param = #p_some_param;
END
END
Is this use case safe? Are there any pitfalls I should be a ware of?
Seems like you've lost some things in the translation to meaningless names to prepare your query to post here, so it's kinda hard to tell. However, the overall approach seems OK to me.
Using a whitelist with QUOTENAME for the identifiers will protect you from SQL injections using the identifiers parameters, and passing the value parameters as a parameter to sp_executeSql will protect you from SQL injections using the value parameters, so I would say you are doing fine on that front.
There are a couple of things I would change, though.
In addition to testing your tables and columns names against a hard coded white list, I would also test then against information_schema.columns, just to make sure that the procedure will not raise an error in case a table or column is missing.
Also, Your whitelist conditions can be improved - Instead of:
IF (LEN(#p_param) = 2 and (#p_param = ‘aa’ or #p_param = ‘bb’ or #p_param = ‘cc’ or #p_param = ‘dd’ or #p_param = ‘aa’)
You can simply write:
IF #p_param IN('aa', 'bb', 'cc','dd')

Query fails on "converting character string to smalldatetime data type"

I've been tasked with fixing some SQL code that doesn't work. The query reads from a view against a predicate. The query right now looks like so.
SELECT TOP (100) Beginn
FROM V_LLAMA_Seminare
//Removal of the following line makes the query successful, keeping it breaks it
where Beginn > (select cast (getdate() as smalldatetime))
order by Beginn desc
When I run the above query, I am greeted with the following error.
Msg 295, Level 16, State 3, Line 1
Conversion failed when converting character string to smalldatetime data type.
I decided to remove the WHERE clause, and now it runs returning 100 rows.
At first, I thought that behind the scenes, SQL Server was somehow including my predicate when bringing back the View . But then I investigated how the View was being created, especially the Beginn field, and at no point does it return a String.
Long story short, the column that becomes the Beginn field is a BIGINT timestamp like 201604201369.... The original user transforms this BIGINT to a smalldatetime using the following magic.
....
CASE WHEN ma.datum_dt = 0
THEN null
ELSE CONVERT(smalldatetime, SUBSTRING(CAST(ma.datum_dt AS varchar(max)),0,5) + '-' +
SUBSTRING(CAST(ma.datum_dt AS varchar(max)),5,2) + '-' +
SUBSTRING(CAST(ma.datum_dt AS varchar(max)),7,2) + ' ' +
SUBSTRING(CAST(ma.datum_dt AS varchar(max)),9,2) +':'+
SUBSTRING(CAST(ma.datum_dt AS varchar(max)),11,2) +':' +
RIGHT(CAST(ma.datum_dt AS varchar(max)),2)) END AS Beginn
...
My last attempt at finding the problem was to query the view and run the function ISDATE over the Beginn column and see if it returned a 0 which it never did.
So my question is two fold, "Why does a predicate break something" and two "Where on earth is this string error coming from when the Beginn value is being formed from a BIGINT".
Any help is greatly appreciated.
This problem is culture related...
Try this and then change the first SET LANGUAGE to GERMAN
SET LANGUAGE ENGLISH;
DECLARE #bi BIGINT=20160428001600;
SELECT CASE WHEN #bi = 0
THEN null
ELSE CONVERT(datetime, SUBSTRING(CAST(#bi AS varchar(max)),0,5) + '-' +
SUBSTRING(CAST(#bi AS varchar(max)),5,2) + '-' +
SUBSTRING(CAST(#bi AS varchar(max)),7,2) + ' ' +
SUBSTRING(CAST(#bi AS varchar(max)),9,2) +':'+
SUBSTRING(CAST(#bi AS varchar(max)),11,2) +':' +
RIGHT(CAST(#bi AS varchar(max)),2)) END AS Beginn
It is a very bad habit to think, that date values look the same everywhere (Oh no, my small application will never go international ...)
Try to stick to culture independent formats like ODBC or ISO
EDIT
A very easy solution for you actually was to replace the blank with a "T"
SUBSTRING(CAST(ma.datum_dt AS varchar(max)),7,2) + 'T' +
Then it's ISO 8601 and will convert...
The solution was found after looking through #Shnugo's comment. When I took my query which contained the Bigint->Datetime conversion logic, and put it into a CTE with "TOP 100000000" to avoid any implicit conversion actions, my query worked. Here is what my view looks like now with some unimportant parts omitted.
---Important part---
CREATE VIEW [dbo].[V_SomeView] AS
WITH CTE AS (
SELECT TOP 1000000000 ma.id AS MA_ID,
---Important part---
vko.extkey AS ID_VKO,
vko.text AS Verkaufsorganisation,
fi.f7000 AS MDM_Nr,
vf.f7105 AS SAPKdnr,
CASE WHEN ma.datum_dt = 0 --Conversion logic
CASE WHEN ma.endedatum_dt = 0 --Conversion logic
CONVERT(NVARCHAR(MAX),art.text) AS Art,
.....
FROM [ucrm].[dbo].[CRM_MA] ma,
[ucrm].[dbo].[CRM_fi] fi,
[ucrm].[dbo].[CRM_vf] vf,
[ucrm].[dbo].[CRM_ka] vko,
[ucrm].[dbo].[CRM_ka] art,
[ucrm].[dbo].[CRM_ka] kat
where ma.loskz = 0
and fi.loskz = 0
and vf.loskz = 0
and fi.F7029 = 0
and vf.F7023 = 0
...
GROUP BY ma.id,
vko.extkey,
vko.text,
fi.f7000 ,
vf.f7105,
ma.datum_dt,
ma.endedatum_dt,
....
)
select * FROM CTE;

Toad and SQL Server 2005

where a.system_nr =''''5300'''' and
a.external_status_cd = '''''''' and
a.cust_acct_id = b.rel_cust_acct_id and
b.cust_acct_id = c.cust_acct_id and
c.cust_acct_id = d.cust_acct_id and
d.acct_status_cd = ''''OPEN'''' and
d.time_mnth_gen_id =''''' + #BegDate + ''''' and
a.cust_acct_id = e.cust_acct_id and
e.tran_dt >=''''' + #BegDate + ''''' and
e.tran_dt<=''''' + #EndDate + ''''' and
d.portfolio_cd = ''''HEQ'''' and
a.time_mnth_gen_id =''''' + #BegDate + ''''' '')'
Here is the where condition which is already written and I need to make changes.
Can you please tell me why they are using '''''+#begdate'''''? Can i use '+Bedate'?
I mean why they are using ''''' each side?
Try this in SQL Server:
select '''''someval'''''
You notice that item gives:
''someval''
In SQL Server '' will equate to a single quote character, so the above line is
select [open string][single quote][single quote]someval[single quote][single quote][close string]
Without seeing the rest of the SQL, my guesses would be:
for use in dynamic SQL as #BegDate is a variable and you have the statement ending with a single quote
the data contains a bunch of single quotes
You should not be able to just '+BegDate' because it's a variable and stripping the # would cause it to be evaluated as a field.
If you meant to just reduce the number of single quotes, I would imagine the original author put them there for a reason. You can run the query with the original single quotes and again with the reduced single quotes and see if you get the same result set.

How to do hit-highlighting of results from a SQL Server full-text query

We have a web application that uses SQL Server 2008 as the database. Our users are able to do full-text searches on particular columns in the database. SQL Server's full-text functionality does not seem to provide support for hit highlighting. Do we need to build this ourselves or is there perhaps some library or knowledge around on how to do this?
BTW the application is written in C# so a .Net solution would be ideal but not necessary as we could translate.
Expanding on Ishmael's idea, it's not the final solution, but I think it's a good way to start.
Firstly we need to get the list of words that have been retrieved with the full-text engine:
declare #SearchPattern nvarchar(1000) = 'FORMSOF (INFLECTIONAL, " ' + #SearchString + ' ")'
declare #SearchWords table (Word varchar(100), Expansion_type int)
insert into #SearchWords
select distinct display_term, expansion_type
from sys.dm_fts_parser(#SearchPattern, 1033, 0, 0)
where special_term = 'Exact Match'
There is already quite a lot one can expand on, for example the search pattern is quite basic; also there are probably better ways to filter out the words you don't need, but it least it gives you a list of stem words etc. that would be matched by full-text search.
After you get the results you need, you can use RegEx to parse through the result set (or preferably only a subset to speed it up, although I haven't yet figured out a good way to do so). For this I simply use two while loops and a bunch of temporary table and variables:
declare #FinalResults table
while (select COUNT(*) from #PrelimResults) > 0
begin
select top 1 #CurrID = [UID], #Text = Text from #PrelimResults
declare #TextLength int = LEN(#Text )
declare #IndexOfDot int = CHARINDEX('.', REVERSE(#Text ), #TextLength - dbo.RegExIndexOf(#Text, '\b' + #FirstSearchWord + '\b') + 1)
set #Text = SUBSTRING(#Text, case #IndexOfDot when 0 then 0 else #TextLength - #IndexOfDot + 3 end, 300)
while (select COUNT(*) from #TempSearchWords) > 0
begin
select top 1 #CurrWord = Word from #TempSearchWords
set #Text = dbo.RegExReplace(#Text, '\b' + #CurrWord + '\b', '<b>' + SUBSTRING(#Text, dbo.RegExIndexOf(#Text, '\b' + #CurrWord + '\b'), LEN(#CurrWord) + 1) + '</b>')
delete from #TempSearchWords where Word = #CurrWord
end
insert into #FinalResults
select * from #PrelimResults where [UID] = #CurrID
delete from #PrelimResults where [UID] = #CurrID
end
Several notes:
1. Nested while loops probably aren't the most efficient way of doing it, however nothing else comes to mind. If I were to use cursors, it would essentially be the same thing?
2. #FirstSearchWord here to refers to the first instance in the text of one of the original search words, so essentially the text you are replacing is only going to be in the summary. Again, it's quite a basic method, some sort of text cluster finding algorithm would probably be handy.
3. To get RegEx in the first place, you need CLR user-defined functions.
It looks like you could parse the output of the new SQL Server 2008 stored procedure sys.dm_fts_parser and use regex, but I haven't looked at it too closely.
You might be missing the point of the database in this instance. Its job is to return the data to you that satisfies the conditions you gave it. I think you will want to implement the highlighting probably using regex in your web control.
Here is something a quick search would reveal.
http://www.dotnetjunkies.com/PrintContent.aspx?type=article&id=195E323C-78F3-4884-A5AA-3A1081AC3B35
Some details:
search_kiemeles=replace(lcase(search),"""","")
do while not rs.eof 'The search result loop
hirdetes=rs("hirdetes")
data=RegExpValueA("([A-Za-zöüóőúéáűíÖÜÓŐÚÉÁŰÍ0-9]+)",search_kiemeles) 'Give back all the search words in an array, I need non-english characters also
For i=0 to Ubound(data,1)
hirdetes = RegExpReplace(hirdetes,"("&NoAccentRE(data(i))&")","<em>$1</em>")
Next
response.write hirdetes
rs.movenext
Loop
...
Functions
'All Match to Array
Function RegExpValueA(patrn, strng)
Dim regEx
Set regEx = New RegExp ' Create a regular expression.
regEx.IgnoreCase = True ' Set case insensitivity.
regEx.Global = True
Dim Match, Matches, RetStr
Dim data()
Dim count
count = 0
Redim data(-1) 'VBSCript Ubound array bug workaround
if isnull(strng) or strng="" then
RegExpValueA = data
exit function
end if
regEx.Pattern = patrn ' Set pattern.
Set Matches = regEx.Execute(strng) ' Execute search.
For Each Match in Matches ' Iterate Matches collection.
count = count + 1
Redim Preserve data(count-1)
data(count-1) = Match.Value
Next
set regEx = nothing
RegExpValueA = data
End Function
'Replace non-english chars
Function NoAccentRE(accent_string)
NoAccentRE=accent_string
NoAccentRE=Replace(NoAccentRE,"a","§")
NoAccentRE=Replace(NoAccentRE,"á","§")
NoAccentRE=Replace(NoAccentRE,"§","[aá]")
NoAccentRE=Replace(NoAccentRE,"e","§")
NoAccentRE=Replace(NoAccentRE,"é","§")
NoAccentRE=Replace(NoAccentRE,"§","[eé]")
NoAccentRE=Replace(NoAccentRE,"i","§")
NoAccentRE=Replace(NoAccentRE,"í","§")
NoAccentRE=Replace(NoAccentRE,"§","[ií]")
NoAccentRE=Replace(NoAccentRE,"o","§")
NoAccentRE=Replace(NoAccentRE,"ó","§")
NoAccentRE=Replace(NoAccentRE,"ö","§")
NoAccentRE=Replace(NoAccentRE,"ő","§")
NoAccentRE=Replace(NoAccentRE,"§","[oóöő]")
NoAccentRE=Replace(NoAccentRE,"u","§")
NoAccentRE=Replace(NoAccentRE,"ú","§")
NoAccentRE=Replace(NoAccentRE,"ü","§")
NoAccentRE=Replace(NoAccentRE,"ű","§")
NoAccentRE=Replace(NoAccentRE,"§","[uúüű]")
end function

Resources