Writing a dreaded SQL search query (2nd phase) - sql

I am working on a search query (with an asp.net 3.5 front end) which seems quite simple, but is quite complex.
The complete query is:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[usp_Item_Search]
#Item_Num varchar(30) = NULL
,#Search_Type int = NULL
,#Vendor_Num varchar(10) = NULL
,#Search_User_ID int = 0
,#StartDate smalldatetime = NULL
,#EndDate smalldatetime = NULL
AS
DECLARE #SQLstr as nvarchar(4000)
Set #SQLstr = 'SELECT RecID, Vendor_Num, Vendor_Name, InvoiceNum, Item_Num,
(SELECT CONVERT(VARCHAR(11), RecDate, 106) AS [DD MON YYYY]) As RecDate, NeedsUpdate, RecAddUserID FROM [tbl_ItemLog] where 1=1 '
IF (#Item_Num IS NOT NULL and LTRIM(#Item_Num) <> '')
Begin
If #Search_Type = 0
BEGIN
Set #SQLstr = #SQLstr + 'AND Item_Num LIKE ''' + #Item_Num + '%'''
END
If #Search_Type = 1
BEGIN
Set #SQLstr = #SQLstr + 'AND Item_Num LIKE ''%' + #Item_Num + '%'''
END
If #Search_Type = 2
BEGIN
Set #SQLstr = #SQLstr + 'AND Item_Num LIKE ''%' + #Item_Num + ''''
END
End
IF (#Vendor_Num IS NOT NULL and LTRIM(#Vendor_Num) <> '')
Begin
Set #SQLstr = #SQLstr + ' AND Vendor_Num = ''' + #Vendor_Num + ''''
End
IF (#Search_User_ID IS NOT NULL and #Search_User_ID > 0)
Begin
Set #SQLstr = #SQLstr + ' AND RecAddUserID = ' + convert(nvarchar(20),#Search_User_ID)
End
Set #SQLstr = #SQLstr + ' AND (RecDate BETWEEN ''' + convert(nvarchar(10),#StartDate,106) + ''' AND ''' + convert(nvarchar(10),#EndDate,106) + ''')'
PRINT (#SQLstr)
--Execute (#SQLstr)
When I pass all empty parameter values, I get an error:
"Failed to convert parameter value
from a String to a Int32."
The asp.net code that is calling the stored proc is:
//Display search results in GridView;
SqlConnection con = new SqlConnection(strConn);
//string sqlItemSearch = "usp_Item_Search";
SqlCommand cmdItemSearch = new SqlCommand(sqlItemSearch, con);
cmdItemSearch.CommandType = CommandType.StoredProcedure;
cmdItemSearch.Parameters.Add(new SqlParameter("#Item_Num", SqlDbType.VarChar, 30));
cmdItemSearch.Parameters["#Item_Num"].Value = txtItemNumber.Text.Trim();
cmdItemSearch.Parameters.Add(new SqlParameter("#Search_Type", SqlDbType.Int));
cmdItemSearch.Parameters["#Search_Type"].Value = ddlSearchType.SelectedItem.Value;
cmdItemSearch.Parameters.Add(new SqlParameter("#Vendor_Num", SqlDbType.VarChar, 10));
cmdItemSearch.Parameters["#Vendor_Num"].Value = txtVendorNumber.Text.Trim();
cmdItemSearch.Parameters.Add(new SqlParameter("#Search_User_ID", SqlDbType.Int));
cmdItemSearch.Parameters["#Search_User_ID"].Value = ddlSeachUser.SelectedItem.Value;
if (!string.IsNullOrEmpty(txtStartDate.Text))
{
cmdItemSearch.Parameters.Add(new SqlParameter("#StartDate", SqlDbType.DateTime));
cmdItemSearch.Parameters["#StartDate"].Value = Convert.ToDateTime(txtStartDate.Text.Trim());
}
else
{
cmdItemSearch.Parameters.Add(new SqlParameter("#StartDate", SqlDbType.DateTime));
cmdItemSearch.Parameters["#StartDate"].Value = Convert.ToDateTime("01/01/1996");
}
if (!string.IsNullOrEmpty(txtEndDate.Text))
{
cmdItemSearch.Parameters.Add(new SqlParameter("#EndDate", SqlDbType.DateTime));
cmdItemSearch.Parameters["#EndDate"].Value = Convert.ToDateTime(txtEndDate.Text.Trim());
}
else
{
cmdItemSearch.Parameters.Add(new SqlParameter("#EndDate", SqlDbType.DateTime));
cmdItemSearch.Parameters["#EndDate"].Value = Convert.ToDateTime(DateTime.Now);
}
con.Open();
SqlDataAdapter ada = new SqlDataAdapter(cmdItemSearch);
DataSet ds = new DataSet();
ada.Fill(ds);
gvSearchDetailResults.DataSource = ds;
gvSearchDetailResults.DataBind();
pnlSearchResults.Visible = true;
How can I resolve this?

You're not quite building the string correctly as far as I can tell. If no #Item_Num is passed in, you'll end up with no WHERE key word... you'll just have "FROM [tblItem_Log] AND..."
I would make all of the criteria appends be "AND ..." and as your initial statement use:
FROM [tbl_Item_Log] WHERE (1=1)
Since you have code to return the generated string, why not put that into SSMS and try to run it?
I also just noticed that if you don't pass in date values that you will end up executing a NULL string, because your final concatenation will end up causing a NULL. These are the kinds of things that you need to pay very close attention to if you're going to be using dynamic SQL to build queries.
Once I corrected that I was able to run the stored procedure without any errors (at least to generate what looks like a valid SQL statement). That leads me to believe that it may be a problem with data types in the underlying table. Can you provide the definition for that?
One last note: Personally, I would use
CONVERT(VARCHAR(11), RecDate, 106) AS RecDate
instead of the seemingly unnecessary subquery that you have.
Yet another edit:
You may want to remove the code that checks LTRIM(#Search_User_ID) <> ''. It's a pointless bit of code and perhaps a setting particular to your server/connection is causing it to fail because of the type mismatch.

IF (Search_User_ID IS NOT NULL)
needs an # symbol infront of the variable
You say you are passing empty string in for all variables but one is an int, it can't take an empty string that is not int data. Can't believe I didn;t notice that the first time.

Why don't you use single parameterized query like this:
select
recdid,
Vendor_Num,
Vendor_Name,
InvoiceNum,
Item_Num,
CONVERT(VARCHAR(11), RecDate, 106) as RecDate,
NeedsUpdate,
RecAddUserID
FROM
[tbl_ItemLog] as t
where
(((Item_num like #Item_Num + '%' and #Search_Type = 0) OR
(Item_num like '%' + #Item_Num + '%' and #Search_Type = 1) OR
(Item_num like '%' + #Item_Num + '%' and #Search_Type = 2))
OR
#Item_Num IS NULL) AND
(Vendor_Num = #Vendor_Num OR #Vendor_Num IS NULL) AND
(RecAddUserId = #Search_User_Id OR #Search_User_Id IS NULL) AND
(RecDate BETWEEN #StartDate AND #EndDate)

You really have several different stored procedures here. Why not just write them separately? Everything that's controlled by switch statements could be easily in procedural code. Same for the LTRIM calls.
You could call them all from a single SP with switch statements; but I think it's generally better to not merge them in the first place. The SP queries will optimize more easily, and the code will be simpified. There's not much you gain by consolidating them.
Not sure of your business rules, but you could simplify this outside SQL with
switch(search_type) {
case 1:
do_query_type_1(args);
break;
case 2:
do_query_type_2(args);
break;
case 3:
do_query_type_3(args);
break;
default:
whatever ...
}
Also it looks like you have separate logic for cases where the item number is provided or not. Same for other fields. Each of your use cases looks like it resolves to a pretty simple query.

Related

Multiple conditional Where clause

I currently have a query that will pull a bunch of information from my database based on whatever where condition that I want to use.
declare #CaseNum char(7),
#ImportId char,
#FormatId char,
#SessionId char(5)
set #CaseNum = '' --I can place the value that I want to search by in here
set #ImportId = ''
set #FormatId = ''
set #SessionId = ''
--------------------
query in here
--------------------
where
gr.[CaseNum] = #CaseNum --currently I have to comment the ones I'm not using out
--im.[ImportId] = #ImportId
--fr.[FormatId] = #FormatId
--se.[SessionId] = #SessionId
I want to be able to take the comment portion out and simply display all rows if the parameter = ''
For example if I use set #CaseNum = '1234567' then it will search by that parameter and if I use #FormatId = '12' it will search by that one.
I have tried using the following and a few other attempts but I am getting nowhere fast.
where
gr.[CaseNum] = '%' + #CaseNum + '%'
and im.[ImportId] = '%' + #ImportId + '%'
and fr.[FormatId] = '%' + #FormatId + '%'
and se.[SessionId] = '%' + #SessionId + '%'
With help from the link that #Norman posted I figured it out. I wanted to post my solution for others to see.
declare #CaseNum varchar(MAX),
#ImportId varchar(MAX)
set #CaseNum = ''
set #ImportId = ''
----------------------------------------------------------------------------
If(#CaseNum = '') --Sets the parameter to NULL for COALESCE to work
Begin
Select #CaseNum = NULL
End
If(#ImportId = '') --Sets the parameter to NULL for COALESCE to work
Begin
Select #ImportId = NULL
End
--------------------
query in here
--------------------
where
gr.[CaseNum] = COALESCE(#CaseNum, gr.[CaseNum])
and im.ImportId = COALESCE(#ImportId, im.ImportId)
This solution allows the query to use just a single parameter or all at the same time.
You might want to look into building your query.
DECLARE #Number varchar(10)
DECLARE #Where varchar(max)
DECLARE #Query varchar(max)
SET #Query = 'SELECT * FROM TestTable'
SET #Where = ''
SET #Number = '3'
IF ISNULL(#Number, '') != ''
BEGIN
SET #Where = #Where + 'and testNumber = ' + #Number
END
IF LEN(#Where) > 0
BEGIN
SET #Where = SUBSTRING(#Where, 4, LEN(#Where))
END
if ISNULL(#Where, '') != ''
BEGIN
SET #Query = #Query + ' WHERE ' + #Where
END
EXEC(#Query)
Check out this gentleman's article for reference: https://social.msdn.microsoft.com/forums/sqlserver/en-US/1ec6ddd9-754b-4d78-8d3a-2b4da90e85dc/dynamically-building-where-clauses

Export database table to existing excel file

I've looked already but what I found so far is exporting to a existing excel sheet, what I need is to make a copy of an existing excel template I have and pass my data there. I just need to be pointed in the right way, hope I'm clear enough.
EDIT
I have an application working on Java to which I will add this.
Assuming that you ask about SQL Server, you can create 2 step job, one for copying your xls, and in second step using openrowset/insert into your copied excel file.
Here is Tsql which copies template and also sends an email, and info about xp_cmdshell https://msdn.microsoft.com/en-us/library/ms190693.aspx
DECLARE #cmd varchar(512)
DECLARE #fd varchar(512)
DECLARE #odbc varchar(128)
DECLARE #db varchar(128)
SELECT #fd = 'D:\Reports\Exact\ByWeek' + CONVERT(VARCHAR(10), GETDATE()-8,120)+'_'+CONVERT(VARCHAR(10), GETDATE(),120)+'.xls'
SELECT #cmd = 'copy D:\Reports\System\shipments_week.xls ' + #fd
EXEC MASTER..XP_CMDSHELL #cmd, NO_OUTPUT
SET #odbc = 'Microsoft.Jet.OLEDB.4.0'
SET #db = 'Excel 8.0;Database=' + #fd
exec('INSERT INTO OPENrowset(''' + #odbc + ''',''' + #db + ''',''SELECT
OrderNr,Debtor,SUM_NSHIPPED,SUM_TOTAL,PERC_NSHIPPED,ORDDAT,INVDAT,ORD_SYSCREATED,HIST_LAST_MODIFIED,PT_WZ_SENT
FROM [Shipped$]'')
SELECT * FROM salag_shipmentsbyWeek')
exec('INSERT INTO OPENrowset(''' + #odbc + ''',''' + #db + ''',''SELECT
OrderNr,Debtor,SUM_NSHIPPED,SUM_TOTAL,PERC_NSHIPPED,ORDDAT,INVDAT,ORD_SYSCREATED,HIST_LAST_MODIFIED,PT_WZ_SENT
FROM [SB$]'')
SELECT * FROM salag_shipmentsbyWeek WHERE Debtor NOT LIKE ''%CASTORAMA%'' AND Debtor NOT LIKE ''Praktiker%''')
DECLARE #Body VARCHAR(4096)
DECLARE #BodyType VARCHAR(16)
DECLARE #path VARCHAR(128)
DECLARE #f VARCHAR(32)
DECLARE #topic VARCHAR(128)
SELECT #f = CONVERT(VARCHAR(10), GETDATE()-8,120)+'_'+CONVERT(VARCHAR(10), GETDATE(),120)
SELECT #topic = 'New Report ['+ CONVERT(VARCHAR(10), GETDATE()-8,120)+'_'+CONVERT(VARCHAR(10), GETDATE(),120) + ']'
SELECT #path = '\\appsrv\Reports\Exact\ByWeek' + CONVERT(VARCHAR(10), GETDATE()-8,120)+'_'+CONVERT(VARCHAR(10), GETDATE(),120)+'.xls'
SELECT #Body = 'New Scan Report has been created: '+#f+' '+ #path
SELECT #BodyType = 'HTMLBody'
exec sp_send_cdosysmail 'Reports Info ','reports#foo ar.com',#topic, #Body, #BodyType
I was able to achieve what I was looking for using JExcel and it was pretty easy, here I will share my code and hope it helps someone in the future. Sorry its not properly commented yet, I will later post an update.
Db_Connect connection = new Db_Connect();
conn = connection.connect();
String originalFile = "C:\\Users\\Geni\\Desktop\\Book1-Template-new.xls";
date = date.replace("/", "-");
SimpleDateFormat myDate = new SimpleDateFormat("yyyy-MM-dd");
try{
Workbook original = Workbook.getWorkbook(new File(originalFile));
WritableWorkbook copy = Workbook.createWorkbook(new File(date+".xls"), original);
java.util.Date format = myDate.parse(date);
java.sql.Date newDate = new java.sql.Date(format.getTime());
String sql = "SELECT Name, sporecount.* FROM sporesfungi "
+ "INNER JOIN sporecount ON sporesfungi.IDSpore = sporecount.IDSpore"
+ "WHERE Date = ? ORDER BY TraceNum";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setDate(1, newDate);
rs = statement.executeQuery();
/*stmt = conn.createStatement();
rs = stmt.executeQuery(sql);*/
WritableSheet sheet = copy.getSheet(0);
WritableCell cell;
String spore;
while(rs.next()){
for(int i = 2; i < 64 ;i++){
cell = sheet.getWritableCell(1,i);
spore = cell.getContents();
if(rs.getString("Name").equals(spore)){
Number l1 = new Number(14-rs.getInt("TraceNum"),i,rs.getInt("Amount")) ;
sheet.addCell(l1);
}
}
}
copy.write();
copy.close();
original.close();
}
catch (BiffException | IOException e) {
}

Parametrize query in t-sql

SELECT TOP #columnCount #columnName
FROM #tableName
I get the following error
Incorrect syntax near '#columnCount'.
What could be wrong?
If I change to
SELECT TOP (#columnCount) #columnName
FROM #tableName
I get the following error
Must declare the table variable "#tableName".
I run it from C#
A safe and secure way would be
DECLARE #columnCount INT = 100
DECLARE #columnName NVARCHAR(128) = 'YourColumnName'
DECLARE #tableName NVARCHAR(128) = 'YourTableName'
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N'SELECT TOP (#columnCount) ' + QUOTENAME(#columnName) + N'
FROM ' + QUOTENAME(#tableName)
EXECUTE sp_executesql #Sql
,N'#columnCount INT'
,#columnCount
You need dynamic SQL to accomplish what you're trying to do.
DECLARE #sql VARCHAR(max);
SET #sql = 'SELECT TOP ' + #columnCount + ' ' + #columnName + ' FROM ' + #tableName;
EXEC(#sql);
The variables used need to be converted appropriately.
Read more in the documentation
Column lists and Table names cannot be parameters. However, since you are running this from C# you are technically already using Dynamic SQL (unless you are calling a stored procedure with those params but there is no mention here of stored procedures being used so for now I will assume not). When building the SQL in C#, you need to concatenate the Column List and Table Name into the query but you can still use a parameter for the value used by the TOP() operator:
SqlConnection _Connection = new SqlConnection("connection string");
SqlCommand _Command = new SqlCommand();
SqlDataReader _Reader = null;
string _Query;
string _TableName = "dbo.MyTable";
string _ColumnList = "Field1, Field2 AS [AliasedName], Field3";
int _NumberOfRows = 12;
_Query = String.Concat("SELECT TOP (#NumberOfRows) ",
_ColumnList, " FROM ", _TableName);
SqlParameter _NumRows = new SqlParameter("#NumberOfRows", SqlDbType.Int);
_NumRows.Value = _NumberOfRows;
try
{
_Connection.Open();
_Reader = _Command.ExecuteReader();
// do stuff
}
finally
{
_Reader.Close();
_Connection.Close();
}
Of course, you could also just concatenate the #NumberOfRows value directly into the query as well, but keeping it as a parameter will allow for Query Plan re-use if running this query multiple times with the same values for ColumnList and TableName but changing the #NumberOfRows value.

Dynamic sql is giving syntax error.

I am executing the following sql. I get a syntax error which is (Incorrect syntax near '=')
The query executes fine and gives proper results when executed normally. couldn't understand. plz take a look.
DECLARE #pvchMachineId VARCHAR(100) = ''
DECLARE #pvchMake VARCHAR(100) = ''
DECLARE #sql NVARCHAR(1000)
SELECT #sql = ' SELECT TOP 20 x.intId, x.vchMachineId, x.AUDenom, x.intGroupId,
x.vchMake, x.vchModel, x.mCurrency
from dbo.Machine x
inner join
(select max(m1.AUDenom) as audenom, m1.vchMachineId
from dbo.Machine m1
left JOIN dbo.ImportedFile ife on m1.intImportedFileId = ife.intId
WHERE ife.dtFileDate >= ''1-1-2013'' AND ife.dtFileDate <= ''1-29-2014'' AND
--following two lines cause the error
(' + #pvchMake + '= ''0'' OR m1.vchMake = #pvchMake) AND
(' + #pvchMachineId +'= ''0'' OR m1.vchMachineId = #pvchMachineId)
group by vchMachineId) y
on x.AUDenom = y.audenom and x.vchMachineId = y.vchMachineId
ORDER BY x.AUDenom DESC'
Update your query to the following
(#pvchMake = ''0'' OR m1.vchMake = #pvchMake) AND
(#pvchMachineId = ''0'' OR m1.vchMachineId = #pvchMachineId)
than later when you go to execute just pass it in as parameters to sp_executesql function.
EXEC sp_executesql #sql
,N'#pvchMachineId VARCHAR(100), #pvchMake VARCHAR(100)'
,#pvchMachineId,#pvchMake
or this which is cleaner
Declare #ParametersDefinition NVARCHAR(max) = N'#pvchMachineId VARCHAR(100), #pvchMake VARCHAR(100)'
EXEC sp_executesql #sql, #ParametersDefinition, #pvchMachineId,#pvchMake
In the end you do not want to concatenate your dynamic SQL statement, it opens it up for SQL Injections. Even though it is a valid option it should be avoided at all cost.
This statement :
'(' + #pvchMake + '= ''0'' OR m1.vchMake = #pvchMake)'
Will output, since the variables are not initialized by anything else than '' :
(= '0' OR m1.vchMake = #pvchMake)
Which is not correct syntaxically.
You should use :
'(''' + #pvchMake + '''= ''0'' OR m1.vchMake = #pvchMake)'
Which would output :
(''= '0' OR m1.vchMake = #pvchMake)
Maybe this can make sense:
...
(''' + #pvchMake + '''= ''0'' OR m1.vchMake = ''' + #pvchMake +''') AND
(''' + #pvchMachineId +'''= ''0'' OR m1.vchMachineId = ''' + #pvchMachineId + ''')
...

best approach to execute a dynamic query inside a SP

I'm having some trouble executing a dynamic query inside my SP, and I thought asking for some help as I can't execute it correctly no matter what I try:
I have tried:
SET #subWorksQuery =
'UPDATE JK_SubscriberWorks SET ' +
'update_date = convert(datetime, ''' + #dateNow + ''', 103), ' +
'challenge_' + convert(nvarchar(2), #challengeDay) + '_q = ''' + #challengeQuestion + ''', ' +
'challenge_' + convert(nvarchar(2), #challengeDay) + '_a = ''' + #challengeAnswer + ''' ' +
'WHERE subscriberwork_id = '' + convert(nvarchar(10), #subscriberWorksId) + '';';
execute #execReturn = #subWorksQuery
but I always get:
Msg 203, Level 16, State 2, Procedure sp_InsertChallengeResponse_test,
Line 112
The name 'UPDATE JK_SubscriberWorks SET update_date = convert(datetime, '23-12-2011 23:35:17', 103), challenge_23_q =
'Hvilket år blev Klasselotteriet omdannet til et aktieselskab? Få hjælp til svaret.',
challenge_23_a = '1992' WHERE subscriberwork_id = ' +
convert(nvarchar(10), #subscriberWorksId) + ';' is not a valid
identifier.
Removing the UPDATE statement from that error and run it independently, it runs and performs the update
If I use sp_executesql like
SET #subWorksQuery =
N'UPDATE JK_SubscriberWorks SET ' +
'update_date = #a, ' +
'challenge_' + convert(nvarchar(2), #challengeDay) + '_q = #b, ' +
'challenge_' + convert(nvarchar(2), #challengeDay) + '_a = #c ' +
'WHERE subscriberwork_id = #d;';
SET #parmDefinition = N'#a datetime, #b nvarchar(250), #c nvarchar(500), #d decimal';
execute sp_executesql
#subWorksQuery,
#parmDefinition,
#a = #CreateDate, #b = #challengeQuestion, #c = #challengeAnswer, #d = #subscriberWorksId;
It never performs the UPDATE, but does not throw any error.
What am I missing here?
Run it like this:
execute (#subWorksQuery)
[you won't be getting anything back from the update statement in the variable, and you can't run like this execute (#execReturn = #subWorksQuery) ]
Without parentheses it seems to be starting parsing, assuming it is a stored procedure name, but failing when it hits the max length for one.
In saying that, it is better to use sp_executesql with parameters.
I am not sure what you are looking for in the return value, but if you just need the count of rows affected, that should be easy to obtain.
Change:
execute #execReturn = #subWorksQuery
to:
execute (#subWorksQuery)
select #execReturn = ##ROWCOUNT
just a thought...your #d parameter is a decimal value. Is your id an int? is there a possible data type conflict?
how are your sp input parameters defined? Could you post the full sp?
Dave