OUTPUT Clause value with another Data in SQL Sever : C# - c#

I am using below query to get OUTPUT Clause value with another data.
#PageIndex bigint=1,
#PageSize bigint=5,
#RecordCount bigint=0 OUTPUT,
Select ROW_NUMBER() OVER (ORDER BY EmpId ASC) AS RowNumber,
EmpId,
(Salutation + ' ' + FirstName + ' ' + LastName) as Name,
Specialties,
EmpPhoto
INTO #Results
from tblEmployee
Where EmpType='Doctor'
Select #RecordCount = COUNT(*) from #Results
Select *
from #Results
Where RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1
AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
Now, I want #RecordCount value with another data of Select query. How to get it in C# ? I am using 3-tier architecture so I have created method with DataTable datatype. So, what changes I will need to do ?

// 1. Create the output parameter
var p = new SqlParameter("RecordCount", 0) { Direction = ParameterDirection.Output };
// 2. Make the SQL call
using (var con = new SqlConnection(connString))
using (var cmd = con.CreateCommand())
{
cmd.Parameters.Add(p);
using (var reader = cmd.ExecuteReader())
{
// Retrieve reader values from query
}
}
// 3. Retrieve the value of the output parameter
var recordCount = (long)p;
Another option would be to refactor your SQL and do
RETURN ##ROWCOUNT
Which can be retrieved in C# with the following (note that you are limited to an int type for the return value)
cmd.Parameters["#RETURN_VALUE"]
And as an aside, avoid using TempDb like that. It's unnecessary. And a better way to page is like this using built-in SQL paging
SELECT ...
FROM ...
WHERE ...
ORDER BY ...
OFFSET (#PageIndex - 1) * #PageSize ROWS
FETCH NEXT #PageSize ROWS ONLY

Since you haven't show your c# code - it is hard to say what changes should you made.
But basically - you can't use output keyword in a script. It should be output parameter of stored procedure - so you have to wrap your logic into stored procedure and call it from c# code.
In this case you should add parameter to your SqlCommand with ParameterDirection.Output. After executing command you can get returned value of your parameter from SqlParameter.Value property.

Related

How to increase the Performance of the Select query fetching Huge data from database

i want fetch a huge data on site (about 19000 record) and show that on datalist control.
my data list have a paging feature and on the first time i show only 6 record on datalist.
then he user can go to page 2 and 3 and ...
fetch all record to data table get more time.
Please help me in details what should i use in sql server.
private void FetchDataToDataList()
{
DataTable dt = new DataTable();
if (Cache["DataTable-cach"] == null)
{
String strConnString = System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
SqlConnection con = new SqlConnection(strConnString);
SqlCommand cmd = new SqlCommand("SELECT dbo.table_name.field_name, FROM dbo.table_name ", con);
con.Open();
dt = new DataTable("T");
string startime = System.DateTime.Now.ToLongTimeString();
dt.Load(cmd.ExecuteReader());
string endtime = System.DateTime.Now.ToLongTimeString();
Cache.Insert("DataTable-cach", dt, null, DateTime.Now.AddMinutes(5), System.Web.Caching.Cache.NoSlidingExpiration);
con.Close();
}
else
{
dt = ((DataTable)Cache["DataTable-cach"]);
}
// pagedDS is data list control
PagedDataSource pagedDS = new PagedDataSource();
pagedDS.DataSource = dt.DefaultView;
pagedDS.AllowPaging = true;
pagedDS.PageSize = 6;
pagedDS.CurrentPageIndex = CurrentPage;
dlPaging.DataSource = pagedDS;
dlPaging.DataBind();
lblCurrentPage.Text = pagedDS.PageCount.ToString() +" صفحه " + (CurrentPage + 1).ToString()+ " از " ;
// Disable Prev or Next buttons if necessary
cmdPrev.Enabled = !pagedDS.IsFirstPage;
cmdNext.Enabled = !pagedDS.IsLastPage;
}
Depending on the SQL Server version, you should request only as many records from the database as you need.
In SQL Server 2012 you can use the OFFSET and FETCH NEXT keywords. In earlier versions, use ROW_NUMBER.
Laoding 19,000 records at a time is not a good option beacuse it would take time to build all the html at the same time.
And most importantly you are not going to display all the records at a time.
So, you need to load only those records that are currently being displayed to user.
This tremendously boosts your performance / page load speed.
Write your stored procedure in such a way that you are fetching only required records and not records all at a time.
Example:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
CREATE PROCEDURE GetDataPageWise // Name of the stored procedure
#PageIndex INT = 1
,#PageSize INT = 10
,#RecordCount INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT ROW_NUMBER() OVER
(
ORDER BY [ColumnName] ASC
)AS RowNumber
,[ColumnName]
,[ColumnName]
,[ColumnName]
INTO #Results // #Results is the temporary table that we are creating
FROM [TableName]
SELECT #RecordCount = COUNT(*)
FROM #Results
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results // Dropping the temporary table results as it is not required furthur
END
GO
Hope this helps..
You can try paging like below
http://www.codeproject.com/Articles/192408/SQL-Pager-Control-for-GridView-DataList-Repeater-D

is using delimited parameter in stored procedure dangerous?

My C# side is like :
if(Request.QueryString["ValuesFromUser"]!=null)
{
ValuesFromUser_ = Request["ValuesFromUser"];
}
DataTable dtle = new DataTable();
SqlDataAdapter sqda;
sqda = new SqlDataAdapter("Checkforuserinput", Connection);
SqlParameter SQP = sqda.SelectCommand.Parameters.Add("#arg", SqlDbType.VarChar);
SQP.Direction = ParameterDirection.Input;
SQP.Value = "ValuesFromUser_";
sqda.Fill(dtle );
User will pass few arguments like "user1,user2,user3"
In my sql side :
Create PROC [dbo].[Checkforuserinput] #arg VARCHAR(50)= 'All'
As
Select *
from UserData
where User in (SELECT *
FROM SplitDelimiterString(#Arg, ','))
And SplitDelimiterString Function is like :
ALTER FUNCTION [dbo].[SplitDelimiterString] (#StringWithDelimiter VARCHAR(8000), #Delimiter VARCHAR(8))
RETURNS #ItemTable TABLE (Item VARCHAR(8000))
AS
BEGIN
DECLARE #StartingPosition INT;
DECLARE #ItemInString VARCHAR(8000);
SELECT #StartingPosition = 1;
--Return if string is null or empty
IF LEN(#StringWithDelimiter) = 0 OR #StringWithDelimiter IS NULL RETURN;
WHILE #StartingPosition > 0
BEGIN
--Get starting index of delimiter .. If string
--doesn't contain any delimiter than it will returl 0
SET #StartingPosition = CHARINDEX(#Delimiter,#StringWithDelimiter);
--Get item from string
IF #StartingPosition > 0
SET #ItemInString = SUBSTRING(#StringWithDelimiter,0,#StartingPosition)
ELSE
SET #ItemInString = #StringWithDelimiter;
--If item isn't empty than add to return table
IF( LEN(#ItemInString) > 0)
INSERT INTO #ItemTable(Item) VALUES (#ItemInString);
--Remove inserted item from string
SET #StringWithDelimiter = SUBSTRING(#StringWithDelimiter,#StartingPosition +
LEN(#Delimiter),LEN(#StringWithDelimiter) - #StartingPosition)
--Break loop if string is empty
IF LEN(#StringWithDelimiter) = 0 BREAK;
END
RETURN
END
Is this code safe or in risk for SQL injection?
Or should i use this :
Create PROC [dbo].[Checkforuserinput] #arg VARCHAR(50)= 'All'
As
declare #query nvarchar(max)
set #query = 'Select * from UserData where User in ('+#Arg+')'
EXECUTE sp_executesql #query
I don't see any forseeable problems with the split function, but passing a table valued parameter would be more efficient.
SQL
CREATE TYPE ArrayOfString AS TABLE
(
Item VARCHAR(50)
);
GO
CREATE PROC [dbo].[Checkforuserinput] #arg ArrayOfString READONLY
AS
SELECT *
FROM UserData
WHERE User IN (SELECT Item FROM #arg)
GO
C#
Write a helper function to write any IEnumerable(string) into a DataTable that the SQL Server will understand.
public static class SqlExtensions
{
public static DataTable ToSqlArray(this IEnumerable<string> collection)
{
var dt = new DataTable("ArrayOfString");
dt.Columns.Add(new DataColumn("Item", typeof(string)));
foreach(var item in collection)
{
var row = dt.NewRow();
row[0] = item;
dt.Rows.Add(row);
}
return dt;
}
}
Usage:
if(Request.QueryString["ValuesFromUser"]!=null)
{
ValuesFromUser_ = Request["ValuesFromUser"];
}
var values = ValuesFromUser_.Split(",");
using(var adapter = new SqlDataAdapter("Checkforuserinput", Connection))
{
adapter.SelectCommand.CommandType = CommandType.StoredProcedure;
adapter.SelectCommand.Parameters.AddWithValue("#arg", values.ToSqlArray());
adapter.Fill(dtle);
}
Depending on your version of SQL Server, you may want to consider passing a table-valued parameter to your stored procedure.
Table-valued parameters provide an easy way to marshal multiple rows of data from a client application to SQL Server without requiring multiple round trips or special server-side logic for processing the data. You can use table-valued parameters to encapsulate rows of data in a client application and send the data to the server in a single parameterized command. The SqlParameter is populated by using the AddWithValue method and the SqlDbType is set to Structured.
The following link provides a good overview:
http://msdn.microsoft.com/en-us/library/bb675163(v=vs.110).aspx

Execute stored procedure with array-like string separated by comma [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Help with a sql search query using a comma delimitted parameter
I want to write a stored procedure that performs a select on a table and need one input variable of type varchar(max).
I'd like to send a bunch of values separated by , as the input parameter, e.g.
'Jack','Jane','Joe'
and then get the rows that contain one of these names.
In SQL the code would be
Select * from Personnel where Name in ('Jack','Joe','Jane');
Now I want to have a variable in my C# app, say strNames and fill it like
string strNames = "'Jack','Joe','Jane'";
and send this variable to the SP and execute it. Something like
Select * from Personnel where Name in (''Jack','Joe','Jane'') -- this is wrong
But how can I tell SQL Server to run such command?
I need to make this happen and I know it's possible, please give me the clue.
First of all, the single names don't need to be quoted when you pass them to the stored procedure.
using (SqlCommand cmd = new SqlCommand("MyStoredProc", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#longFilter", "Jack,Jill,Joe");
using (SqlDataReader reader = cmd.ExecuteReader())
{
...
}
}
Then, in the stored procedure, you can use simple text functions and a temporary table as follows to split up the string at the commas and an an entry to the temporary table for each part of the string:
DECLARE #temp AS TABLE (Name NVARCHAR(255))
IF ISNULL(#longFilter, '') <> ''
BEGIN
DECLARE #s NVARCHAR(max)
WHILE LEN(#longFilter) > 0
BEGIN
IF CHARINDEX(',', #longFilter) > 0
BEGIN
SET #s = LTRIM(RTRIM(SUBSTRING(#longFilter, 1, CHARINDEX(',', #longFilter) - 1)))
SET #longFilter = SUBSTRING(#longFilter, CHARINDEX(',', #longFilter) + 1, LEN(#longFilter))
END ELSE
BEGIN
SET #s = LTRIM(RTRIM(#longFilter))
SET #longFilter= ''
END
-- This was missing until 20140522
INSERT INTO #temp (Name) VALUES (#s)
END
END
Later use the following SELECT to get a list of all people the name of which is in #temp or all of them if #temp doesn't contain any rows (unfiltered result):
SELECT * FROM Personnel WHERE Name IN (SELECT Name FROM #temp) OR (SELECT COUNT(*) FROM #temp) = 0
You could use Table Valued Parameters.
Basically, you could insert a list of values as a parameter in the procedure, and use them as a table, something along the lines of
Select * from Personnel
where Name in (select name from #NamesTable).
Now, the specifics
To use table valued parameters, the type of the parameter must be predefined in sql server, using
create type NamesTable as table (Name varchar(50))
You can then use the defined type as a parameter in the procedure
create procedure getPersonnelList
#NamesTable NamesTable readonly
as
begin
select * from personnel
where Name in (select Name from #NamesTable)
end
You can see that in action, in this SQL Fiddle
On the C# side of things you need to create the parameter. If you have the names in a collection, and build the string, you can just use that to generate the parameter, and if they are a comma-separated string, a quick string.Split could take care of that. Since I do not know your specifics, I'll assume you have a List<string> called names. You'll need to convert that to a table valued parameter to be sent to the procedure, using something like:
DataTable tvparameter = new DataTable();
tvparameter.Columns.Add("Name", typeof(string));
foreach (string name in names)
{
tvparameter.Rows.Add(name);
}
You can find more info on how to generate a TVP in C# code in the SO Question..
Now you just need to send that parameter to the procedure, and that's that. Here is a complete console program that executes the procedure and outputs the results.
List<string> names = new List<string> { "Joe", "Jane", "Jack" };
using (SqlConnection cnn = new SqlConnection("..."))
{
cnn.Open();
using (SqlCommand cmd = new SqlCommand("getPersonnelList", cnn))
{
cmd.CommandType = CommandType.StoredProcedure;
DataTable tvparameter = new DataTable();
tvparameter.Columns.Add("Name", typeof(string));
foreach (string name in names)
{
tvparameter.Rows.Add(name);
}
cmd.Parameters.AddWithValue("#NamesTable", tvparameter);
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
Console.WriteLine("{0} - {1}", dr["ID"], dr["Name"]);
}
}
}
}
I guess you need Split Function in Sql Server to break Comma-Separated Strings into Table. Please refer these links.
Split Function in Sql Server to break Comma-Separated Strings into Table
SQL User Defined Function to Parse a Delimited String
You can select the data from table using
Select * from
Personnel where
Name in (select items from dbo.Split ('Jack,Joe,Jane',','))
You could simply check if Name is contained in the string. Note the commas at the start of the end to ensure you match the full name
string strNames = ",Jack,Joe,Jane,";
The the SQL becomes
select * from Personnel where PATINDEX('%,' + Name + ',%', #strNames) > 0
See http://www.sqlfiddle.com/#!3/8ee5a/1

Return rows affected from a Stored Procedure on each INSERT to display in ASP.NET page

I have a stored procedure that contains like 10 different INSERTS, is it possible to return the COUNT of the rows affected on each INSERT to ASP.NET c# page so i can display Stored Procedure process for the client viewing that ASP.NET page?
You need to use following command in the start of your stored procedure:
SET NOCOUNT OFF
In this case SQL server will send text messages ("X rows affected" ) to client in real time after each INSERT/UPDATE. So all you need is to read these messages in your software.
Here is my answer how to do it in Delphi for BACKUP MS SQL command. Sorry I've not enough knowledge in C# but I guess you can do it in C# with SqlCommand class.
On the server side send the message to the client using RAISERROR function with severity 10 (severity higher than 10 causes exception that breaks procedure execution, i.e. transfers execution to the CATCH block, if there is one). In the following example I haven't added error number, so the default error number of 50000 will be used by RAISERROR function. Here is the example:
DECLARE #count INT = 0
DECLARE #infoMessage VARCHAR(1000) = ''
-- INSERT
SET #count = ##ROWCOUNT
SET #infoMessage = 'Number of rows affected ' + CAST(#count AS VARCHAR(10))
RAISERROR(#infoMessage, 10, 0) WITH NOWAIT
-- another INSERT
SET #count = ##ROWCOUNT
SET #infoMessage = 'Number of rows affected ' + CAST(#count AS VARCHAR(10))
RAISERROR(#infoMessage, 10, 0) WITH NOWAIT
On the client side, set the appropriate event handlers, here is an example:
using (SqlConnection conn = new SqlConnection(...))
{
conn.FireInfoMessageEventOnUserErrors = true;
conn.InfoMessage += new SqlInfoMessageEventHandler(conn_InfoMessage);
using (SqlCommand comm = new SqlCommand("dbo.sp1", conn)
{ CommandType = CommandType.StoredProcedure })
{
conn.Open();
comm.ExecuteNonQuery();
}
}
static void conn_InfoMessage(object sender, SqlInfoMessageEventArgs e)
{
// Process received message
}
After every Inserts, use ##ROWCOUNT, then get the value by query.
Returns the number of rows affected by the last statement. If the
number of rows is more than 2 billion, use ROWCOUNT_BIG.
Sample:
USE AdventureWorks2012;
GO
UPDATE HumanResources.Employee
SET JobTitle = N'Executive'
WHERE NationalIDNumber = 123456789
IF ##ROWCOUNT = 0
PRINT 'Warning: No rows were updated';
GO
Edit: How you can get ##rowcount with multiple query? Here's an example:
DECLARE #t int
DECLARE #t2 int
SELECT * from table1
SELECT #t=##ROWCOUNT
SELECT * from table2
SELECT #t2=##ROWCOUNT
SELECT #t,#t2'

Build SQL query string using user input

I have to make a string by using the values which the user selects on the webpage,
Suppose I need to display files for multiple machines with different search criteria...
I currently use this code:
DataTable dt = new DataTable();
SqlConnection connection = new SqlConnection();
connection.ConnectionString = ConfigurationManager
.ConnectionStrings["DBConnectionString"].ConnectionString;
connection.Open();
SqlCommand sqlCmd = new SqlCommand
("SELECT FileID FROM Files
WHERE MachineID=#machineID and date= #date", connection);
SqlDataAdapter sqlDa = new SqlDataAdapter(sqlCmd);
sqlCmd.Parameters.AddWithValue("#machineID", machineID);
sqlCmd.Parameters.AddWithValue("#date", date);
sqlDa.Fill(dt);
Now this is a fixed query where the user just has one machine and just selects one date...
I want to make a query in which the user has multiple search options like type or size if he/she wants depending on what he/she selects.
Also if he/she can select multiple machines...
SELECT FileID FROM Files
WHERE (MachineID=#machineID1 or MachineID = #machineID2...)
AND (date= #date and size=#size and type=#type... )
All of this happens at runtime... otherwise I have to create a for loop to put multiple machines one by one... and have multiple queries depending on the case the user selected...
This is quite interesting and I could use some help...
If you are going to do this via dynamic SQL, you need to build a call to the IN function. (e.g. In(id1, id2, id3...)
private string GetSql( IList<int> machineIds )
{
var sql = new StringBuilder( "SELECT FileID FROM Files Where MachineID In(" );
for( var i = 0; i < machineIds.Count; i++ )
{
if ( i > 0 )
sql.Append(", ")
sql.Append("#MachineId{0}", i);
}
sql.Append(" ) ");
//additional parameters to query
sql.AppendLine(" And Col1 = #Col1" );
sql.AppendLine(" And Col2 = #Col2 ");
...
return sql.ToString();
}
private DataTable GetData( IList<int> machineIds, string col1, int col2... )
{
var dt = new DataTable();
var sql = GetSql( machineIds );
using ( var conn = new SqlConnection() )
{
conn.ConnectionString = ConfigurationManager.ConnectionStrings["DBConnectionString"].ConnectionString;
using ( var cmd = new SqlCommand( sql, conn ) )
{
conn.Open();
for( var i = 0; i < machineIds.Count; i++ )
{
var parameterName = string.Format("#MachineId{0}", i );
cmd.Parameters.AddWithValue( parameterName, machineIds[i] );
}
cmd.Parameters.AddWithValue( "#Col1", col1 );
cmd.Parameters.AddWithValue( "#Col2", col2 );
...
using ( var da = new SqlDataAdapter( cmd ) )
{
da.Fill( dt );
}
}
}
return dt;
}
You can use WHERE MachineID IN ('Machine1', 'Machine2', 'Machine3', ... 'MachineN')
Then in your loop you would just add the 1..n machines. The IN clause works with 1 element or n elements, so it should be fine.
However, I'd look at using a stored procedure to do it rather than hardcoding the SQL into your application.
Build a real table and load the machine ids into it.
Then your SQL would be:
where MachineID in ( select MachineID from userMachine where userID = x)
When you are done, remove all rows for the userID:
delete from userMachine where userID = x.
Normally when I want to create a "search" type query, I use optional parameters. This allows me to send something or nothing to the parameter, making the query go from vague to very specific.
Example:
SELECT
COL1,
COL2,
COL3
FROM TABLE
WHERE (#COL1 IS NULL OR #COL1 = '' OR #COL1 = COL1)
As you'll notice above, if you pass in NULL or BLANK it won't add the parameter to the query. If you do enter a value, then it'll be used in the comparison.
Ideally you are trying to arrive at a solution similar to creating "MachineID in (1, 2, 3, 4)" dynamically.
Option 1
There are many ways to complete this task from passing in a comma separated string into the stored proc and dynamically build the sql string and then calling "EXEC sp_executesql #sql"
WHERE IN (array of IDs)
Option 2
You can pass in a string of comma separated values and then parse out the values into their own temp-table and then join on to it
http://vyaskn.tripod.com/passing_arrays_to_stored_procedures.htm
Option 3 - my choice
You can now pass in the array of values using XML and then select the array items easily.
http://support.microsoft.com/kb/555266
.
I would also recommend using a stored procedure because otherwise you will leave yourself open to an SQL injection attack - especially where you are building up a string based on user input.
Something like:
a' or 1=1; -- Do bad things
You can use sp_executesql in SQL to run an SQL statement that is built up with a where clause like #dcp suggests and although it wouldn't optimize well it is probably a quick command to run anyway.
SQL Injection attacks by example
One way to achieve this would be using charindex. This example demonstrates how a stored procedure could be run when passed a space separated list of ids:
declare #machine table (machineId int, machineName varchar(20))
declare #files table (fileId int, machineId int)
insert into #machine (machineId, machineName) values (1, 'machine')
insert into #machine (machineId, machineName) values (2, 'machine 2.0')
insert into #machine (machineId, machineName) values (3, 'third machine')
insert into #machine (machineId, machineName) values (4, 'machine goes forth')
insert into #machine (machineId, machineName) values (5, 'machine V')
insert into #files (fileId, machineId) values (1, 3)
insert into #files (fileId, machineId) values (2, 3)
insert into #files (fileId, machineId) values (3, 2)
insert into #files (fileId, machineId) values (4, 1)
insert into #files (fileId, machineId) values (5, 3)
insert into #files (fileId, machineId) values (6, 5)
declare #machineText1 varchar(100)
declare #machineText2 varchar(100)
declare #machineText3 varchar(100)
set #machineText1 = '1 3 4'
set #machineText2 = '1'
set #machineText3 = '5 6'
select * from #files where charindex(rtrim(machineId), #machineText1, 1) > 0
-- returns files 1, 2, 4 and 5
select * from #files where charindex(rtrim(machineId), #machineText2, 1) > 0
-- returns file 4
select * from #files where charindex(rtrim(machineId), #machineText3, 1) > 0
--returns file 6
So you can create this stored procedure to achieve your aim:
create procedure FilesForMachines (#machineIds varchar(1000))
as
select * from [Files] where charindex(rtrim(machineId), #machineIds, 1) > 0
The charindex tip is from BugSplat.

Categories