I have this stored procedure:
ALTER PROCEDURE [dbo].[uspPages_HotelPrices_Lookup_Select]
#HotelCode nvarchar(100)
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM tPages_HotelPrices_Lookup
WHERE HotelCode IN (SELECT * FROM DBO.ufSplit(#HotelCode, ','))
END
DBO.ufsplit splits a comma delimited string and returns a table of which each row containing each of the comma separated values.
I am passing a string to this stored procedure with the code below:
static void Main(string[] args)
{
HotelCodesTableAdapter hcTa = new HotelCodesTableAdapter();
DestinationMarketingEntity.HotelCodesDataTable hotelCodesDt = hcTa.GetData();
string hotelCodesString = "";
//Comma separating hotel codes and putting each word in '' to be passed to sql sproc as a list
for (int i = 0; i < hotelCodesDt.Count; i++)
{
hotelCodesString += hotelCodesDt.Rows[i].ItemArray.GetValue(0).ToString() + ",";
}
hotelCodesString = hotelCodesString.TrimEnd(',');
HiltonEEHotelPricesTableAdapter hEETa = new HiltonEEHotelPricesTableAdapter();
WorldWideFeedEntity.HiltonEEHotelPricesDataTable hEEDt= hEETa.GetData(hotelCodesString);
}
The last line is where the stored procedure is being called.
Essentially hotelCodesString will be similar to "1,2,3" but this is returning nothing form this stored procedure. But if I run the below:
select *
from tPages_HotelPrices_Lookup
where HotelCode IN
(
SELECT *
FROM DBO.ufSplit('1,2,3',',')
);
It gets back everything that I want. Am I missing something here? Why will it not return anything when passing from values with c#?
Don't do the split at all. Create a table valued parameter and pass this to your stored procedure. Then change your stored procedure to join to the table valued parameter.
Your sproc will end up looking like this:
CREATE PROCEDURE [dbo].[uspPages_HotelPrices_Lookup_Select]
#HotelCodes dbo.MyCodesTable READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM tPages_HotelPrices_Lookup a
INNER JOIN #HotelCodes b ON (a.ID = b.ID)
END
There are lots of good examples of using table values parameters on SO and the internet. A good method to get used to.
You can try doing the split in C# instead of at the db level.
string[] m_separators = new string[] { "," };
string[] m_stringarray = somestring.Split(m_separators, StringSplitOptions.RemoveEmptyEntries);
Or follow the examples on SO regarding passing an array to a stored proc. It is probably what you want to do anyway.
Related
I have an ArrayList (in C#) that contains some int numbers (those are IDs in a table), I want to select some data for each number(s) in this ArrayList and return a table variable or a #temporary table :)
I found a solution for passing this ArrayList as an user-defined table type to my stored procedure:
CREATE TYPE [dbo].[integer_list_tbltype] AS TABLE(
[n] [int] NOT NULL,
PRIMARY KEY CLUSTERED ([n] ASC)
WITH (IGNORE_DUP_KEY = OFF)
)
GO
CREATE PROCEDURE [dbo].[Sp_apr_get_apraisors]
(#listNumbers INTEGER_LIST_TBLTYPE readonly)
AS
....
but I didn't find an efficient way to read this array as easily as in C# :(
Is there any way to write a loop for each of these numbers and save data in a temp table and finally return it to C#??
SQL is set based, so your best option is to write a single select statement that would join your input table to the tables containing the data you would like to look up. The select statement would be the result set to be sent back to your application. Then if you want to use straight ADO.Net, you can use the SqlDataReader class to read back into C#, or you could use an ORM like Linq2Sql, Entity Framework, or NHibernate. By the way, if you must do a loop in Sql, please avoid cursors. They are slow and unnecessarily complicated both to manage and to develop. Use a while loop instead.
I would suggest you change the procedure parameter to varchar(n) and then send in those values as comma-delimited string.
DECLARE #IDs VARCHAR(MAX)
SELECT #IDs = '1,2,3'
DECLARE #ID INT
WHILE LEN(#IDs) > 0
BEGIN
SELECT #ID = CONVERT(INT, LEFT(#IDs, CHARINDEX(',', #IDs + ',') -1)
-- Do something with the ID here...
SELECT #IDs = STUFF(#IDs, 1, CHARINDEX(',', #IDs + ','), '')
END
mmmmm :), after 24h (!) search aorund the www , i found my problem Answer, #Toni's answer helped me on this :) Tanx #Toni :*
1) first define stored procedure
CREATE PROCEDURE [spName]( #list_entry VARCHAR(max)=NULL)
AS
BEGIN
SELECT [Column1,column2,...]
FROM [TABLE(s)]
WHERE ( #list_entry IS NULL
OR Column1 IN (SELECT value FROM Fn_split(#list_person, ',')) )
END
2) write a function to split items (comma delimited)
CREATE FUNCTION [dbo].[fn_Split](#text varchar(8000), #delimiter varchar(20) = ' ')
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value varchar(8000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
GO
3) pass my array as a comma-delimited string from .NET
//defin sample array list or your data
ArrayList array = new ArrayList();
//fill array with some data
for (int i = 1000; i<1010;i++)
array.Add(i);
//define connection and command
using(SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connString"].ConnectionString))
{
connection.Open();
SqlCommand cmd = new SqlCommand("",connection);
cmd.Parameters.AddWithValue("#list_entry", SqlDbType.varchar,8000,Get_comma_delimited_string(array));
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "yourSpName";
cmd.ExecuteNonQuery();
}
/// <summary>
/// Resturns a comma delimited string (sepearte each item in list with ',' )
/// </summary>
public string Get_comma_delimited_string(ArrayList arrayList)
{
string result = string.Empty;
foreach (object item in arrayList)
result += item.ToString() + ",";
return result.Remove(result.Length - 1);
}
I have stored procedure in which I'm executing a query. When I execute the query it returns multiple rows instead of one. But when I execute the same query without the store procedure it returns single row. Since I'm new to MySQL I'm facing issues in declaring variables inside stored procedures and using them.
I'm calling the stored procedure from C# code.
Stored procedure code(returns multiple values):
CREATE PROCEDURE `USP_UPDATE_BLACKLIST_RECORD_DETAIL`(
FILE_NAME varchar(100)
)
BEGIN
SET #FILE_NAME=null; SET #FILE_NAME = FILE_NAME;
SET #UPDATE_CODE = null; SET #ACTUAL_RECORD_COUNT = null;
/*Get the update code of the extracted file*/
SET #UPDATE_CODE = (SELECT count(UPDATE_CODE) FROM TX_BLACKLIST_FILE_DETAIL WHERE FILE_NAME = #FILE_NAME );
select #UPDATE_CODE;
END
Query without stored procedure(returns single value):
set #FILE_NAME=null;
set #FILE_NAME='TPCCH_ULEPL_20141112_164635.BLT';
set #UPDATE_CODE=null;
set #ACTUAL_RECORD_COUNT=null;
/*Get the update code of the extracted file*/
SET #UPDATE_CODE = (SELECT count(UPDATE_CODE) FROM TX_BLACKLIST_FILE_DETAIL WHERE FILE_NAME = #FILE_NAME );
select #UPDATE_CODE;
end
I have created a stored procedure for SQL Server 2014.
There are two parameters: Name which is a user name and Hash which is password md5 hash. I check in the database if the md5 hashes are equal (first hash is from the program and the second one is already stored in the database).
If I just run a query (not a stored procedure) in the database (or in program using commandType.Text) - everything works and the user is being selected, but when I run the exact thing but using stored procedures, the SqlReader in C# has no elements returned, which most likely means that the conditions during those variable comparison were not met.
Maybe I am doing something wrong?
I also have about 10 other stored procedures for reading or/and writing to the database, everything works except this one.
Here is the procedure:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId, Employee.name, Employee.surname
FROM Orders
LEFT JOIN Employee ON Orders.orderId = Employee.id
WHERE batchName = '#Name' AND productCode = '#Hash'
END
GO
Code part:
public Boolean VerifyPassword(string name, string password)
{
var paramsList = new List<SqlParameter> { new SqlParameter("#Name", name), new SqlParameter("#Hash", GetMd5Hash(password)) };
const string ProcedureName = "GetHash";
var ActiveUser = new DBController().GetFromDatabase(ProcedureName, "Login", "EJL15_DB", paramsList).ToList();
return ActiveUser.Count > 0;
}
And from Database Controller
private void SetCommandProperties(string procedureName, IEnumerable<SqlParameter> paramsList)
{
this.sqlCommand.CommandText = procedureName;
this.sqlCommand.CommandType = CommandType.StoredProcedure;
foreach (var curParam in paramsList)
this.sqlCommand.Parameters.Add(curParam);
this.sqlCommand.CommandTimeout = 15;
}
You don't need to quote the parameters in the stored procedure. Do this instead:
CREATE PROCEDURE GetHash
#Name nvarchar(50),
#Hash nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
SELECT Orders.orderId,
Employee.name,
Employee.surname
FROM Orders
LEFT JOIN Employee
ON Orders.orderId=Employee.id
WHERE batchName = #Name
AND productCode = #Hash
END
I just wonder, obviously your #Hash parameter passed to the stored
procedure is a user's password. But for some reason your WHERE clause
in the procedure goes like that:
"WHERE batchName='#Name' AND productCode='#Hash'"
Is there a chance your condition is incorrect? I guess it should be something like: Employee.password = #Hash
You should not put '' around your variables. Otherwise your comparison is totally wrong.
i'm trying to create a dynamic sql statement either thru sql code or c# code. For example, I'd like to generate a result set based on a string containing a table name. Currently, I am doing this thru c# code.
My current issue is I'd like to generate a search similar to following
select * from customers
where ContactName+City like '%Berlin%'
so I'm thinking given a table name as a string parameter I need to somehow produce a string variable 'ContactName+City+etc' to build part of the search
I'm open to any other ideas as well.
var sql = string.Format(#"
select * from {0}
where {1} like '%criteria%'"
, variable_table
, "column1+column2+columnX"); //need function here to produce this string based on variable table?
Basically, how would I create a string that concatenates a variable number of columns together ('ContactName+City+etc') based on a variable_table?
Why not simply this:
select * from variable_table_name
WHERE column1+column2+columnX like '%criteria%'
You can do this purely in SQL as well. But as you have already done this in C# and you need only to get the list of columns based on the table name, try this.
Create a SQL udf as below.
CREATE FUNCTION funcReturnAllColumns
(
#tableName VARCHAR(50)
)
RETURNS VARCHAR(500)
BEGIN
DECLARE #ID INT
DECLARE #ALLColumns VARCHAR(500)
SET #ALLColumns = ''
SELECT #ID = id
FROM sys.sysobjects
WHERE name = #tableName
SELECT #ALLColumns = #ALLColumns + '+' + name
FROM sys.syscolumns
WHERE id = #ID
RETURN SUBSTRING(#ALLColumns,2,LEN(#ALLColumns))
END
SELECT dbo.funcReturnAllColumns('table_name')
OUTPUT:
Column1 + Column2 + ..... + ColumN
You may have to adjust varchar limits, validations as required.
Scenario
I have a stored procedure that takes a single parameter. I want to update this stored procedure to take a VARIABLE NUMBER OF PARAMETERS - a number that I will never know.
I currently use SQLConnections through a C# interface in order to pass in a single parameter to the stored procedure and return a result.
The SQL Part
Lets say that I have a stored procedure that returns a list of results based on a single input parameter "#ccy" - (Currency). Now lets say that I want to update this stored procedure to take a list of Currencies instead of a single one, but that this number will be variable depending on the situation.
The SQL Code
ALTER PROCEDURE [dbo].[SEL_BootStrapperInstRICs]
(
#ccy varchar(10)
)
AS
SELECT DISTINCT i.CCY, i.Instrument, i.Tenor, r.RIC, r.[Server], r.RIType
FROM MDR.dbo.tblBootStrapperInstruments as i, MDR.dbo.tblBootStrapperRICs as r
WHERE i.Instrument = r.MurexInstrument
AND
i.Tenor = r.Tenor
AND i.CCY = r.CCY
AND i.CCY = #ccy
AND r.RIType NOT LIKE '%forward%'
The C# Part
This particular stored procedure is called from a C# WinForms application that uses the "SqlCommand.Parameters.AddWithValue()" method. As mentioned earlier this method currently passes in a single Currency as the parameter to the stored procedure and returns the result as a DataSet.
public DataSet GetBootStrapperInstRICsDS(List<string> ccys)
{
DataSet ds;
SqlConnection dbConn = null;
SqlCommand dbCmd = new SqlCommand();
try
{
dbConn = GetSQLConnection();
dbCmd = GetSqlCommand();
dbCmd.CommandType = CommandType.StoredProcedure;
dbCmd.CommandText = Utils.Instance.GetSetting ("SELBootStrapInsRics", "default");
foreach(string ccy in ccys)
dbCmd.Parameters.AddWithValue("#ccy", ccy);
dbCmd.CommandTimeout = 600;
dbCmd.Connection = dbConn;
SqlDataAdapter adapter = new SqlDataAdapter(dbCmd);
ds = new DataSet();
adapter.Fill(ds, "tblBootStrapperInstRICs");
dbCmd.Connection.Open();
return ds;
}
catch (Exception ex)
{
ApplicationException aex = new ApplicationException ("GetBootStrapperInstRICsDS", ex);
aex.Source = "Dal.GetBootStrapperInstRICsDS " + ex.Message;
MainForm.job.Log(aex.Source, Job.MessageType.Error);
Job.incurredErrors = true;
throw aex;
}
finally
{
if (dbCmd != null)
dbCmd.Dispose();
if (dbConn != null)
{
dbConn.Close();
dbConn.Dispose();
}
}
}
The Question
On the C# side I think my best option is to use a "foreach/for loop" in order to iterate through a list of parameters and dynamically add a new one to the SPROC. (I have already made this update in the C# code above).
HOWEVER - Is there some way that I can do this in the SQL Stored Procedure too? My thoughts are split with two potential options - Either create 20 or more parameters in the SPROC (each with the same name but with an incrementing number at the end e.g. - #ccy1,#ccy2 etc.) and use "for(int i=0;i
for(int i=0;i<NumberOfCurrenciesToAdd;i++)
dbCmd.Parameters.AddWithValue("#ccy"+i, currencyArray[i]);
Or the other option is to do something completely different and less rubbish and hack-esque. Help greatly appreciated.
EDIT - SQL Server 2005
EDIT2 - Must Use SPROCS - Company Specification Requirement.
You never specified SQL Server version, but for 2008 there are Table-Valued Parameters, which may help you:
Table-valued parameters are a new parameter type in SQL Server 2008. Table-valued parameters are declared by using user-defined table types. You can use table-valued parameters to send multiple rows of data to a Transact-SQL statement or a routine, such as a stored procedure or function, without creating a temporary table or many parameters.
I worked for a company that had to do this. It is much easier to just pass an nvarchar that is really a list that is comma delimited and then parse it when you get into the stored proc and insert the values into a temp table. The other option would be to have an xml parameter in your proc. That should also work. This is all for SQL 2005. 2008 does give you the table variable and that would be your best option.
I would try to stay away from dynamically changing your stored proc because I think that would be hard to maintain. At any given time if you try to look at the proc it could be different. Also, what happens when 2 people are trying to use your site and hit that proc at the same moment? One person's session will be modifying the procedure and the others will try to do it. This could cause a lock on the stored proc or it could cause other issues. Regardless it would be pretty inefficient.
Here is another option - though I think Anton's answer is better. You can pass in a csv string as a single parameter. Use a user-defined function to convert the csv string into a table of values, which you can join in your query. There are several csv parsing functions listed on SO and other places (though, sorry, I can't come up with a link right now).
edit: here is another option. Pass in the same csv string, then generate the sql query as a string in the procedure, and execute the string. Use the csv in an 'in' clause :
where i.ccy in (1,2,3,4)
I would not try to change the stored procedure, but (since you are on SQL Server 2005 and don't have table variable parameters) just pass in a comma separated list of values and let the procedure split them apart. You can change your C# loop to just build a CSV string and once you create a SQL split procedure, use it like:
SELECT
*
FROM YourTable y
INNER JOIN dbo.yourSplitFunction(#Parameter) s ON y.ID=s.Value
I prefer the number table approach to split a string in TSQL
For this method to work, you need to do this one time table setup:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(#SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = #SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
);
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')
OUTPUT:
ListValue
-----------------------
1
2
3
4
5
6777
(6 row(s) affected)
Your can pass in a CSV string into a procedure and process only rows for the given IDs:
SELECT
y.*
FROM YourTable y
INNER JOIN dbo.FN_ListToTable(',',#GivenCSV) s ON y.ID=s.ListValue
I use this function to split CSV text into a table of numbers, it has great performance due to various optimizations (like returning a table with a primary key which greatly influence the query optimizer to produce good query plans ever for extremely large data sets).
Also it's not limited to 4000 characters, so you can pass in very large strings.
CREATE Function [dbo].[TextSplitToInt](#list text,
#delim char(1) = N',')
RETURNS #T TABLE (ID_T int primary key)
BEGIN
DECLARE #slices TABLE (slice nvarchar(4000) NOT NULL)
DECLARE #slice nvarchar(4000),
#textpos int,
#maxlen int,
#stoppos int
SELECT #textpos = 1, #maxlen = 4000 - 2
WHILE datalength(#list) / 2 - (#textpos - 1) >= #maxlen
BEGIN
SELECT #slice = substring(#list, #textpos, #maxlen)
SELECT #stoppos = #maxlen - charindex(#delim, reverse(#slice))
INSERT #slices (slice) VALUES (#delim + left(#slice, #stoppos) + #delim)
SELECT #textpos = #textpos - 1 + #stoppos + 2 -- On the other side of the comma.
END
INSERT #slices (slice)
VALUES (#delim + substring(#list, #textpos, #maxlen) + #delim)
INSERT #T (ID_T)
SELECT distinct Cast(str as int)
FROM (SELECT str = ltrim(rtrim(substring(s.slice, N.Number + 1,
charindex(#delim, s.slice, N.Number + 1) - N.Number - 1)))
FROM Numbers N
JOIN #slices s ON N.Number <= len(s.slice) - 1
AND substring(s.slice, N.Number, 1) = #delim) AS x
RETURN
END