C# - SQL - add rowsCount dynamically to data models - c#

I have a code where i do sql query by casting the table model like this:
string sql = string.Format("SELECT * FROM {0}...", tableName...);
and then:
IEnumerable<T> r = dbConn.Connection.Query<T>(sql...);
the thing is if i want to get total rowsCount(of course i can get count on the "r" but if there is a where clause its not possible because i want total count) i have to another query without where.
so i want to remove the second query. i did this in sql query to get rowsCount:
string sql = string.Format("SELECT *, count(*) over() rowsCount FROM {0}...", tableName...);
i can get the rowsCount with this query but since neither one of models has rowsCount i cant access it, is there any suggestions on how i should do it?
Edit:
first query has paging filter by using offset and limit, so i want totalcount not the count of filtered query.
I'm looking to see if there is a way to not use two seperate queries, and get results and also rowsCount by just one query.

You will have to do 2 SQL queries.
Nothing stopping you running them in one SQL block or calling a stored proc with output parameters. So you don't have to make 2 calls but you will need 2 queries at least.
https://www.sqlservertutorial.net/sql-server-stored-procedures/stored-procedure-output-parameters/
If you are worried about performance of a total count just make sure you have an index on the smallest column in that table and it should be mega fast.
The below example return a dataset and an output in one call
DROP PROCEDURE IF EXISTS dbo.DataSetAndOutput
GO
CREATE PROCEDURE dbo.DataSetAndOutput
#YourId BIGINT,
#CountRecords INT OUTPUT
AS
BEGIN
SELECT * FROM YourTable
WHERE Id = #YourId
SET #CountRecords = (SELECT COUNT(YourId) FROM YourTable)
END
GO
-- Test the output
DECLARE #ResultCount INT
EXEC dbo.DataSetAndOutput #YourId= 252452, #CountRecords = #ResultCount OUTPUT
SELECT #ResultCount AS TheCount

Related

c# MySql.Data.EntityFrameworkCore store procedure pagination

