SQL server conditional select statement - asp.net

Ok, this might be an easy one, but I just can't get it.
I am creating a page which will query a table with many columns and most items are not unique. I need to be able to get a list of records that match as many of the (up to 4) search criteria as possible.
Example:
I am user searching for the following items, I enter at least one and up to 4 of the items below in a text box:
Name, age, gender, weight (user may or may not fill in all of them).
If he just enters "F" for gender, then he will get a list of thousands of females with their name, age, gender and weight.
However if he enters "F" for gender and "300" for weight, he will get a much smaller list of returned records.
I need to be able to create a sql statement that can perform that search with that functionality.
advTHANKSance

I've used similar to the one below to do what you are trying:
DECLARE #Gender varchar(1)
DECLARE #Age int
DECLARE #Weight int
DECLARE #Name varchar(64)
SELECT * FROM MyTable
WHERE
(#Gender is null OR Gender = #gender)
AND (#weight is null OR Weight = #weight)
AND (#Age is null OR age = #Age)
and (#Name is null OR Name = #Name)
if you were to create a stored procedure (which i would recommend) it would look like this:
CREATE PROCEDURE SelectRecords
#Gender varchar(1),
#Age int,
#Weight int,
#Name varchar(64)
AS
SELECT * FROM MyTable
WHERE
(#Gender is null OR Gender = #gender)
AND (#weight is null OR Weight = #weight)
AND (#Age is null OR age = #Age)
and (#Name is null OR Name = #Name)
What this stored procedure is doing is checking to see if you passed a value in for the specific parameter. If you DID NOT then it will be null and the condition will be true. if you DID then it will not be null and the second condition must evaluate to true for the record to be returned.

I've often seen this done with the following SQL statement (where #gender, #weight, #age, and #name are filled in with data from the user, and gender, weight, age, and name are table fields):
SELECT * FROM MyTable
WHERE
gender = COALESCE(#gender, gender)
AND weight = COALESCE(#weight, weight)
AND age = COALESCE(#age, age)
and name= COALESCE(#name, name)
(Edit: I just wanted to add a short explanation of why this works for anyone not familiar with coalesce. The coalesce function takes the first not-null value of the 2 passed to it. So if there is something in the # parameter, which means the user entered data for that field, it will check if the field equals that user-entered value; if the user didn't enter anything and the # parameter is null, it will test against the second value, which is the field itself - and as the field is always equal to itself, this will return all records - it won't filter based on this field at all.)

I did stuff like this by combining a null check with the parameter. If it was null then everything got included, if not then the other part actually mattered
CREATE myFunnyProc ( #p1 nvarchar(10), #p2 nvarchar(10) ) AS BEGIN
SELECT * FROM dbo.myReallyLongTable table
WHERE
(#p1 is null or table.name LIKE #p1)
AND (#p2 is null or table.age = #p2)
END

#Abe - Your solution will work IF Age and Weight are not between Name and Gender in the SELECT statement. I know cuz I tried it :) on SQL Server 2008. I made a table with many records with NULLs scattered throughout. I also made a proc, and running it as you wrote it would not return rows when age or weight were entered as parameters. I moved the int based params in the SELECT statement to the top or bottom of list, both worked. But nesting the int params between the string params in the select statement and then specifying the int params made it fail...
So why did the original not work?? :) Anyone?
BTW, the COALESCE will not work at all (yes, I tested it and reordered the SELECT statement in the proc). It LOOKED like it should work...

Seeing as you are using ASP.NET, you could take a look at LINQ-to-SQL which solves this in a very elegant way:
var query = db.T_Persons;
if (txtGender.Text != string.Empty)
query = query.Where(x => x.Gender == txtGender.Text);
if (txtWeigth.Text != string.Empty)
query = query.Where(x => x.Weight == int.Parse(txtWeight.Text));
...
Of course, you'll need to be using .NET 3.5 or newer.

The answer is something most people try to stay away from it is dynamic sql.
I would suggest creating a stored procedure you can call for this, but here is the code. You need to put the name of your table in place of tablename.
Declare
#gender varchar(1),
#weight int,
#age int,
#name varchar(100),
#sql varchar(200),
#variableCount int
set #variableCount = 0
set #sql = 'select * from tablename'
if(#gender is not null)
Begin
#sql += ' where gender = #gender'
#vaiableCount = #VariableCount+1
End
if(#weight is not null)
Begin
if(#variableCount = 0)
Begin
#sql += ' Where '
End
else
#sql += ' And '
#sql += 'weight = #weight'
End
if(#age is not null)
Begin
if(#VariableCount = 0)
Begin
#sql += ' where '
End
else
#sql += ' And '
#sql += 'age = #age'
End
if(#name is not null)
Begin
if(#VariableCount = 0)
Begin
#sql += ' where '
End
else
#sql += ' And '
#sql += 'name = #name'
End
execute #sql

Related

SQL Server stored procedure dynamic Where condition in temporary table

I am using ASP.NET and a SQL Server database.
I have options to search values from gridview base on some criteria. The criteria are mostly independent from each other.
For example:
"where ProductType = " + Convert.ToInt32(recordType.persoRecord) +
" and AccountNumber like '%" + SearchValue + "%'";
or
"where fileid=" + File_ID + ShowSearch +
" and lower(j.CardHolderName) like '%" + SearchValue.Trim().ToLower() + "%'
There are a lot of options to search by user. I have millions of rows of data in this table, in order to fetch the data and bind it fast to gridview, I have created a stored procedure.
It works fine for fetching and binding but for searching, it's hard to manage. Due to I don't have much time, i want to configure the stored procedure to 'if there's a searching' fetch the searched data only.
Here is my stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SetJobsGrid]
(#FileID varchar(50),
#PageIndex int = 1,
#PageSize int = 1,
#DynamicQuery NVARCHAR(MAX),
#SearchFlag bit,
#RecordCount int output)
AS
BEGIN
DECLARE #SearchQuerySQL as nvarchar(MAX)
SET NOCOUNT ON;
select ROW_NUMBER()OVER (
order by j.creationdate asc,
j.id desc
) AS RowNumber
, j.id as 'Serial #', j.jobname as 'File Record Name',
j.RecordNumber as 'Record Number',i.issuername as'Issuer Name',p.ProductName as 'Product Name',p.productNumber as 'Product Number',
(j.AccountNumber) as AccountNumber ,j.CardHolderName as 'Card Holder Name', j.CardholdersBranchName as 'Card Holder Branch Name',
j.ShipmentBranchName as 'Shipment Branch Name', j.EmbossingCardholderName as 'Embossing Card Holder Name', j.MaskedPAN as 'PAN',
j.creationdate as 'File Record Creation Date', j.status as 'File Record Status', j.chipdatastatuserror as 'ChipDataStatus Erro',
j.chipdatastatuserrormessage as 'Error Message', j.chipdatastatus as 'Data Prepared',j.isduplicaterecord as 'isduplicate',j.isduplicatefromsamefile as 'IsDuplicateFromSameFile',
j.validationerrors , j.isworkordercreatedForCard,j.isworkordercreatedForPin,j.isworkordercreatedForCarrier,j.PersoMachineId,j.PinMachineId,j.CarrierMachineId
INTO #Results
FROM jobs j join issuer i on j.issuerid=i.id join Product p on p.id=j.productid WHERE fileid = #FileID
IF(#SearchFlag = 1)
begin
select #SearchQuerySQL = 'SELECT ' + #RecordCount + ' = COUNT(*) FROM #Results ' + #DynamicQuery
EXEC(#SearchQuerySQL)
select #SearchQuerySQL = 'SELECT * FROM #Results ' + #DynamicQuery + ' and RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1'
EXEC(#SearchQuerySQL)
end
ELSE
begin
SELECT #RecordCount = COUNT(*)
FROM #Results
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
end
DROP TABLE #Results
END
When the SearchFlag is set to true from ASP.NET, I want to fetch only searched value. #DynamicQuery set from asp for example:
WHERE AccountNumber LIKE '%" + SearchValue + "%'"
or with many different case.
When I run this stored procedure as in the above, I get an exception:
Conversion failed when converting the varchar value 'select ' to data type int
Regards
To make the stored procedure use your dynamic where statement, it is better to use sp_executesql
example:
EXEC sp_executesql
N'select * from Employees where Id = #param1',
N'#param1 int'
,#param1 = 1
for more information about dynamic query refer to the following site
SQL Server Dynamic SQL
Too long for a comment but you need to understand why the error occurs to fix it. You have this snippet in your code
select #SearchQuerySQL = 'SELECT ' + #RecordCount +
First, if you intend to assign a scalar value (e.g., #SearchQuerySQL) then you should use SET, not SELECT. That's another topic you can research at your leisure. The assignment expression that follows is where you intended to do string concatenation. Unfortunately, the interpretation of the plus sign operator varies according to datatypes involved.
What happens when the database engine encounters an operator that involves 2 different datatypes. Like any other language, one (or both) must be converted to the same type in order to perform the expressed operation. How does the engine do that? There are rules for datatype precedence. In this case, your int parameter has higher precedence and so those strings in that expression are converted to int. That fails with the error that you encountered.
If you want to write dynamic sql, you need to have an advanced understanding of tsql. You should also consider searching the internet first before trying to reinvent the wheel. Maybe Aaron's article on dynamic pagination might help - but it might be a bit much for you at this point.
And while you're mucking about with things, add some comments within the procedure declaration about the usage you intend to support. No one should have to read your code to understand what it does and how it should be used.

SQL stored procedure sent data for "IN" condition

I have a stored procedure and it has where condition using 'IN' key word. So I want to send more than one value to stored procedure .
This is my stored procedure:
ALTER PROCEDURE [dbo].[xxx]
#COM_KEY varchar(max) = NULL
AS
BEGIN
SELECT
UserName, UserId
FROM
company
WHERE
(COM_KEY IN (#COM_KEY))
END
So I pass the value in here
string companyID = "";
for (int i = 0; i < lbCompanies.Items.Count; i++)
{
if (i == 0)
{
companyID += Convert.ToInt32(lbCompanies.Items[i].Value);
}
else
{
companyID += "," + Convert.ToInt32(lbCompanies.Items[i].Value);
}
}
DataSet ApproveList = DataRepository.TUsersProvider.xxx(companyID);
but there is an error
Conversion failed when converting the varchar value '3087,4058' to data type int
How can I solve it?
You cannot pass a comma-delimited string into #COM_KEY. You should pass an array.
One way to accomplish this task would be using Table-Valued Parameters.
Have a look at this article, under Solution #3: TSQL: Passing array/list/set to stored procedure (MS SQL Server).
In essence, you treat your series of ints as a table you apply JOIN upon, and not query it via WHERE ... IN () phrase.
You can create a UserDefinedFunction which parses the string and puts each integer extracted into a table to solve this issue
CREATE Function fnSplitter (#IDs Varchar(100) )
Returns #Tbl_IDs Table (ID Int) As
Begin
-- Append comma
Set #IDs = #IDs + ','
-- Indexes to keep the position of searching
Declare #Pos1 Int
Declare #pos2 Int
-- Start from first character
Set #Pos1=1
Set #Pos2=1
While #Pos1<Len(#IDs)
Begin
Set #Pos1 = CharIndex(',',#IDs,#Pos1)
Insert #Tbl_IDs Select Cast(Substring(#IDs,#Pos2,#Pos1-#Pos2) As Int)
-- Go to next non comma character
Set #Pos2=#Pos1+1
-- Search from the next charcater
Set #Pos1 = #Pos1+1
End
Return
End
Now alter your stored procedure like this
ALTER PROCEDURE [dbo].[xxx]
#COM_KEY varchar(max) = NULL
AS
BEGIN
SELECT UserName, UserId
From company
WHERE COM_KEY IN (Select ID From dbo.fnSplitter(#COM_KEY))
END
Check this link for detailed Implemetation
The most trivial solution to your problem is to vale SQL string(for dynamic Query) and then execute the statement
ALTER PROCEDURE [dbo].[xxx]
#COM_KEY varchar(max) = NULL
AS
BEGIN
DECLARE #SQL nvarchar(MAX)
SET #SQL=N' SELECT
UserName, UserId
FROM
company
WHERE
(COM_KEY IN ('+#COM_KEY+'))'
EXCE #SQL
END
Solution mentioned by #ssilas777 is the sophisticated version for the same.
Also note that using IN is not always advisable. I have run into
query time out errors
when IN has lot of IDs
Change your code from:
if (i == 0)
{
companyID += Convert.ToInt32(lbCompanies.Items[i].Value);
}
else
{
companyID += "," + Convert.ToInt32(lbCompanies.Items[i].Value);
}
to:
if (i == 0)
{
companyID +="'" + Convert.ToInt32(lbCompanies.Items[i].Value) + "'";
}
else
{
companyID += ",'" + Convert.ToInt32(lbCompanies.Items[i].Value) + "'";
}

Ordering SQL Server results by IN clause

I have a stored procedure which uses the IN clause. In my ASP.NET application, I have a multiline textbox that supplies values to the stored procedure. I want to be able to order by the values as they were entered in the textbox. I found out how to do this easily in mySQL (using FIELD function), but not a SQL Server equivalent.
So my query looks like:
Select * from myTable where item in #item
So I would be passing in values from my application like '113113','112112','114114' (in an arbitrary order). I want to order the results by that list.
Would a CASE statement be feasible? I wouldn't know how many items are coming in the textbox data.
How are you parameterising the IN clause?
As you are on SQL Server 2008 I would pass in a Table Valued Parameter with two columns item and sort_order and join on that instead. Then you can just add an ORDER BY sort_order onto the end.
From KM's comment above...
I know you didn't state it is comma seperated, but if it was a CSV or even if you have it space seperated you could do the following.
DECLARE #SomeTest varchar(100) --used to hold your values
SET #SomeTest = (SELECT '68,72,103') --just some test data
SELECT
LoginID --change to your column names
FROM
Login --change to your source table name
INNER JOIN
( SELECT
*
FROM fn_IntegerInList(#SomeTest)
) n
ON
n.InListID = Login.LoginID
ORDER BY
n.SortOrder
And then create fn_IntegerInList():
CREATE FUNCTION [dbo].[fn_IntegerInList] (#InListString ntext)
RETURNS #tblINList TABLE (InListID int, SortOrder int)
AS
BEGIN
declare #length int
declare #startpos int
declare #ctr int
declare #val nvarchar(50)
declare #subs nvarchar(50)
declare #sort int
set #sort=1
set #startpos = 1
set #ctr = 1
select #length = datalength(#InListString)
while (#ctr <= #length)
begin
select #val = substring(#InListString,#ctr,1)
if #val = N','
begin
select #subs = substring(#InListString,#startpos,#ctr-#startpos)
insert into #tblINList values (#subs, #sort)
set #startpos = #ctr+1
end
if #ctr = #length
begin
select #subs = substring(#InListString,#startpos,#ctr-#startpos)
insert into #tblINList values (#subs, #sort)
end
set #ctr = #ctr +1
set #sort = #sort + 1
end
RETURN
END
This way your function creates a table that holds a sort order namely, SortOrder and the ID or number you are passing in. You can of course modify this so that you are looking for space rather then , values. Otherwise Martin has the right idea in his answer. Please note in my example I am using one of my tables, so you will need to change the name Login to whatever you are dealing with.
the same way you concatenate ('113113','112112','114114') to pass to the sql sentence in the where clausule you can concatenate
order by
case item
when '113113' then 1
when '112112' then 2
when '114114' then 3
end
to pass to your order by clausule

Concatenating multiple rows fields into one column in T-SQL

I am writing an SQL query in which that I will need to perform a sub select on a table, which will usually return multiple rows. I need to be able to join together the results of a certain field from all the rows into one field to output. Is this possible, and how?
For example, if the SQL query returns
id | field
1 | test1
2 | test2
3 | test3
I need the outputted field to be "test1 test2 test3".
Thanks
Here's the for xml trick to do that:
SELECT field + ' ' as [text()]
FROM YourTable
FOR XML PATH ('')
This prints:
test1 test2 test3
It's typically used with an outer apply to execute it once for each row.
declare #sample table(id int, field varchar(20))
insert into #sample values(1,'test1')
insert into #sample values(2,'test2')
insert into #sample values(3,'test3')
declare #result varchar(max) set #result = ''
select #result = #result + ' '+field from #sample
select #result
A SQLCLR custom aggregator would be a an alternative (read better) solution
Try this:
SELECT RTRIM(field)
FROM (
SELECT field + ' ' field
FROM <YOUR_TABLE>
FOR XML PATH('')
) a
As an addition to the existing answers. Try including the COALESCE expression with column name your going to use. This avoids having null values in your concatenated string and avoid your list looking like this. Notice the redundant blank space.
field1 field2 field4 field
Further details can be found here.
GO
DECLARE #tableName VARCHAR(MAX)
SELECT #tableName = COALESCE(#tableName + ' ' ,'') + Name
FROM sys.tables
SELECT #tableName
GO
it is possible to do with a cursor.
declare #field nvarchar(max)
declare #concat nvarchar(max)
set #concat = ''
declare #cursor cursor
set #cursor = cursor for select field from table
open #cursor
fetch next from #cursor into #field
while ##fetch_status = 0
begin
set #concat = concat(#concat,#field)
fetch next from #cursor into #field
end
your exercise is to add space between the concatenated strings :-)

SQL use comma-separated values with IN clause

I am developing an ASP.NET application and passing a string value like "1,2,3,4" into a procedure to select those values which are IN (1,2,3,4) but its saying "Conversion failed when converting the varchar value '1,2,3,4' to data type int."
Here is the aspx code:
private void fillRoles()
{
/*Read in User Profile Data from database */
Database db = DatabaseFactory.CreateDatabase();
DbCommand cmd = db.GetStoredProcCommand("sp_getUserRoles");
db.AddInParameter(cmd, "#pGroupIDs", System.Data.DbType.String);
db.SetParameterValue(cmd, "#pGroupIDs", "1,2,3,4");
IDataReader reader = db.ExecuteReader(cmd);
DropDownListRole.DataTextField = "Group";
DropDownListRole.DataValueField = "ID";
while (reader.Read())
{
DropDownListRole.Items.Add((new ListItem(reader[1].ToString(), reader[0].ToString())));
}
reader.Close();
}
Here is my procedure:
CREATE Procedure [dbo].[sp_getUserRoles](#pGroupIDs varchar(50))
AS BEGIN
SELECT * FROM CheckList_Groups Where id in (#pGroupIDs)
END
Here is a workaround I found to do what you are trying to achieve
CREATE Procedure [dbo].[sp_getUserRoles](
#pGroupIDs varchar(50)
)
As
BEGIN
SELECT * FROM CheckList_Groups Where (',' + #pGroupIDs +',' LIKE '%,' + CONVERT(VARCHAR, id) + ',%')
End
This gets your comma delimited list and compares it to the id's(which are represented like so ',1,', ',2,' etc) in the table using LIKE
If you dont want to use dynamic sql, the best way ive found is to create a function which turns a delimited string into a table, something like this works for an Integer list:
CREATE FUNCTION [dbo].[StringToIntList]
(#str VARCHAR (MAX), #delimeter CHAR (1))
RETURNS
#result TABLE (
[ID] INT NULL)
AS
BEGIN
DECLARE #x XML
SET #x = '<t>' + REPLACE(#str, #delimeter, '</t><t>') + '</t>'
INSERT INTO #result
SELECT DISTINCT x.i.value('.', 'int') AS token
FROM #x.nodes('//t') x(i)
ORDER BY 1
RETURN
END
Then use that in your sp:
CREATE Procedure [dbo].[sp_getUserRoles](
#pGroupIDs varchar(50)
)
As
BEGIN
SELECT * FROM CheckList_Groups Where id in (
SELECT ID FROM dbo.StringToIntList(#pGroupIds,',')
)
End
Sure it can't do that,
The generated query would be sth like this
SELECT * FROM CheckList_Groups Where id in ('1,2,3,4')
and sure it can't be executed.
you can build the query in your stored procedure then execute it with exec
'SELECT * FROM CheckList_Groups Where id in (' + #pGroupIDs + ')'
or
SELECT * FROM CheckList_Groups Where charindex(','+id+',' , #pGroupIDs)>0
but you first must add the ',' to start and end of your parameter in your c# code
It is not possible to put those values (the comma separated string) in a parameter-value.
What you'll have to do, is to create the SQL Statement in your stored procedure dynamically, by string concatenation. You'll have to execute it with the sp_executesql stored procedure then.
CREATE PROCEDURE [dbo].[getUserRoles]( #groupIds NVARCHAR(50) )
AS BEGIN
DECLARE #statement NVARCHAR(255)
SELECT #statement = N'SELECT * FROM CheckList_Groups Where id in ( ' + #pGroupIDs + N')'
execute sp_executesql #statement
END
Also, not that I named the SP getUserRoles instead of sp_getUserRoles.
The reason is very simple: when you execute a stored procedure whose name starts with sp_, then SQL Server will first query the master database to find that stored procedure, which causes a performance hit offcourse.
The way you are trying to do this is slightly wrong. You will need to use EXECUTE in order to achieve this.
CREATE PROCEDURE [dbo].[sp_getUserRoles](#pGroupIDs nvarchar(50))
As
BEGIN
EXECUTE (N'SELECT * FROM CheckList_Groups Where id in (' + #pGroupIDs + ')';
END
DECLARE #TagId NVARCHAR(100) = '43,81'
SELECT * FROM TABLE WHERE TagId IN (SELECT TRIM(VALUE) FROM STRING_SPLIT( #TagId , ',' ) )
USE STRING_SPLIT FUNCTION FOR THIS
You need to use SP_executesql to achieve this functionllity
CREATE Procedure [dbo].[sp_getUserRoles](
#pGroupIDs varchar(50)
)
As
BEGIN
EXECUTE sp_executesql
N'SELECT * FROM CheckList_Groups Where id in (#pGroupIDs)',
N'#level varchar(50)',
#level = #pGroupIDs;
End
The IN clause can't take a bound parameter like that. What it's being given when the query is actually created is SELECT * FROM CheckList_Groups Where id in ('1,2,3,4'). Essentially the IN clause is being passed a single string.
First create function -
Just run this code
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CSVToTable] (#InStr VARCHAR(MAX))
RETURNS #TempTab TABLE
(id int not null)
AS
BEGIN
;-- Ensure input ends with comma
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE VARCHAR(1000)
WHILE PATINDEX('%,%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%,%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #TempTab(id) VALUES (#VALUE)
END
RETURN
END
GO
Then -
Use function in bracket with select statment -
DECLARE #LIST VARCHAR(200)
SET #LIST = '1,3'
SELECT Id, Descr FROM CSVDemo WHERE Id IN (SELECT * FROM dbo.CSVToTable(#LIST))

Resources