9月29日
/*
* A deceptively ~simple self-join query to find all date ranges in a table that overlap.
* A date range is defined by the range between InTime and OutTime.
* The "Seq != Seq" filter is required so that each record does not match itself and
* Distinct is required to remove duplicates so we end up with only records
* that overlap date ranges.
* Which means (StartA <= EndB) And (EndA >= StartB)
*/
var rr = (
from t1 in db.TimeLogs
from t2 in db.TimeLogs
where t1.Seq != t2.Seq
where t1.InTime <= t2.OutTime && t1.OutTime >= t2.InTime
select t1).Distinct();
ObjectDumper.Write(rr);
Data:
Output:
Seq=26 EmpID=10 InTime=9/20/2007 OutTime=9/20/2007 Employee={ } TotalHours=4 Duration=04:00:00
Seq=27 EmpID=10 InTime=9/20/2007 OutTime=9/20/2007 Employee={ } TotalHours=3 Duration=03:00:00
Seq=28 EmpID=10 InTime=9/20/2007 OutTime=9/20/2007 Employee={ } TotalHours=2 Duration=02:00:00
CLR Objects
Here is a generic static method to do this with any IEnumerable<T>. To be generic, our function needs a some help from caller. We need to know what the Start and End dates are in the object. We could handle this by interface or by Functions. The caller can pass simple lambda expressions. We also added a filter function that allows you to pick from the list the record or records you want to restrict matches to. You could add another overload and hide the filter or just pass a lambda that returns true for anything. Example: (T t)=>true
// Takes a list and returns all records that have overlapping time ranges.
public static IEnumerable<T> GetOverlappedTimes<T>(IEnumerable<T> list, Func<T, bool> filter, Func<T,DateTime> start, Func<T, DateTime> end)
{
// Selects all records that match filter() on left side and returns all records on right side that overlap.
var overlap = from t1 in list
where filter(t1)
from t2 in list
where !object.Equals(t1, t2) // Don't match the same object on right side.
let in1 = start(t1)
let out1 = end(t1)
let in2 = start(t2)
let out2 = end(t2)
where in1 <= out2 && out1 >= in2
let totover = GetMins(in1, out1, in2, out2)
select t2;
return overlap;
}
public static void TestOverlap()
{
var tl1 = new TempTimeEntry() { ID = 1, Name = "Bill", In = "1/1/08 1:00pm".ToDate(), Out = "1/1/08 4:00pm".ToDate() };
var tl2 = new TempTimeEntry() { ID = 2, Name = "John", In = "1/1/08 5:00pm".ToDate(), Out = "1/1/08 6:00pm".ToDate() };
var tl3 = new TempTimeEntry() { ID = 3, Name = "Lisa", In = "1/1/08 7:00pm".ToDate(), Out = "1/1/08 9:00pm".ToDate() };
var tl4 = new TempTimeEntry() { ID = 4, Name = "Joe", In = "1/1/08 3:00pm".ToDate(), Out = "1/1/08 8:00pm".ToDate() };
var tl5 = new TempTimeEntry() { ID = 1, Name = "Bill", In = "1/1/08 8:01pm".ToDate(), Out = "1/1/08 8:00pm".ToDate() };
var list = new List<TempTimeEntry>() { tl1, tl2, tl3, tl4, tl5 };
var overlap = GetOverlappedTimes(list, (TempTimeEntry t1)=>t1.ID==1, (TempTimeEntry tIn) => tIn.In, (TempTimeEntry tOut) => tOut.Out);
Console.WriteLine("\nRecords overlap:");
foreach (var tl in overlap)
Console.WriteLine("Name:{0} T1In:{1} T1Out:{2}", tl.Name, tl.In, tl.Out);
Console.WriteLine("Done");
/* Output:
Records overlap:
Name:Joe T1In:1/1/2008 3:00:00 PM T1Out:1/1/2008 8:00:00 PM
Name:Lisa T1In:1/1/2008 7:00:00 PM T1Out:1/1/2008 9:00:00 PM
Done
*/
}
One of the cooler things about Linq (if it wasn't already cool enough) is that queries
are composable. This comes in real handy when you want to dynamically add "where"
statements to a query based on some runtime criteria.
Data:
var q1 = from t1 in db.TimeLogs
where t1.EmpID == 10
where t1.InTime.Value.Hour == 15 // Same as using &&, but just shows multiple where statements.
select t1;
if (true) // If condition is true, add another where filter.
q1 = from t1 in q1
where t1.OutTime.Value.Hour > 12 // Remember Hour is 0-23
select t1;
ObjectDumper.Write(q1);
The resulting SQL looks like so:
SELECT [t0].[Seq], [t0].[EmpID], [t0].[InTime], [t0].[OutTime]
FROM [dbo].[TimeLogs] AS [t0]
WHERE (DATEPART(Hour, [t0].[OutTime]) > @p0) AND (DATEPART(Hour, [t0].[InTime]) = @p1) AND ([t0].[EmpID] = @p2)
-- @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [12]
-- @p1: Input Int32 (Size = 0; Prec = 0; Scale = 0) [15]
-- @p2: Input Int32 (Size = 0; Prec = 0; Scale = 0) [10]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1
Seq=28 EmpID=10 InTime=9/20/2007 OutTime=9/20/2007
Nice feature.