Optimize SQL function in C# code - c#

I'm running into an issue with an application that's leading me to a method doing the following.
protected override int GetCount(List<int> itemlist)
{
sql.Execute(#"TRUNCATE Table table0");
int count = 0;
foreach (int itemgroup in itemlist)
{
count += sql.Execute(#" INSERT INTO table0 (ID, Guid)
SELECT table1.ID , table1.Guid
FROM dbo.tablefunction(#p0) table1 LEFT JOIN
dbo.table0 ON table1.ID = table0.ID
WHERE table0.ID IS NULL", itemgroup);
}
return count;
}
i'm running into a key constraint issue during the insert loop which isn't completely unexpected.
But I'm also noticing that it's potentially doing multiple varied size inserts so I'm looking for ideas/suggestions on dynamically assembling a union query and then inserting all results at once. For example,the resulting query might be
WITH b AS
(
SELECT table1.ID , table1.Guid
FROM dbo.tablefunction(item0) table1 LEFT JOIN
dbo.table0 ON table1.ID = table0.ID
WHERE table0.ID IS NULL
UNION
SELECT table1.ID , table1.Guid
FROM dbo.tablefunction(item1) table1 LEFT JOIN
dbo.table0 ON table1.ID = table0.ID
WHERE table0.ID IS NULL
)
INSERT INTO table0 (ID, Guid)
SELECT * FROM b
I'm just not sure how best to go about it.

Use string.format() method :
protected override int GetCount(List<int> itemlist)
{
sql.Execute(#"TRUNCATE Table table0");
int count = 0;
foreach (int itemgroup in itemlist)
{
string sql = string.Format(#" INSERT INTO table0 (ID, Guid)
SELECT table1.ID , table1.Guid
FROM dbo.tablefunction({0}) table1 LEFT JOIN
dbo.table0 ON table1.ID = table0.ID
WHERE table0.ID IS NULL", itemgroup);
count += sql.Execute(sql);
}
return count;
}

You could use Table-Valued Parameters - msdn and a stored procedure to do this.
First, you would need to create a table type to use with the procedure:
create type dbo.ItemGroups_udt as table (ItemGroup int not null);
go
Then, create the procedure:
create procedure dbo.table0_truncate_and_insert (
#ItemGroups as dbo.ItemGroups_udt readonly
) as
begin;
set nocount, xact_abort on;
truncate table table0;
insert into table0 (id, guid)
select tvf.id, tvf.guid
from #ItemGroups as i
cross apply dbo.tablefunction(i.ItemGroup) as tvf;
end;
go
If you run in to constraint violoations then distinct, group by, or other conditions may be necessary
Then, assemble and pass the list of item groups to the stored procedure using a DataTable added as a SqlParameter using SqlDbType.Structured.
Table Valued Parameter Reference:
SQL Server 2008 Table-Valued Parameters and C# Custom Iterators: A Match Made In Heaven! - Leonard Lobel
Table Value Parameter Use With C# - Jignesh Trivedi
Using Table-Valued Parameters in SQL Server and .NET - Erland Sommarskog
Maximizing Performance with Table-Valued Parameters - Dan Guzman
Maximizing throughput with tvp - sqlcat
How to use TVPs with Entity Framework 4.1 and CodeFirst

This is what I ended up coming up with. I may have been pre-coffee earlier when I was looking at this. It could probably still use a little work but it should work.
protected override int GetCount(List<int> itemlist)
{
sql.Execute(#"TRUNCATE Table table0");
int count = 0;
string sql = #"WITH b AS
(
{0}
)
INSERT INTO table0 (ID, Guid)
SELECT ID, Guid
FROM b";
List<string> sqlsubs = new List<string>();
foreach (int itemgroup in itemlist)
{
sqlsub.Add( string.Format(#"SELECT table1.ID , table1.Guid
FROM dbo.tablefunction({0}) table1 LEFT JOIN
dbo.table0 ON table1.ID = table0.ID
WHERE table0.ID IS NULL", itemgroup));
}
string sqlunion = string.Join(" UNION ", sqlsub);
return context.Execute(string.Format(sql, sqlunion));
}

Related

Entity Framework 6 Stored Procedure C# side missing created column

I have the following stored procedure. I implemented the UNION to avoid writing 2 stored procedures. My main issue is that EF will not recognize the dynamically created column, even if I add it manually to the object_result class that queries the model. Does anyone know how to get such dynamically generated columns to work?
(What's weird is the RANK() VolumeRank below does work. But RefYear does not map).
SQL:
BEGIN
SET NOCOUNT ON;
SET FMTONLY OFF;
SELECT *
FROM
(SELECT
B.OS, B.DS, COUNT(DISTINCT A.PO) Volume,
RANK() OVER (ORDER BY COUNT(DISTINCT A.PO) DESC) VolumeRank,
YEAR(GETDATE()) RefYear
FROM
DB.DW.LoadData AS A
JOIN
DB.DW.Geo AS B ON A.PO = B.PO
INNER JOIN
DW.DMaster AS C ON A.LoadDateKey = C.DateKey
WHERE
C.IsT12Weeks_LastWeek = 1
AND a.PrimaryCustomerID = #CustomerId
GROUP BY
B.OS, B.DS) AS A
WHERE
VolumeRank <= 3
UNION
SELECT *
FROM
(SELECT
B.OS, B.DS,
COUNT(DISTINCT A.PO) Volume,
RANK() OVER (ORDER BY COUNT(DISTINCT A.PO) DESC) VolumeRank,
YEAR(getdate())-1 RefYear
FROM
DB.DW.LoadData AS A
JOIN
DB.DW.Geo AS B ON A.PO = B.PO
INNER JOIN
DW.DMaster AS C ON A.LoadDateKey = C.DateKey
WHERE
PrimaryCustomerID = #CustomerId
AND FWeek >= #FirstWeek
AND FWeek <= #LastWeek
AND FiscalYear = #FiscalYear
GROUP BY
B.OS, B.DS) AS A
WHERE
VolumeRank <= 3
ORDER BY
RefYear DESC, Volume DESC
END
C#:
List<spGetT_Result> result = new List<spGetT_Result>();
using (var db = new T_Entities())
{
result = db.spGetT(eid, firstWeek, lastWeek, year).ToList();
}
Model class includes this line, but it's always null when I run:
public Nullable<int> RefYear { get; set; }

Query taking too long to execute in LINQ

I've written a query which should take all the rows from one table and do a subquery to a second table pulling only one value from the most recent record. In SQL Server the query takes about 15 seconds to execute and the LINQ query takes close to 2 minutes. Can someone help me with translating the SQL to LINQ, I must have done something wrong along the way.
The SQL:
SELECT a.isrunning,
worktype = (
SELECT TOP 1
w.worktype
FROM dbo.workorder w WITH (NOLOCK)
WHERE w.assetnum = a.assetnum
ORDER BY w.statusdate DESC
),
a.status,
*
FROM dbo.asset a WITH (NOLOCK)
WHERE a.assetnum IN ('list', 'of', 'asset', 'numbers')
The LINQ Query:
(
from a in db.assets
let wo = (
from w in db.workorders
where w.assetnum == a.assetnum
orderby w.statusdate descending
select w).FirstOrDefault()
where aliasStrings.Contains(a.assetnum)
select new AssetWithWorkType {
...
}
);
It is recommended to have indexes on foreign keys. Also indexes that covers filtering and ordering clauses. So I suggest you to create the following 3 indexes:
CREATE NONCLUSTERED INDEX [IDX_workorder_statusdate] ON dbo.workorder(statusdate)
CREATE NONCLUSTERED INDEX [IDX_workorder_assetnum] ON dbo.workorder(assetnum)
If assetnum column in asset table is not the primary key then additionally:
CREATE NONCLUSTERED INDEX [IDX_asset_assetnum] ON dbo.asset(assetnum)
You can create a temp table for the correlated subquery results, and then join it later. Syntax is not correct, as I dont have your table schemas or data, but the idea is the same.
CREATE TABLE #workTypes (worktype VARCHAR(X), assetnum VARCHAR(x))
INSERT INTO #workTypes
SELECT TOP 1 worktype, assetnum FROM dbo.workorder ORDER BY statusdate DESC
SELECT a.isrunning,
b.worktype,
a.status,
*
FROM dbo.asset a WITH (NOLOCK)
INNER JOIN #worktypes b
ON a.assetnum = b.assetnum
WHERE a.assetnum IN ('list', 'of', 'asset', 'numbers')
How about:
SELECT a.isrunning,
w.worktype,
cnt = count(*)
FROM dbo.asset a WITH (NOLOCK)
INNER JOIN dbo workorder w WITH (NOLOCK) on w.assetnum = a.assetnum
WHERE a.assetnum IN ('list', 'of', 'asset', 'numbers')
This would give you a count of worktypes for each asset and might allow the database server to optimize more efficiently. Also consider adding indices or using a temp table as other answers have suggested.

Using COUNT For Comparison in SQL Server CE 4.0

I'm attempting to combine the logic for some of my SQL queries, and I can't seem to figure out this problem. Obviously SQL Server CE has many limitations compared to SQL Server or mySQL, but surely there's a way to solve this.
I want to do a count on one table in my database, based on some parameters, and then I want to compare this value to a value stored in a column in another table.
Let's say the database is modeled like this:
Table1:
ID int
Key string
NumberInUse int
Table2:
ID int
OtherID int
Here's the necessary parts of the query.
SELECT *
FROM Table1
LEFT JOIN Table2 ON Table1.ID = Table2.ID
WHERE Table1.Key = #key
AND (SELECT COUNT(*) FROM Table2 WHERE ID = Table1.ID AND OtherID = #otherID) < Table1.NumberInUse;
Unfortunately this query gives me this error:
There was an error parsing the query. [ Token line number = 4,Token line offset = 6,Token in error = SELECT ]`
So is there a way I can rephrase the WHERE clause of my query to utilize this comparison?
Try this:
SELECT *
FROM Table1 t1
INNER JOIN (SELECT ID
,COUNT(*) numCount
FROM Table2 t2
WHERE t2.OtherId = #otherID
GROUP BY ID) t3
ON t1.ID = t3.ID
WHERE t1.Key = #Key
AND t3.numCount < t1.NumberInUse
Sure it's not SQL. You're missing the right operand of the second LEFT JOIN:
SELECT *
FROM Table1 LEFT JOIN Table2
ON Table1.ID = Table2.ID
LEFT JOIN ????? WHUT ?????
WHERE Table1.Key = #key
AND (SELECT COUNT(*) FROM Table2 WHERE ID = Table1.ID AND OtherID = #otherID) < Table1.NumberInUse;

SQL Server SELECT statement filter by passed in string array

I have a table called MyProducts and I want to return item1 and item2
SELECT item1, item2 from MyProducts
However I want it to be filtered on a string array I pass in (from C#). This is a very big table, so I an 'IN' statement is out. How would I do this using a join statement. Thanks!
There's no reason that IN statement is "out"; ultimately, that is a perfectly reasonable way of filtering - let the optimizer worry about the various options. It certainly isn't impacted by the fact that MyProducts is large. Adding a join makes more work: it does not, however, reduce the number of "hits", or the work involved. For example, to do that with dapper is just:
string[] filter = ...
var rows = connection.Query(
"select item1, item2 from MyProducts where SomeField in #filter",
new {filter});
or with LINQ:
string[] filter = ...
var rows = db.Products.Where(x => filter.Contains(x.SomeField));
One solution is to create a temporary table and join with it. The temporary table can have an index on the column on which you will be joining.
I always liked this method...
CREATE FUNCTION dbo.Split(#String varchar(max), #Delimiter char(1))
returns #temptable TABLE (Value varchar(max))
as
begin
declare #idx int
declare #slice varchar(max)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end
then you can do this...
CREATE PROCEDURE MySp
#list varchar(max)
AS
SELECT <columns>
FROM <mytable> mt
INNER JOIN dbo.split(#list,',') s ON s.Value= my.Key
NOTE: There are many Split functions out there so you do not have to use this specific one.
Another method I have used when using SQL Server 2008 is using a table parameter like this...
CREATE TYPE [dbo].[LookupTable] As Table
(
ID Int primary key
)
CREATE PROCEDURE [dbo].[SampleProcedure]
(
#idTable As [dbo].[LookupTable] Readonly
)
AS
BEGIN
SELECT <columns>
FROM <mytable> mt
INNER JOIN #idTable s ON s.Id= my.Key
END
Pass the parameter into SQL Server from C# in this manner...
DataTable dataTable = new DataTable("SampleDataType");
dataTable.Columns.Add("Id", typeof(Int32));
foreach (var id in <mycollectionofids>)
dataTable.Rows.Add(id);
SqlParameter parameter = new SqlParameter();
parameter.ParameterName="#Id";
parameter.SqlDbType = System.Data.SqlDbType.Structured;
parameter.Value = dataTable;
command.Parameters.Add(parameter);

asp.net multiple table update statement

I need to turn this query into an update statement. I will have to update the values from fields. Everything is already in place but the update statement.
Here is the select version of the query:
SELECT i.GoLiveDate, i.FirstBonusRun, i.TechFName, i.TechLName, i.TechEmail, i.TechPhone, i.WebISPFName, i.WebISPLName,
i.WebISPEmail, i.WebISPPhone, i.FullFillFName, i.FullFillLName, i.FullFillEmail, i.FullFillPhone, d.FName,
d.LName, d.HomePhone, d.Email
FROM NC_Information i
INNER JOIN Distributor d
ON d.DistID = i.ClientID
WHERE clientID = #value
Is it possible to update two different tables from within the same query?
Here is the code I have so far:
public void Update (int ClientID)
{
using ( var conn = new SqlConnection( GeneralFunctions.GetConnectionString() ) )
using ( var cmd = conn.CreateCommand() )
{
conn.Open();
cmd.CommandText =
#"SELECT i.GoLiveDate, i.FirstBonusRun, i.TechFName, i.TechLName, i.TechEmail, i.TechPhone, i.WebISPFName, i.WebISPLName,
i.WebISPEmail, i.WebISPPhone, i.FullFillFName, i.FullFillLName, i.FullFillEmail, i.FullFillPhone, d.FName,
d.LName, d.HomePhone, d.Email
FROM NC_Information i
INNER JOIN Distributor d
ON d.DistID = i.ClientID
WHERE clientID = #value";
cmd.Parameters.AddWithValue( "#value", ClientID );
cmd.ExecuteNonQuery();
}
}
You can't update multiple tables in one statement, but you can use a transaction to make sure that the updates are contingent upon one another:
BEGIN TRANSACTION
UPDATE SomeTable
SET SomeColumn = 'Foo'
WHERE SomeID = 123
UPDATE AnotherTable
SET AnotherColumn = 'Bar'
WHERE AnotherID = 456
COMMIT
I think, you cannot directly do the update on two tables. But you can Optimize the query.
How?
OUTPUT keyword in Insert/Update/Delete Statement
The first Update Statement's Select Data(filtered data) can be reused using the below mentioned example.
CREATE TABLE #table1
(
id INT,
employee VARCHAR(32)
)
go
INSERT INTO #table1 VALUES
(1, 'name1')
,(2, 'name2')
,(3, 'name3')
,(4, 'name4');
GO
DECLARE #GuestTable TABLE
(
id INT,
employee VARCHAR(32)
);
update #table1
Set id = 33
OUTPUT inserted.* INTO #GuestTable
Where id = 3
The Data in the '#GuestTable' Table is filtered data and can be
reused.
select * from #GuestTable
drop table #table1
Alternatively, you can create a dataset with two datatables, and let the tableadaptermanager manage the updates.

Categories