for using pagination in complex query before i used sql server (tsql) i can do it very ease using COUNT(*) OVER() as TotalCount in any complex query it will return total count and i can use it in pagination but in mysql it will not work
to connect with mysql with asp.net core there is two packages
MySql.Data.EntityFrameworkCore
Pomelo.EntityFrameworkCore.MySql
i used both but i don't know how to implement pagination in store procedure and return using FromSqlRaw the issue is return number of rows OUT TotalCount INT that i can't get it
DELIMITER $$
CREATE PROCEDURE get_articles(IN _offset INT, IN _count INT, OUT _total INT)
BEGIN
SELECT SQL_CALC_FOUND_ROWS *
FROM content c JOIN content_types t
ON c.content_type = t.id
WHERE t.name = 'article'
LIMIT _offset, _count;
SET _total = FOUND_ROWS();
END$$
DELIMITER ;
c# with MySql.Data.EntityFrameworkCore
var result = await dbContext.Result.FromSqlRaw("CALL get_articles (0,30,#TotalCount)",totalCount
).AsNoTracking().ToListAsync();
Here is SQLFiddle demo
Your procedure should accpet first element or page number then you can combine limit keyword in your SQL. For example limit kpagecountpagenumber , kpagecount(pagenumber+1)-1
There is also version which takes limit n offset m
After edit:
please include also parameters in your call (C#).

special select query

I have 3 tables in my sql database like these :
Documents : (DocID, FileName) //list of all docs that were attached to items
Items : (ItemID, ...) //list of all items
DocumentRelation : (DocID, ItemID) //the relation between docs and items
In my winform application I have showed all records of Items table in a grid view and let user to select several rows of it and then if he press EditAll button another grid view should fill by file name of documents that are related to these selected items but not all of them,
Just each of documents which have relation with ALL selected items
Is there any query (sql or linq) to select these documents?
Try something like:
string query;
foreach (Item in SelectedItems)
{
query += "select DocID from DocumentRelation where ItemID =" + Item.Id;
query += "INTERSECT";
}
query -= "INTERSECT";
And exec the Query;
Take one string and keep on adding itemid comma separated in that,like 1,2,3 and then write query like
declare ItemID varchar(50);
set ItemID='1,2,3';
select FileName
from documents
Left Join DocumentRelation on Documents.DocId = DocumentRelation.DocId
where
DocumentRelation.ItemID in (select * from > dbo.SplitString(ItemID))
and then make one function in database like below
ALTER FUNCTION [dbo].[SplitString] (#OrderList varchar(1000))
RETURNS #ParsedList table (OrderID varchar(1000) )
AS BEGIN
IF #OrderList = ''
BEGIN
set #OrderList='Null'
end
DECLARE #OrderID varchar(1000), #Pos int
SET #OrderList = LTRIM(RTRIM(#OrderList))+ ','
SET #Pos = CHARINDEX(',', #OrderList, 1)
IF REPLACE(#OrderList, ',', '') <''
BEGIN
WHILE #Pos 0
BEGIN
SET #OrderID = LTRIM(RTRIM(LEFT(#OrderList, #Pos - 1)))
IF #OrderID < ''
BEGIN
INSERT INTO #ParsedList (OrderID)
VALUES (CAST(#OrderID AS varchar(1000)))
--Use Appropriate conversion
END
SET #OrderList = RIGHT(#OrderList, LEN(#OrderList) - #Pos)
SET #Pos = CHARINDEX(',', #OrderList, 1)
END
END
RETURN
END
Linq
var td =
from s in Items
join r in DocumentRelation on s.ItemID equals r.ItemID
join k in Documents on k.DocID equals r.DocID
where Coll.Contains (s.ItemID) //Here Coll is the collection of ItemID which you can store when the users click on the grid view row
select new
{
FileName=k.FileName,
DocumentID= k.DocId
};
You can loop through td collection and bind to your grid view
SQL
create a stored proc to get the relevant documents for the itemID selected from the grid view and paramterize your in clause
select k.FileName,k.DocId from Items as s inner join
DocumentRelation as r on
s.ItemID=r.ItemID and r.ItemId in (pass the above coll containing selected ItemIds as an input the SP)
inner join Documents as k
on k.DocId=r.DocIk
You can get the information on how to parametrize your sql query
Here's one approach. I'll let you figure out how you want to supply the list of items as arguments. And I also assume that (DocID, ItemID) is a primary key in the relations table. The having condition is what enforces your requirement that all select items are related to the list of documents you're seeking.
;with ItemsSelected as (
select i.ItemID
from Items as i
where i.ItemID in (<list of selected ItemIDs>)
)
select dr.DocID
from DocumentRelation as dr
where dr.ItemID in (select ItemID from ItemsSelected)
group by dr.DocID
having count(dr.ItemID) = (select count(*) from ItemsSelected);
EDIT
As far as I can tell, the accepted answer is equivalent to the solution here despite OP's comment below.
I did some quick tests with a very long series of intersect queries and confirmed that you can indeed expect that approach to become gradually slower with an increasing number of selected items. But a much worse problem was the time taken just to compile the queries. I tried this on a very fast server and found that that step took about eight seconds when roughly one hundred intersects were concatenated.
SQL Fiddle didn't let me do anywhere near as many before producing this error (and taking more than ten seconds in the process): The query processor ran out of internal resources and could not produce a query plan. This is a rare event and only expected for extremely complex queries or queries that reference a very large number of tables or partitions. Please simplify the query. If you believe you have received this message in error, contact Customer Support Services for more information.
There are several possible methods of passing a list of arguments to SQL Server. Assuming that you prefer the dynamic query solution I'd argue that this version is still better while also noting that there is a SQL Server limit on the number of values inside the in.
There are plenty of ways to have this stuff blow up.

cmd.ExecuteScalar() returns 0

I want to get the count of rows of a table.
cmd.CommandText = "SELECT COUNT(*) FROM (SELECT * FROM (SELECT * FROM EMPLOYEE ))";
Records = int.Parse(cmd.ExecuteScalar().ToString());
even if there is 1 row in employee table, Records always have value 0.
why ExecuteScalar() returns 0?
What you are trying to calculate is the number of rows in the EMPLOYEE table, therefore Pranav and Vinod are right. But assuming you try to simplify your situation just to point directly to your problem, I would say that you need to give names to your temporary tables like this:
cmd.CommandText = "SELECT COUNT(*) FROM (SELECT * FROM (SELECT * FROM EMPLOYEE ) as T1) as T2";
Records = int.Parse(cmd.ExecuteScalar().ToString());
That should do the trick.
One additional suggestion is not to use count(0); instead use count(0). '*' brings all data in the view; which is an unnecessary performance decrement for count function.
It returns 0 because your SQL request isn't valid.
You should always try your requests directly against your database (for instance using SQL Server Management Studio if you are using SQL Server) before writing them in your code. You would have seen it doesn't return anything because it has a syntax error.
If you want to use nested SELECT * FROM as you wrote, here's the valid SQL request that will return what you expected:
SELECT COUNT(*) FROM (SELECT * FROM (SELECT * FROM EMPLOYEE ) AS Foo1 ) AS Foo2
In any case, as all other answers have noted, this doesn't make much sense, and must be replaced by:
SELECT COUNT(*) FROM EMPLOYEE
Try this:
cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEE";
int Records = (int)cmd.ExecuteScalar();
That should return the number of rows in the EMPLOYEE table.
To double check, use the SQL management studio to execute the same code against your SQL server and see what the result is. Is it possible that your application is configured to point a different database to the one that you are viewing?
Just say:
cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEE";
Records = int.Parse(cmd.ExecuteScalar().ToString());
Try this:
cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEE"
Int record=(Int32)cmd.Executescalar();

How do I delete all but the newest 20 rows in a table?

How do you delete the top row? I want to delete the top row if theres more than 20 rows.
im using c#.
Have a look at DELETE (Transact-SQL)
TOP (expression) [ PERCENT ]
Specifies the number or percent of
random rows that will be deleted.
expression can be either a number or a
percent of the rows. The rows
referenced in the TOP expression used
with INSERT, UPDATE, or DELETE are not
arranged in any order.
Maybe also have a read at The DELETE statement in SQL Server
Some info about deletion http://msdn.microsoft.com/en-us/library/bb386925.aspx
var ordFetch =
(from ofetch in db.Orders
where ofetch.OrderID == reqOrder
select ofetch).First();
db.Orders.DeleteOnSubmit(ordFetch);
db.SubmitChanges();
To select top rows:
var data = (from p in people
select p).Take(100);
Assuming the ID field is a suitable substitute and always increases with time (i.e., smallest values of ID are the oldest, and that is always the case):
using (SqlConnection conn = new SqlConnection("yourconnectionstring"))
{
SqlCommand cmd = conn.CreateCommand(
"DELETE FROM table WHERE ID NOT IN (SELECT TOP 20 ID FROM table ORDER BY ID DESC)");
cmd.ExecuteNonQuery();
}
(My C# syntax may be slightly off -- I did this from memory -- but the SQL query should be fine.)

SQL Server 2005: dynamically adding parameters to a stored procedure

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

Categories

Resources