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.
Related
I am trying to make a sql query, that gets me the registration_timestamp of the newest comment.
By supplying a category id.
I have three tables. ( seen below with the fields that should be needed)
Ctm_Comments{
Id,
Page_ID,
Registration_Timestamp
}
Ctm_Forum_Categories{
Id
}
Ctm_Forum_Posts{
Id,
FK_Category_ID
}
I have tried the following, and it returns zero results.
var query = from p in Ctm_Forum_Posts
join c in Ctm_Forum_Categories on p.FK_Categori_ID equals c.Id
join ctm in Ctm_Comments on p.Id equals ctm.Page_ID
where c.Id == 1
select ctm.Reqistration_timestamp;
SQL Queries like these are not my strong suit, so i hope someone here can help out.
Ended up with this, based on the response from accepted answer.
var query = (from comments in Ctm_Comments
join posts in Ctm_Forum_Posts on comments.Page_ID equals posts.Id
join category in Ctm_Forum_Categories on posts.FK_Categori_ID equals category.Id
where category.Id == 1
orderby comments.Reqistration_timestamp descending
select comments.Reqistration_timestamp).FirstOrDefault();
SQL (MS SQL) Query needed is
SELECT TOP 1 [Registration_Timestamp]
FROM [dbo].[Ctm_Comments] AS C
INNER JOIN [dbo].[Ctm_Forum_Posts] AS P ON C.Page_ID = P.Id
INNER JOIN [dbo].[Ctm_Forum_Categories] AS CAT ON CAT.Id = P.Category_ID
WHERE CAT.Id = 1
ORDER BY C.Registration_Timestamp DESC
and this is if we accept that PageID (of Comments Table) is the Post Id. Otherwise, you are missing the PostId Column in the table of Comments which should be the join point
Run the Script below in SQL Server Studio for verification
CREATE TABLE [dbo].[Ctm_Comments] ( [Id] [int] NULL,[Page_ID] [int] NULL,[Registration_Timestamp] [datetime] NULL) ON [PRIMARY]
CREATE TABLE [dbo].[Ctm_Forum_Categories] ( [Id] [int] NULL) ON [PRIMARY]
CREATE TABLE [dbo].[Ctm_Forum_Posts] ( [Id] [int] NULL,[Category_ID] [int] NULL) ON [PRIMARY]
INSERT INTO [dbo].[Ctm_Comments] VALUES (1, 1, '2020-10-23 13:12:55')
INSERT INTO [dbo].[Ctm_Comments] VALUES (2, 1, '2020-10-26 12:12:55')
INSERT INTO [dbo].[Ctm_Comments] VALUES (3, 1, '2020-10-26 12:25:55')
INSERT INTO [dbo].[Ctm_Comments] VALUES (4, 1, '2020-10-26 13:12:55')
INSERT INTO [dbo].[Ctm_Forum_Categories] VALUES (1)
INSERT INTO [dbo].[Ctm_Forum_Posts] VALUES (1, 1)
SELECT TOP 1 [Registration_Timestamp]
FROM [dbo].[Ctm_Comments] AS C
INNER JOIN [dbo].[Ctm_Forum_Posts] AS P ON C.Page_ID = P.Id
INNER JOIN [dbo].[Ctm_Forum_Categories] AS CAT ON CAT.Id = P.Category_ID
WHERE CAT.Id = 1
ORDER BY C.Registration_Timestamp DESC
DROP TABLE [dbo].[Ctm_Comments]
DROP TABLE [dbo].[Ctm_Forum_Categories]
DROP TABLE [dbo].[Ctm_Forum_Posts]
the Result is 2020-10-26 13:12:55.000
When you fix the "my query returns 0 results" part, I'd suggest something like this:
var mostRecentCommentTimestamp = query.Max();
But as you've only selected timestamps, this can only tell you the max timestamp, nothing else about the comment
If you want the whole most recent comment swap the select for an order by descending on the timestamp and take the first*, or install morelinq and use their MaxBy
*Edit, like this:
var query = from p in Ctm_Forum_Posts
join c in Ctm_Forum_Categories on p.FK_Categori_ID equals c.Id
join ctm in Ctm_Comments on p.Id equals ctm.Page_ID
where c.Id == 1
orderby ctm.Reqistration_timestamp descending
select ctm;
var firstComment = query.First();
All this said, at the moment you say your query produces no results; you need to fix that (the joins are wrong, or there is no category 1, or the db is missing data) before you can get a max/orderby of anything
In sql I can do this
select 'a' as MyColumn
so, i have a query in linq with entity framework that get some data from the database, but, i need union that query with one row, and in sql I can do this:
select ... from ...
union
select 'a' as MyColumn
How can i generate this query with linq?
I tried to do this:
var query = (from ... select new {..}).Union(new List<...> { new ...() { MyColumn = 'a' } })
But i gess that Entity Framework DON'T know how to translate that in memory list to sql
I need to get an IQueryable result, not a List or other in memory Collection, because i need to join that result to other sql linq querys in the future.
This isn't possible and you shouldn't do it. Both for the same reason: Entity Framework will try to translate the whole LINQ statement into SQL, including the local list (new List<...>).
The reason why it's not possible is that EF has no way to translate C# objects into SQL constructs.
The reason why you shouldn't do it is that it's incredibly wasteful: you build the list in C# code, EF (if it could) translates it into a SQL statement, the database runs the SQL statement and converts it to a result set, EF receives the result set and converts it into the list you originally offered it.
Just to demonstrate it, I'll show what happens if you do this with a list of primitive values which EF does know how to translate into SQL:
var ints = Enumerable.Range(1,5);
var res = Products.Select(c => c.Id).Union(ints).ToList();
This produces the following SQL statement:
SELECT
[Distinct1].[C1] AS [C1]
FROM ( SELECT DISTINCT
[UnionAll5].[ProductId] AS [C1]
FROM (SELECT
[Extent1].[ProductId] AS [ProductId]
FROM [dbo].[Product] AS [Extent1]
UNION ALL
SELECT
1 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
UNION ALL
SELECT
2 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable2]
UNION ALL
SELECT
3 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable3]
UNION ALL
SELECT
4 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable4]
UNION ALL
SELECT
5 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable5]) AS [UnionAll5]
) AS [Distinct1]
As you see, for each element in the list EF generated a SingleRowTablex entry to build a "temp table" to UNION with the ids from the actual query.
Conclusion: just query what you need from the database and add to the result afterwards. It's easy enough to do that:
(from ... select new {..})
.AsEnumerable() // continue in memory
.Union(...)
I have to iterate through a collection of objects (let's say ID's), and execute a specific query for each of these objects. For example:
IEnumerable<int> ids = getIDs(); //[1,2,3,4...]
Right now I have this solution:
DBEntities db = new DBEntities();
var results =
from a in db.TABLEA
join b in db.TABLEB on a.id equals b.id
join c in db.TABLEC on b.oid equals c.oid
where ids.Contains(c.id)
select a;
but keep in mind that the list of IDs is smaller than the table where I am searching. That being said, the solution above seems inefficient, since I am looking for each record of my table against a smaller list, when I wanted the opposite. I also do not want to iterate through the list, and execute the query for one element at a time.
Ideally, I would want something like this:
DBEntities db = new DBEntities();
(some data structure) ids = getIDs();
var results =
from a in db.TABLEA
join b in db.TABLEB on a.id equals b.id
join c in db.TABLEC on b.oid equals c.oid
join i in ids on c.id equals i.id;
The (pseudo-)code above would iterate my elements of the list, in a single query, doing so in a single query and performing my filter by each element of the list.
Is this the way to do it? If so, what is the appropriate data structure to implement this solution? If not, which alternatives do I have?
Magnus's answer is true but not right :)
Technically you do have two options in newer versions of Entity Framework (and I discovered this by chance). Contains of course, but also Join.
Joining with a local sequence of primitive types has always been possible, but very quickly (after some tens of elements) raised a SqlException:
Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries.
EF tries to translate the local list to a temporary table in SQL. This is surprisingly non-trivial. It has to build the table by UNION-ing select statements that return 1 element each. This is what it used to look like with only 5 elements!
....
INNER JOIN (SELECT
[UnionAll3].[C1] AS [C1]
FROM (SELECT
[UnionAll2].[C1] AS [C1]
FROM (SELECT
[UnionAll1].[C1] AS [C1]
FROM (SELECT
1 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
UNION ALL
SELECT
2 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
UNION ALL
SELECT
3 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
UNION ALL
SELECT
4 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable4]) AS [UnionAll3]
UNION ALL
SELECT
5 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable5]) AS [UnionAll4] ON ....
As you see, if you look closely, the UNION statements are nested. The nesting level soon becomes too deep, which made this approach practically useless.
However, currently the SQL looks like this:
....
INNER JOIN (SELECT
1 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
UNION ALL
SELECT
2 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable2]
UNION ALL
SELECT
3 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable3]
UNION ALL
SELECT
4 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable4]
UNION ALL
SELECT
5 AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable5]) AS [UnionAll4] ON ....
Still not a beauty, but the nesting is replaced by chaining and the list can contain hundreds of elements.
But... (and this is why Magnus's answer is true), it doesn't perform well. A simple test with 2000 elements in the list took 2.5s with join and .25s with Contains. So there's still no practical case for joining with a local sequence.
If this is linq2Sql (or Linq2Entites) your only option is as in your example 1. You can not "join" a table with an in memory list. You have to use Contains. Which will be translated to an Where c.id IN(2,3,4,5,...) SQL query
Hi I am developing an application in VS 2010, C# and SQLCe database.
I am generating a report from 3 tables respectively Income, Expence and Transactions.
table structure is as below:
Income:
Expence:
Transactions:
For this I wrote query
select t.tDate as [Date], t.tDescription as [Detail],e.eAmount as [Debit],
i.iAmount as [Credit], t.balance as [Balance]
from Transactions t
inner join Expence e on t.pid=e.pid
join Income i on t.pid=i.pid
where t.pid='11'
But when I run this query I am just getting
I want that my result should be like a bank statement as below.
As per my understanding query is not right.
How can we write query to get result like this?
You must use the left join, not use Join. Because left return all cases in table from and case exists in table Expence e Income.
select t.tDate as [Date], t.tDescription as [Detail],e.eAmount as [Debit],
i.iAmount as [Credit], t.balance as [Balance]
from Transactions t
left join Expence e on t.pid=e.pid
left join Income i on t.pid=i.pid
where t.pid='11'
If I got it right to join items from Expence table with items from Transactions table you should use pId and eId and to join items from Income you should use pId and iId, so your select would look something like that:
select t.tDate as [Date], t.tDescription as [Detail],e.eAmount as [Debit],
i.iAmount as [Credit], t.balance as [Balance]
from Transactions t
inner join Expence e on t.pid=e.pid and t.eId = e.eId
join Income i on t.pid=i.pid and t.iId = i.iId
where t.pid='11'
I have a problem that I know how to solve in SQL but not with Linq to Entities.
My data looks like this:
ID GROUP TIMESTAMP
-- ----- ---------
1 A 2011-06-20
2 A 2011-06-21
3 B 2011-06-21
4 B 2011-06-22
5 B 2011-06-23
6 C 2011-06-30
I want to retrieve all the Entity objects (not just the ID) such that I am only getting the most recent record from each group. (ie. the records with ids 2, 5, 6)
In SQL I would do something like this:
SELECT * FROM my_table a
WHERE a.timestamp =
(SELECT MAX(timestamp) FROM my_table b
WHERE a.group = b.group)
(For the sake of this question you can assume that timestamp is unique within each group).
I'd like to do this query against a WCF Data Service using Linq to Entities but I can't seem to have a nested query that references the outside query like this. Can anyone help?
Possibly not as clean and efficient as the hand written version but here's what I came up with
var q = from a in db.MyEntities
where a.Timestamp == (from b in db.MyEntities
where b.Group == a.Group
select b.Timestamp).Max()
select a;
which translates into this SQL
SELECT
[Project1].[Id] AS [Id],
[Project1].[Group] AS [Group],
[Project1].[Timestamp] AS [Timestamp]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Group] AS [Group],
[Extent1].[Timestamp] AS [Timestamp],
[SSQTAB1].[A1] AS [C1]
FROM [MyEntities] AS [Extent1]
OUTER APPLY
(SELECT
MAX([Extent2].[Timestamp]) AS [A1]
FROM [MyEntities] AS [Extent2]
WHERE [Extent2].[Group] = [Extent1].[Group]) AS [SSQTAB1]
) AS [Project1]
WHERE [Project1].[Timestamp] = [Project1].[C1]
Hi try to use linqer that will convert your sql statements to linq query.
Linqer
Best Regards
This should work:
var query = db.my_table
.GroupBy(p=>p.group)
.Select(p=>p.OrderByDescending(q=>q.timestamp).First());
Here you go.A simple way to do.
var result = (from x in my_table
group x by x.Group into g
select new
{
g.Key,
timestamp = g.Max(x => x.TimeStamp),
g //This will return everything in g
});