Check if a table contains overlapping timespans - c#

I have a datatable with two columns FromDate and ToDate , which are in string format.
I want to check if there are any duplicate records in my table.i.e
From Date To Date
----------------------
9/01/2012 9/16/2012
8/23/2012 8/24/2012
8/25/2012 8/25/2012
8/5/2012 8/6/2012
8/26/2012 8/27/2012
9/15/2012 9/23/2012
The table contains duplicate records as their date range is mapping for
From Date To Date
----------------------
9/01/2012 9/16/2012
9/15/2012 9/23/2012
It should return false.

var query = from row in dt.AsEnumerable()
from row1 in dt.AsEnumerable()
where
(
(
DateTime.Parse(row1.Field<string>("fromDate")) >= DateTime.Parse(row.Field<string>("fromDate")) &&
DateTime.Parse(row1.Field<string>("fromDate")) <= DateTime.Parse(row.Field<string>("toDate"))
)
||
(
DateTime.Parse(row1.Field<string>("toDate")) >= DateTime.Parse(row.Field<string>("fromDate")) &&
DateTime.Parse(row1.Field<string>("toDate")) <= DateTime.Parse(row.Field<string>("toDate"))
)
)
select new
{
fromDate = DateTime.Parse(row1.Field<string>("fromDate")),
toDate = DateTime.Parse(row1.Field<string>("toDate"))
};
//This lst contains the dates which are overlapping
var lst = query.Distinct().ToList();

