I've been getting stuck into some linq queries for the first time today and I'm struggling with some of the more complicated ones. I'm building a query to extract data from a table to build a graph. The tables colums I'm interested in are Id, Time and Value.
The user will select a start time, an end time and the number of intervals (points) to graph. The value column will averaged for each interval.
I can do this with a linq request for each interval but I'm trying to write it in one query so I only need to go to the database once.
So far I have got:
var timeSpan = endTime.Subtract(startTime);
var intervalInSeconds = timeSpan.TotalSeconds / intervals;
var wattList = (from t in _table
where t.Id == id
&& t.Time >= startTime
&& t.Time <= endTime
group t by intervalInSeconds // This is the bit I'm struggling with
into g
orderby g.Key
select g.Average(a => a.Value))
).ToList();
Any help on grouping over time ranges will be most welcome.
I've done this myself for exactly the same situation you describe.
For speed, modified the database's datapoints table to include an integer-based time column, SecondsSince2000, and then worked with that value in my LINQ to SQL query. SecondsSince2000 is a computed column defined as:
datediff(second, dateadd(month,1200,0), DataPointTimeColumn) PERSISTED
Where DataPointTimeColumn is the name of the column that stores the datapoint's time. The magic function call dateadd(month,1200,0) returns 2000-01-01 at midnight, so the column stores the number of seconds since that time.
The LINQ to SQL query is then made much simpler, and faster:
int timeSlotInSeconds = 60;
var wattList =
(from t in _table
where t.Id == id
&& t.Time >= startTime
&& t.Time <= endTime
group t by t.SecondsSince2000 - (t.SecondsSince2000 % timeSlotInSeconds)
into g
orderby g.Key
select g.Average(a => a.Value))).ToList();
If you can't modify your database, you can still do this:
var baseTime = new DateTime(2000, 1, 1);
var wattList =
(from t in _table
where t.Id == id
&& t.Time >= startTime
&& t.Time <= endTime
let secondsSince2000 = (int)(t.Time- baseTime).TotalSeconds
group t by secondsSince2000 - (secondsSince2000 % timeSlotInSeconds)
into g
orderby g.Key
select g.Average(a => a.Value))).ToList();
The query will be quite a bit slower.
Check out this example I wrote a while ago. It sounds like what you are trying to do, but I'm not sure if it does the grouping in SQL or by .NET.
http://mikeinmadison.wordpress.com/2008/03/12/datetimeround/
Maybe you can do something like:
var wattList = (from t in _table
where t.Id == id
&& t.Time >= startTime
&& t.Time <= endTime
).GroupBy(x => (int) ((x.Time - startTime).TotalSeconds / intervalInSeconds))
.Select(grp => grp.Average(x => x.Value));
Related
I have a table which has structure like below :
JobListings:
JobID
CompanyID
Status
ActiveDate
ExpiryDate
CreationDate
Now I want to calculate below statistics based on conditions:
Active
OnHold
Closed
Filled
Expired
Now because of some data corruption, I had a problem in ActiveDate field which contains date like this for some jobs : "0001-01-01"
So what I want to do is if ActiveDate has data like "0001-01-01" then
Consider ExpiryDate column else consider ActiveDate only.
I am trying to retrieve this statistics for the first day of month till today.
Here is my query:
var statistics = (from j in context.Job
where j.CompanyID == 100 &&
(j.ActiveDate == "0001-01-01" ? (j.CreationDate >= fromDate && j.CreationDate <= toDate):
(j.ActiveDate >= fromDate && j.fromDate <= today))
group j by j.Status into g
select new
{
Id = g.Key,
Statistics = g.Count()
});
Is there a better way to rewrite this query?
I have a table like this
UserID Year EffectiveDate Type SpecialExpiryDate
1 2015 7/1/2014 A
1 2016 7/1/2015 B 10/1/2015
there is no ExpriyDate in the table because it is only valid for one year, so the expiry date can be calculated from the effective date by adding a year.
The result I want to get is like this (the current year's effective date and the next year's expiry date)
UserID EffectiveDate ExpiryDate
1 7/1/2014 7/1/2016
And If the user's type is B, then there will be a special expiry date, so for this person, the result will be
UserID EffectiveDate ExpiryDate
1 7/1/2014 10/1/2015
Here is the code I wrote
var result = db.Table1
.Where(x => x.Year>= 2015 && (x.Type == "A" || x.Type == "B"))
.GroupBy(y => y.UserID)
.OrderByDescending(x => x.FirstOrDefault().Year)
.Select(t => new
{
ID = t.Key,
Type = t.FirstOrDefault().Type,
EffectiveDate = t.FirstOrDefault().EffectiveDate,
ExpiryDate = t.FirstOrDefault().SpecialExpiryDate != null ? t.FirstOrDefault().SpecialExpiryDate : (t.Count() >= 2 ? NextExpiryDate : CurrentExpiryDate)
}
);
The code can get the result I need, but the problem is that in the result set there are about 10000 records which took about 5 to 6 seconds. The project is for a web search API, so I want to speed it up, is there a better way to do the query?
Edit
Sorry I made a mistake, in the select clause it should be
EffectiveDate = t.LastOrDefault().EffectiveDate
but in the Linq of C#, it didn't support this LastOrDefault function transfered to sql, and it cause the new problem, what is the easiest way to get the second item of the group?
You could generate the calculated data on the fly, using a View in your database.
Something like this (pseudocode):
Create View vwUsers AS
Select
UserID,
Year,
EffectiveDate,
EffectiveData + 1 as ExpiryDate, // <--
Type,
SpecialExpiryDate
From
tblUsers
And just connect your LINQ query to that.
Try this:
var result =
db
.Table1
.Where(x => x.Year>= 2015 && (x.Type == "A" || x.Type == "B"))
.GroupBy(y => y.UserID)
.SelectMany(y => y.Take(1), (y, z) => new
{
ID = y.Key,
z.Type,
z.EffectiveDate,
ExpiryDate = z.SpecialExpiryDate != null
? z.SpecialExpiryDate
: (t.Count() >= 2 ? NextExpiryDate : CurrentExpiryDate),
z.Year,
})
.OrderByDescending(x => x.Year);
The .SelectMany(y => y.Take(1) effectively does the .FirstOrDefault() part of your code. By doing this once rather than for many properties you may improve the speed immensely.
In a test I performed using a similarly structured query I got these sub-queries being run when using your approach:
SELECT t0.increment_id
FROM sales_flat_order AS t0
GROUP BY t0.increment_id
SELECT t0.hidden_tax_amount
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000001]
SELECT t0.customer_email
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000001]
SELECT t0.hidden_tax_amount
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000002]
SELECT t0.customer_email
FROM sales_flat_order AS t0
WHERE ((t0.increment_id IS NULL AND #n0 IS NULL) OR (t0.increment_id = #n0))
LIMIT 0, 1
-- n0 = [100000002]
(This continued on for two sub-queries per record number.)
If I ran my approach I got this single query:
SELECT t0.increment_id, t1.hidden_tax_amount, t1.customer_email
FROM (
SELECT t2.increment_id
FROM sales_flat_order AS t2
GROUP BY t2.increment_id
) AS t0
CROSS APPLY (
SELECT t3.customer_email, t3.hidden_tax_amount
FROM sales_flat_order AS t3
WHERE ((t3.increment_id IS NULL AND t0.increment_id IS NULL) OR (t3.increment_id = t0.increment_id))
LIMIT 0, 1
) AS t1
My approach should be much faster.
I want to list out all records where CloseDate is greater than DateTime.Now.
I have write a query for that but it doesn't fetch records where CloseDate is greater than DateTime.Now.
See my query below
var query = (from x in objEntity.VacancyMsts
join o in objEntity.OrganizationMst on
x.OrganizationID equals o.OrganizationId into vacorg
from o in vacorg.DefaultIfEmpty()
where x.Status == true && x.CloseDate >= DateTime.Now
select new VacancyMstDTO
{});
Is there any problem in my query?
you dont need to join here if you have a FK relationship
var query = from x in objEntity.VacancyMsts
where x.Status && x.CloseDate >= DateTime.Now
select new VacancyMstDTO
{
OrganizationName = x.OrganizationMst.Name,
...
};
But I wonder if you really want the CloseDate to be ahead of today, normally a close date happens in the past, no?
And, careful with timezones as well. I tend to always insert/update the database with DateTime.UtcNow and when reading, apply the user timezone...
I am populating a class using Linq to SQL.
What I am trying to do is query my database, return two integer values and subtract the two values from each other, producing the result, but I can't think of a smart way to do it.
What can I do in this case ?
If it is not clear, , then this psuedocode implementation should clarify what functionality I wish for :
DECLARE #currentVal INT, #previousVal INT
#currentVal = SELECT VALUE
FROM Table1
WHERE Date = CURRDATE()
#previousVal = SELECT VALUE
FROM Table1
WHERE Date = MIN(Date)
RETURN #currentVal - #previousVal
But in Linq to SQL, (from o in context.Table1 where Date = currentDate select Value), how can I subtract the other value from this? Is this possible?
I'd stick to having it as a broken out set of queries, because you can then test if the values were actually returned or not and handle the case where too many values are returned:
var currentValResults = (from row in rows
where row.Date == DateTime.Now
select row.Value)
.ToArray();
var previousValResults = (from row in rows
let minDate = rows.Min(r => r.Date)
where row.Date == minDate
select row.Value)
.ToArray();
if (currentValResults.Length == 1 && previousValResults.Length == 1)
{
var diff = currentValResults[0] - previousValResults[0];
}
else
{
// Error condition?
}
Putting it all into a giant linq statement makes too many assumptions (or at least, my implementation does).
Why not simply do a cross join
var query=
from a in Table1 where a.Date == DateTime.Now
from b in Table1 where b.Date == Table1.Min(c=>c.Date)
select a.Amount - b.Amount;
var result=query.First();
Something like this would work to keep it into one trip to the db (Keep in mind this assumes that only two results will be returned):
int[] values = (from o in context.Table1
where Date = currentDate || Date = context.Table1.Min(x => x.Date)
order by Date descending
select value).ToArray();
return values[0] - values[1];
var currentVal = context.Table1.FirstOrDefault(t=>t.Date == DateTime.Now.Date);
var previousVal = context.Table1.FirstOrDefault(t=>t.Date == context.Table1.Min(d=>d.Date));
var result = currentVal - previousVal;
Or
from d in context.Table1
let prevVal = context.Table1.FirstOrDefault(t=>t.Date == context.Table1.Min(d=>d.Date));
where d.Date == DateTime.Now.Date
return new { d - prevVal };
I have the following query that returns the login count per day from a given date.
var sot = from uts in DataContext.UserTrackingStatistics
let startDate = new DateTime(2009, 10, 01)
where uts.LastLogin >= startDate
group uts by uts.LastLogin.Date into myGroup
orderby myGroup.Key.Date
select new { Count = myGroup.Count() , myGroup.Key.Date};
I would like this to say the count was 0 for a given day rather than not return anything. How could I do that within this query?
You can't do it just with LINQ-to-SQL, as you'd have to use a union on your query with data that doesn't actually exist, which LINQ-to-SQL can't do.
To do this, you'll need to fill in the gaps client-side. I'm not in front of VS at the moment, but a general approach would be this:
Define your date range (since you mention no end date in your code and we're talking about login date, I'm assuming that the end date would be the current date.
Use Enumerable.Range to create a list of numbers ranging from 0 to the number of days within your date range, then use Select to transform that list into a list of dates. Select your results using an anonymous type and use the same properties as your L2S statement; this way, the compiler will reuse the same type
Combine your lists together using an outer join (not the most obvious syntax in LINQ, unfortunately) on the Date property
Order your results by date
This will now show 0 for the gaps.
I'll try to post a code sample below, but note that I can't compile where I am, so it may require tweaking.
var allDates = Enumerable.Range(0, (DateTime.Today - startDate).TotalDays)
.Select(i => new { Count = 0, Date = startDate.AddDays(i) });
var fullResults = from d in allDates
join r in results on d.Date == r.Date
from oj in r.DefaultIfEmpty()
select new { Count = oj == null ? 0 : oj.Count, Date = d.Date };