Okay then, a selfjoin will help here:
I have a small class TimePeriod, just to meet your needs
public class TimePeriod
{
public int Id;
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public static DateTime Parse(string date)
{
var dt = DateTime.Parse(date,
CultureInfo.CreateSpecificCulture("en-US"), DateTimeStyles.RoundtripKind);
return dt;
}
}
then I have some TestData
var list = new List();
list.Add(new TimePeriod() { Id = 1, FromDate = TimePeriod.Parse("9/01/2012"), ToDate = TimePeriod.Parse("9/16/2012") });
list.Add(new TimePeriod() { Id = 2, FromDate = TimePeriod.Parse("8/23/2012"), ToDate = TimePeriod.Parse("8/24/2012") });
list.Add(new TimePeriod() { Id = 3, FromDate = TimePeriod.Parse("8/25/2012"), ToDate = TimePeriod.Parse("8/25/2012") });
list.Add(new TimePeriod() { Id = 4, FromDate = TimePeriod.Parse("8/5/2012"), ToDate = TimePeriod.Parse("8/6/2012") });
list.Add(new TimePeriod() { Id = 5, FromDate = TimePeriod.Parse("8/26/2012"), ToDate = TimePeriod.Parse("8/27/2012") });
list.Add(new TimePeriod() { Id = 6, FromDate = TimePeriod.Parse("9/15/2012"), ToDate = TimePeriod.Parse("9/23/2012") });
And here is the solution: (with some inspiration of OraNob, thanks for that)
var overlaps = from current in list
from compare in list
where
(
(compare.FromDate > current.FromDate &&
compare.FromDate < current.ToDate) ||
(compare.ToDate > current.FromDate &&
compare.ToDate < current.ToDate)
)
select new
{
Id1 = current.Id,
Id2 = compare.Id,
};
Perhaps you want to leave out the second Id (as you will have duplicates
here ( 1 / 6) and (6 / 1)

Sort by ToDate, FromDate (or build a sorted array of indexes into your DataTable). Loop from row or array position #2 to the end and see if the FromDate <= to the previous item's ToDate. Place overlapping items into a list. Job done.
You can also sort by FromDate, ToDate and do similar logic.

Try parsing the "To Date" column and for each, search the "From Date" column for any earlier date that has a later corresponding "To Date".

Use DataTable.Search() mehod to find out existence of any record in your DataTable this way you could enforce uniqueness in your records.
Something like this
string expression;
expression = "FromDate = #9/01/2012# AND ToDate = #9/16/2012#";
DataRow[] foundRows;
// Use the Select method to find all rows matching the filter.
foundRows = table.Select(expression);
if(foundRows.Length > 0)
// Show duplicate message
else
// Insert your new dates
For more Go here

If you handle large datatables you should use #ErikE response.
which needs more lines of code, but is definitely the most efficient by far.
If its small table and you prefer to compare each two rows.
you can use what other advised.
just make sure to prevent row from being compared to itself, and duplicates in the result enumeration.
var query = from x in list
where list.Exists((y) => x != y &&
x.FromDate <= y.ToDate &&
y.FromDate <= x.ToDate)
select x;

strong textDeclare #Table Table
(
RowId Int Identity(1, 1) Not Null,
Id NChar(3) Not Null,
StartDate DATETIME Not Null,
EndDate DATETIME Not Null
);
Insert Into #Table (Id, StartDate, EndDate)
Select 'id1', '20131210 10:10', '20131220 10:10' Union All
Select 'id1', '20131211', '20131215' Union All
Select 'id1', '20131201', '20131205' Union All
Select 'id1', '20131206', '20131208' Union All
Select 'id1', '20131225 10:10', '20131225 10:11'
Select *
From #Table;
With Overlaps (OverlapRowId, BaseRowId, OStart, OEnd, BStart, BEnd)
As
(
Select Overlap.RowId, Base.RowId, Overlap.StartDate, Overlap.EndDate, Base.StartDate, Base.EndDate
From #Table As Base
Inner Join #Table As Overlap On Overlap.Id = Base.Id
Where (((Overlap.StartDate > Base.StartDate) And (Overlap.StartDate < Base.EndDate))
Or ((Overlap.StartDate = Base.StartDate) And (Overlap.EndDate > Base.EndDate)))
And (Base.RowId != Overlap.RowId)
)
-- Remove records that were found to cause overlap issues.
Delete T
From #Table As T
Inner Join
(
Select O.OverlapRowId
From Overlaps As O
Left Join Overlaps As Fp On Fp.OverlapRowId = O.BaseRowId
Where (Fp.OverlapRowId Is Null)
) As SubQuery On SubQuery.OverlapRowId = T.RowId;
-- Select the valid options.
Select RowId, Id, StartDate, EndDate
From #Table where StartDate<EndDate;
Go

Related

Linq searches for recent birthdays (within 15 days)

How to use Linq
A table
Field Birthday
Linq searches for recent birthdays (within 15 days)
from a in Employee where a.PositionStatus == true select new{ a.Name,a.Birthday}
Try below query
var fromdate = DateTime.Now;
var todate = DateTime.Now.AddDays(15);
var result =
(
from a in Employee
where a.PositionStuats==true && a.DateOfBirth.Value.Month >= fromdate.Month &&
a.DateOfBirth.Value.Month <= todate.Month &&
a.DateOfBirth.Value.Day >= fromdate.Day && a.DateOfBirth.Value.Day <= todate.Day
select new{ a.Name,a.Birthday}
).ToList();
Depending on your entity frame work version you can also replace a.DateOfBirth.Value.Month with a.DateOfBirth.Month.

Linq union two tables where on has an extra column

I am using a Union to combine two tables audit and message. They both have all the same column names except audit has an extra column called deleteDate. I am try to use a Union to merge both table where if deleteDate does not exist, than it sets the DeleteDate method to null. I commented out what I am trying to get to work. When I uncomment the code, I get an error. Is there a work around? Here is my code
var audit = from a in _Audit.GetAll()
.Where(a => a.PInt == pInt && a.CreateDate < startDate && (endDate == null || endDate > a.CreateDate))
select new
{
a.MInt,
a.MId,
a.Desc,
a.PString,
a.PType,
a.MType,
a.CreateDate,
// a.DeleteDate,
a.PInt
};
var message = from a in _Message.GetAll()
.Where(a => a.PartnerInt == partnerInt && a.CreateDate < startDate && (endDate == null || endDate > a.CreateDate))
select new
{
a.MInt,
a.MId,
a.Desc,
a.PString,
a.PType,
a.MType,
a.CDate,
a.PInt
};
var test = from a in audit.Union(message)
select new AuditMessagesGroup
{
MInt = a.Int,
MId = a.Id,
Desc = a.Desc,
PString = a.PString,
PType = a.PayloadType,
MessageType = a.MType,
CreateDate = a.CreateDate,
// DeleteDate = a.DeleteDate != null ? a.DeleteDate : null,
PInt = a.PInt
};
Here is the error
Error CS1929 'IQueryable<<anonymous type: int MInt, Guid
MId, string Desc, string PString, string PType,
MTypes MType, DateTime CreateDate, DateTime DeleteDate, int?
PInt>>' does not contain a definition for 'Union' and the best
extension method overload 'ParallelEnumerable.Union<<anonymous type: int
MInt, Guid MId, string Desc, string PString, string
PType, MTypes MType, DateTime CreateDate, int? PInt>>
(ParallelQuery<<anonymous type: int MInt, Guid MId, string
Desc, string PString, string PType, MTypes
MType, DateTime CreateDate, int? PInt>>, IEnumerable<<anonymous
type: int MInt, Guid MId, string Desc, string
PString, string PType, MTypes MType, DateTime
CreateDate, int? PInt>>)' requires a receiver of type
'ParallelQuery<<anonymous type: int MInt, Guid MId, string
Desc, string PString, string PType, MTypes
MType, DateTime CreateDate, int? PInt>>'
To make .Union(...) work the anonymous type must be exactly the same - and that requires it to have the exact same number of fields, with the exact same types, with the exact same names.
So, from your queries, I think you need this:
var audit =
from a in _Audit
.GetAll()
.Where(a => a.PInt == pInt)
.Where(a => a.CreateDate < startDate)
.Where(a => endDate == null || endDate > a.CreateDate)
select new
{
a.MInt,
a.MId,
a.Desc,
a.PString,
a.PType,
a.MType,
a.CreateDate,
a.DeleteDate,
a.PInt
};
var message =
from a in _Message
.GetAll()
.Where(a => a.PartnerInt == partnerInt)
.Where(a => a.CreateDate < startDate)
.Where(a => endDate == null || endDate > a.CreateDate)
select new
{
a.MInt,
a.MId,
a.Desc,
a.PString,
a.PType,
a.MType,
CreateDate = a.CDate,
DeleteDate = (DateTime?)null,
a.PInt
};
I can't tell for sure what the types are of each table, but hopefully that's pretty close.
It is possible that you'll need to pull the records in to memory using .ToArray() followed by a new .Select(...) to get the field types to align, but assuming they are already the same it should work fine.
NO you can't. Basic criteria on union is that column count must match b/w tables. You can generate a dummy column though to bypass. like
considering a.DeleteDate is DateTime in audit change your message to be
var message = from a in _Message.GetAll()
.Where(a => a.PartnerInt == partnerInt && a.CreateDate < startDate && (endDate == null || endDate > a.CreateDate))
select new
{
a.MInt,
a.MId,
a.Desc,
a.PString,
a.PType,
a.MType,
a.CDate,
DateTime.Now, // a dummy column
a.PInt
};
Then you can perform the UNION

How to use SkipWhile with multiple conditions

I'm trying to filter objects with SkipWhile but it does not evaluate multiple conditions.
Below is a sample code demonstrating the issue,
int[] numbers = { 1, 2, 3, 4, 5 };
var result = numbers.SkipWhile(n => n < 2 && n != 2).ToList();
This query selects 2,3,4,5, which omits the second condition (n != 2), when the first condition is true.
Is it possible to make the query evaluate both conditions?
Edit:
My actual condition is something like
... dateRanges
.OrderBy(d=>d.Sequence)
.SkipWhile(d => d.FromDate <= currentDate && d.ToDate >= currentDate)
.Skip(1).First();
which is operating on DateTime filed, to select next object in the list
Edit 2:
I have created a sample program, which is something similar to my actual code
Class to hold data,
public class DateRange
{
public int Sequence { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
}
Program
static void Main(string[] args)
{
var dateRanges = new List<DateRange>(){
new DateRange{Sequence = 1 , FromDate = new DateTime(2014,1,1), ToDate = new DateTime(2014,1,31)},
new DateRange{Sequence = 2 , FromDate = new DateTime(2014,2,1), ToDate = new DateTime(2014,2,28)},
new DateRange{Sequence = 3 , FromDate = new DateTime(2014,3,1), ToDate = new DateTime(2014,3,31)},
new DateRange{Sequence = 4 , FromDate = new DateTime(2014,4,1), ToDate = new DateTime(2014,4,30)},
new DateRange{Sequence = 5 , FromDate = new DateTime(2014,5,1), ToDate = new DateTime(2014,5,31)},
};
var myDate = new DateTime(2014, 2, 10); // A Date in Fabruary
//This query selects {2, 2014/2/1, 2014/2/28}
var selectedItem = dateRanges.OrderBy(d => d.Sequence)
.Where(d => d.FromDate <= myDate && d.ToDate >= myDate)
.First();
//What I actually need to select is {3, 2014/3/1, 2014/3/31}
//Which is next item of the list
//This is what i have tried
//But this query also selects {2, 2014/2/1, 2014/2/28}
var nextItem = dateRanges.OrderBy(d => d.Sequence)
.SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate)
.Skip(1).First();
//Because, results of this part of query returns objects from {1, 2014/1/1, 2014/1/31} ...
var unexpectdItems = dateRanges.OrderBy(d => d.Sequence)
.SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate);
}
It is evaluating both conditions - but as soon as the condition is false, the rest of the sequence is returned. As soon as n==2, n < 2 && n != 2 is false. In fact, your condition makes no sense anyway - if n is less than 2 it can't be equal to 2.
Basically it's not clear what you're trying to achieve, but the condition you're using isn't appropriate - and if you want to check your condition on every value rather than just "values until the condition isn't met" then you should use Where instead of SkipWhile.
EDIT: Now that you've posted a complete example, we can see what's wrong. Look at your condition:
SkipWhile(d => d.FromDate <= myDate && d.ToDate >= myDate)
Now look at the first item of your data:
new DateRange{Sequence = 1 , FromDate = new DateTime(2014,1,1),
ToDate = new DateTime(2014,1,31)},
And myDate:
var myDate = new DateTime(2014, 2, 10);
Is your condition satisfied by the first item of your data? No, because ToDate (January 31st) is not greater than or equal to myDate (February 10th). So no items are skipped by SkipWhile. Perhaps you wanted || instead of &&? (It's still not clear what this query is meant to achieve.)

linq-to-sql null or default

I have a query that looks like this:
var TheQuery = (from....
where x.TheDate >= StartDate && x.TheDate <= EndDate
select new MyModel()
{
Total = (int?)x.Count() ?? 0,
....
}).Single();
Basically, I'm querying a number of records based between 2 dates. If for the date there are 0 values, it returns 0 as the Total. However, if there are no values at all, it returns null and crashes. I could add .SingleOrDefault() but it would return null instead of MyModel populated with a 0. The property Total is defined as an int.
How can I solve this?
Thanks
Count has an overload with a predicate, and returns 0 when no item matches the predicate
var result = new MyModel {
Total = <yourDataSource>
.Count(x.TheDate >= StartDate && x.TheDate <= EndDate)
};
if(TheQuery !=null || TheQuery .Count()>0){
//do something you wanna do
}
or
var v = TheQuery.ToList();
now check
if (v.Count > 0)
You should opt for:
int count = (from x in ...
where x.TheDate >= StartDate && x.TheDate <= EndDate
select c).Count();
That's what you want.
var TheQuery = (from....
where x.TheDate >= StartDate && x.TheDate <= EndDate
select new MyModel()
{
Total = (int?)x.Count() ?? 0,
....
}).DefaultIfEmpty(0).Single()'

How to merge 5 different type of linq queries into one based on a column

I am really confused on a report I need. As of today, most of my reports were simple, so I was able to do them myself easily. But being a newbie in sql/dlinq, I cannot find my way through the following:
var closingStock =
(from p in session.Query<Product>()
select new
{
p.Id,
p.Name,
p.Batch,
p.Rate,
ClosingStock = p.Quantity - p.AllocatedQuantity,
p.DivisionId
}).ToList();
var distributedQuantityAfterPeriod =
(from i in session.Query<OutwardInvoiceItem>()
where i.ParentInvoice.Date > ToDate
select new
{
Id = i.Product.Id,
DistributedAfter = i.Quantity
}).ToList();
var distributedQuantityInPeriod =
(from i in session.Query<OutwardInvoiceItem>()
where i.ParentInvoice.Date >= FromDate && i.ParentInvoice.Date <= ToDate
select new
{
Id = i.Product.Id,
Distributed = i.Quantity
}).ToList();
var receivedQuantityAfterPeriod =
(from i in session.Query<InwardInvoiceItem>()
where i.ParentInvoice.Date > ToDate
select new
{
Id = i.Product.Id,
ReceivedAfter = i.Quantity
}).ToList();
var receivedQuantityInPeriod =
(from i in session.Query<InwardInvoiceItem>()
where i.ParentInvoice.Date >= FromDate && i.ParentInvoice.Date <= ToDate
select new
{
Id = i.Product.Id,
Received = i.Quantity
}).ToList();
As you can see, I am trying to build a inventory movement report for a specific date. I have the following problems:
1. How can I reduce the five queries? Is it possible?
2. How can I merge the data provided by these queries into one table which is grouped on the product id and summed on the quantity related columns? As of now, I am using for loops which are really slow.
What I am using:
C# 4, nHibernate, Sqlite
Any help will be very highly appreciated.
Regards,
Yogesh.
to reduce roundtrips use .Future() instead of .List()
let all queries return
group i by i.Id into g
select new
{
Id = g.Key,
Quantity = g.Sum(x => x.Quantity)
}).Future();
and do
var alltogether = groupedDistributedQuantityAfterPeriod
.Concat(groupedDistributedQuantityInPeriod)
.Concate(...);
from g in alltogether
group g by g.key into all
select new
{
Id = all.Key,
Quantity = all.Sum(x => x.Quantity)
};
Update:
you can reduce the number of queries with
from i in session.Query<OutwardInvoiceItem>()
where (i.ParentInvoice.Date > ToDate) || (i.ParentInvoice.Date >= FromDate && i.ParentInvoice.Date <= ToDate)
select ...
from i in session.Query<InwardInvoiceItem>()
where (i.ParentInvoice.Date > ToDate) || (i.ParentInvoice.Date >= FromDate && i.ParentInvoice.Date <= ToDate)
select ...

Categories