How to use Linq to create unique Collection which contains a collection - c#

I am retrieving records from a db and creating the following object:
public class RemittanceBatchProcessingModel
{
public string FileId { get; set; }
public string SourceFileName { get; set; }
public string BatchCode { get; set; }
public string BatchType { get; set; }
public decimal PaymentAmount { get; set; }
public string BillingSystemCode { get; set; }
}
Example objects created after db read:
FileId | SourceFileName | BatchCode | BatchType | PaymentAmt |BillingCode
1 | test.file1.txt | 100 | S | 1000.00 | Exc
1 | test.file1.txt | 100 | S | 2000.00 | Exc
1 | test.file1.txt | 200 | N | 500.00 | Adc
2 | test.file2.txt | 300 | S | 1200.00 | Exc
2 | test.file2.txt | 300 | S | 1500.00 | Exc
I want to create an object that has a collection of the unique files which has a collection of each summarized batch within a file. For example,
Collection of Unique Files:
FileId | SourceFileName | BatchCode | BatchType | BatchTotal |RecordCount
1 | test.file1.txt | 100 | S | 3000.00 | 2
1 | test.file1.txt | 200 | N | 500.00 | 1
2 | test.file2.txt | 100 | S | 1700.00 | 2
I am able to create my collection of batches with no issue the problem I'm having is figuring out how to create the collection of unique files with the correct batches within them. I'm attempting this using the following:
private static RemittanceCenterFilesSummaryListModel SummarizeFiles(RemittanceCenterSummaryListModel remittanceCenterSummaryListModel)
{
var summarizedBatches = SummarizeBatches(remittanceCenterSummaryListModel);
var fileResult = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord.GroupBy(x => new { x.FileId, x.SourceFileName })
.Select(x => new RemitanceCenterFileSummarizedModel()
{
FileId = x.Key.FileId,
SourceFileName = x.Key.SourceFileName,
ScannedBatchCount = x.Count(y => y.BatchType == "S"),
ScannedBatchAmount = x.Where(y => y.BatchType == "S").Sum(y => y.PaymentAmount),
NonScannedBatchCount = x.Count(y => y.BatchType != "S"),
NonScannedBatchAmount = x.Where(y => y.BatchType != "S").Sum(y => y.PaymentAmount),
});
var summaryListModel = CreateSummaryFilesListModel(fileResult);
summaryListModel.Batches = summarizedBatches.RemittanceBatchSummary;
return summaryListModel;
}
private static RemittanceCenterFilesSummaryListModel CreateSummaryFilesListModel(IEnumerable<RemitanceCenterFileSummarizedModel> summaryModels)
{
var summaryModelList = new RemittanceCenterFilesSummaryListModel();
foreach (var summaryFileRec in summaryModels)
{
var summaryModel = new RemitanceCenterFileSummarizedModel
{
FileId = summaryFileRec.FileId.ToString(CultureInfo.InvariantCulture),
SourceFileName = summaryFileRec.SourceFileName.ToString(CultureInfo.InvariantCulture),
ScannedBatchCount = summaryFileRec.ScannedBatchCount,
ScannedBatchAmount = summaryFileRec.ScannedBatchAmount,
NonScannedBatchCount = summaryFileRec.NonScannedBatchCount,
NonScannedBatchAmount = summaryFileRec.NonScannedBatchAmount
};
summaryModelList.RemittanceFilesSummary.Add(summaryModel);
}
return summaryModelList;
}

You can group it on the 4 columns including BatchType and BatchCode as well and pick the Count and sum the Amount like :
var fileResult = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord
.GroupBy(x => new
{
x.FileId,
x.SourceFileName,
x.BatchType,
x.BatchCode
})
.Select(x => new
{
FileId = x.Key.FileId,
SourceFileName = x.Key.SourceFileName,
BatchType = x.Key.BatchType,
BatchCode = x.Key.BatchCode,
BatchTotal= x.Sum(y=>y.PaymentAmt),
RecordCount = x.Count()
});

I guess you need to GroupBy FileId & BatchType instead of FileName:-
var fileResult = remittanceCenterSummaryListModel.RemittanceBatchSummaryRecord
.GroupBy(x => new { x.FileId, x.BatchType })
.Select(x =>
{
var firstObj = x.FirstOrDefault();
return new RemitanceCenterFileSummarizedModel()
{
FileId = x.Key.FileId,
SourceFileName = firstObj.SourceFileName,
BatchCode = firstObj.BatchCode,
BatchType = x.Key.BatchType,
BatchTotal = x.Sum(z => z.PaymentAmt),
RecordCount = x.Count()
};
});
Considering FileId maps to SourceFileName & BatchCode maps to BatchType you can simply store the first set in a variable like I did in firstObj to get the relevant values which are not grouped. Please check for nulls before accessing relevant properties as it may cause NRE if no set is found.

For pure linq non fluent
var files = new[] {
new { FileId = 1, SourceFileName = "test.file1.txt" , BatchCode = 100 , BatchType = "S", PaymentAmt = 1000.00 , BillingCode = "Exc" },
new { FileId = 1, SourceFileName = "test.file1.txt" , BatchCode = 100 , BatchType = "S", PaymentAmt = 2000.00 , BillingCode = "Exc" },
new { FileId = 1, SourceFileName = "test.file1.txt" , BatchCode = 200 , BatchType = "N", PaymentAmt = 500.00 , BillingCode = "Adc" },
new { FileId = 1, SourceFileName = "test.file2.txt " , BatchCode = 300 , BatchType = "S", PaymentAmt = 1200.00 , BillingCode = "Exc" },
new { FileId = 1, SourceFileName = "test.file2.txt " , BatchCode = 300 , BatchType = "S", PaymentAmt = 1500.00 , BillingCode = "Exc" }
};
var result = from file in files
group file by new { file.FileId, file.BatchCode } into fileBachGroups
select new
{
FileId = 1,
SourceFileName = fileBachGroups.First().SourceFileName,
BatchCode = fileBachGroups.Key.BatchCode,
BatchType = fileBachGroups.First().BatchType,
BatchTotal = fileBachGroups.Sum(f => f.PaymentAmt),
RecordCount = fileBachGroups.Count()
};
Console.WriteLine("FileId | SourceFileName | BatchCode | BatchType | BatchTotal |RecordCount");
foreach (var item in result)
{
Console.WriteLine("{0} | {1} | {2} | {3} | {4} | {5}",item.FileId,item.SourceFileName, item.BatchCode, item.BatchType, item.BatchTotal, item.RecordCount);
}

Related

Linq Select First Active Record in a Set

I am trying to return the most recent revision of a document that is not in a cancelled status. However, if all the revision of the document are cancelled, it should still return the most revision in the cancelled status.
Data looks something like:
ID | Name | Rev | Status
00 | Manual | 000 | Active
00 | Manual | 001 | Active
00 | Manual | 002 | Active
00 | Manual | 003 | Active
00 | Manual | 004 | Active //return this one
ID | Name | Rev | Status
01 | Manual2 | 000 | Active
01 | Manual2 | 001 | Active
01 | Manual2 | 002 | Active
01 | Manual2 | 003 | Active //return this one
01 | Manual2 | 004 | Cancel
ID | Name | Rev | Status
02 | Manual3 | 000 | Cancel
02 | Manual3 | 001 | Cancel
02 | Manual3 | 002 | Cancel
02 | Manual3 | 003 | Cancel
02 | Manual3 | 004 | Cancel //return this one
I can group and sort the records easily. I can also filter our records with a cancelled status, but in the case of the 3rd data set, all documents are in cancelled status and I get an exception.
List<Records> r = records
.GroupBy(a => a.ID)
.Select(b => new Record
{
ID = b.Key,
Name = b.First().Name,
Rev = b.OrderByDescending(o => o.Rev)
.First(x=> x.status != "Cancel").Rev
}).ToList();
You're almost there with what you have, you can use ThenBy after an OrderBy to add subsequent ordering:
List<Records> r = records
.GroupBy(a => a.ID)
.Select(b => new Record
{
ID = b.Key,
Name = b.First().Name,
Rev = b.OrderBy(o => o.status == "Cancel")
.ThenByDescending(o => o.Rev)
.First().Rev
}).ToList();
NOTE: In this example I've ordered on status == "Cancel" as false < true.
EDIT
Based on the comments section requirement for extra statuses: you could create a function to convert the status into a numeric rank:
public int GetStatusRank(string status)
{
switch (status)
{
case "active":
return 0;
case "endorsed"
return 1;
case "cancelled":
return 2;
//etc...
default:
return int.MaxValue;
}
}
You could then use this in your OrderBy:
...b.OrderBy(o => GetStatusRank(o.status))
The following Query first orders the Items by Status and Rev. Then the group by picks every ID once. distinctRecord.First() returns the topmost item of that group, hence the ordering.
var query =
from record in records
orderby record.Status ascending, // Active before Cancel ('A' is alphabetically before 'C')
record.Rev descending // highest revision first
group record by record.ID
into distinctRecord
select distinctRecord.First();
var r = query.ToList();
The cool thing about this is, that you won't need to create a new instance of Record. Instead it brings back the actual Object from your Collection.
As you require to create new Instances of Record and not use references to the ones in the collection. You can do It like I've also explained in my comment:
var query =
from record in records
orderby record.Status ascending, // Active before Cancel ('A' is alphabetically before 'C')
record.Rev descending // highest revision first
group record by record.ID
into distinctRecord
select new Record {
ID = distinctRecord.Key,
Name = distinctRecord.First().Name,
Rev = distinctRecord.First().Rev,
Status = distinctRecord.First().Status
};
var r = query.ToList();
try this:
.First(x=> x.status != "Cancel" || x.status == "Cancel")
List<Records> r = records
.OrderByDescending(x => x.Status != "Cancel")
.ThenBy(r => r.Rev).ToList();
after (r=>r.Rev) you can Sorting with anything else what you need. Just use .ThenBy(...)
You can try this:
Create an anonymous object with the Revision object (Active or Cancel)
Create your colllection of Record using the Rev property.
var r = records.GroupBy(x => new {x.Id, x.Name})
.Select(x => new {
ID = x.Key.ID,
Name = x.Key.Name,
RevObj = x.OrderBy(y => y.Rev).LastOrDefault(y => y.Status != "Cancel") ??
x.OrderBy(y => y.Rev).Last(y => y.Status == "Cancel")
})
.ToArray()
.Select(x => new Record() {
ID = x.ID,
Name = x.Name,
Rev = x.RevObj.Rev
})
.ToList();
I would use reducing based on Aggregate-method:
var resul = list
.GroupBy(
k => k.ID,
(key, groups) =>
groups.Aggregate((accumulatedItem, item) =>
{
if (accumulatedItem.Status == item.Status)
{
return string.Compare(accumulatedItem.Rev, item.Rev) >= 0
? accumulatedItem
: item;
}
return accumulatedItem.Status == "Active"
? accumulatedItem
: item;
})
)
.ToArray();
var list = new[] {
new Record("00", "Manual", "000","Active"),
new Record("00", "Manual", "001","Active"),
new Record("00", "Manual", "002","Active"),
new Record("00", "Manual", "003","Active"),
new Record("00", "Manual", "004","Active"),
new Record("01", "Manual2", "000", "Active"),
new Record("01", "Manual2", "001", "Active"),
new Record("01", "Manual2", "002", "Active"),
new Record("01", "Manual2", "003", "Active"),
new Record("01", "Manual2", "004", "Cancel"),
new Record("02", "Manual3", "000", "Cancel"),
new Record("02", "Manual3", "001", "Cancel"),
new Record("02", "Manual3", "002", "Cancel"),
new Record("02", "Manual3", "003", "Cancel"),
new Record("02", "Manual3", "004", "Cancel")
};
public class Record
{
public Record(string id, string name, string rev, string status)
{
ID = id;
Name = name;
Rev = rev;
Status = status;
}
public string ID { get; set; }
public string Name { get; set; }
public string Rev { get; set; }
public string Status { get; set; }
}

get List<SomeClass> after .GroupBy()

Can someone explain me how i can use GroupBy with Select to get from this:
public class BadClass{
public int id; //can be grouped by id
public string name; //identical for the same Id
public bool flag; //identical for the same Id
public int bId;
public string bName;
public int cId;
public string cName;
}
This one: List <A> where:
public class A {
public int id;
public string name;
public bool flag;
public List<B> bList;
}
public class B {
public int id;
public string name;
public C c;
}
public class C {
public int id;
public int name;
}
I make example of data in BadClass:
List<BadClass> badClass = new List<BadClass>() {
new BadClass(){ id = 1, name = "A1", flag = true, bId = 1, bName = "B1", cId = 1, cName = "C1" },
new BadClass(){ id = 1, name = "A1", flag = true, bId = 2, bName = "B2", cId = 3, cName = "C2" },
new BadClass(){ id = 1, name = "A1", flag = true, bId = 3, bName = "B3", cId = 1, cName = "C1" },
new BadClass(){ id = 2, name = "A2", flag = false, bId = 4, bName = "B4", cId = 2, cName = "C2" },
new BadClass(){ id = 2, name = "A2", flag = false, bId = 5, bName = "B5", cId = 3, cName = "C3" }
};
/*
+----+------+-------+-----+-------+-----+-------+
| id | name | flag | bId | bName | cId | cName |
+----+------+-------+-----+-------+-----+-------+
| 1 | A1 | true | 1 | B1 | 1 | C1 |
+----+------+-------+-----+-------+-----+-------+
| 1 | A1 | true | 2 | B2 | 1 | C1 |
+----+------+-------+-----+-------+-----+-------+
| 1 | A1 | true | 3 | B3 | 3 | C3 |
+----+------+-------+-----+-------+-----+-------+
| 2 | A2 | false | 4 | B4 | 2 | C2 |
+----+------+-------+-----+-------+-----+-------+
| 2 | A2 | false | 5 | B5 | 3 | C3 |
+----+------+-------+-----+-------+-----+-------+
*/
I try to make something like that:
var result = badClass.GroupBy(x => x.id).Select(x => x. ???? ) ???? =(
but don't know how to make it right.
UPDATE:
var result = badClass
.GroupBy(x => x.id)
.Select(x => new A {
id = x.Key,
name = x.First().name,
flag = x.First().flag,
bList = x.ToList()
});
here is error in bList = x.ToList(); I need to change names bId to id e.t.c.
ANSWER:
var result = badClass
.GroupBy(x => x.id)
.Select(x => new A
{
id = x.Key,
name = x.First().name,
flag = x.First().flag,
bList = badClass.Where(y => y.id == x.Key).Select(y =>
new B { id = y.bId, name = y.bName, c = new C { id = y.cId, name = y.cName } }).ToList()
});
Just add a .Select and ToList which will has all the items you need
var result = badClass.GroupBy(x => x.id).Select(x => x.ToList());
var result = badClass
.GroupBy(x => x.id)
.Select(x => new A
{
id = x.Key,
name = x.First().name,
flag = x.First().flag,
bList = badClass.Where(y => y.id == x.Key).Select(y =>
new B { id = y.bId, name = y.bName, c = new C { id = y.cId, name = y.cName } }).ToList()
});
This should get you what you want
But to be honest I would prefer a foreach loop + Dictionary for readability.

Sort the two date columns individually and cumulative effect?

I have two date columns and two size columns (one size column related to one date column) like you can see in the Following table. Now I want the two arrays where in the first array it will be sort by Collected and show the cumulative effect of CollectedSize and same with Staged and StagedSize.
Required:
Collected array |
1/1/2016 | 1
11/1/2016 | 4
12/1/2016 | 6
30/1/2016 | 11
Staged array |
13/1/2016 | 3
14/1/2016 | 7
18/1/2016 | 13
16/1/2016 | 20
Table:
| Collected | CollectedSize | Staged | StagedSize |
| 11/1/2016 | 3 | 14/1/2016 | 4
| 12/1/2016 | 2 | 13/1/2016 | 3
| 30/1/2016 | 5 | 18/1/2016 | 7
| 01/1/2016 | 1 | 16/1/2016 | 6
Currently using the following code:
public class ProductionDataOverTimeVM
{
public ProductionDataOverTimeVM()
{
Collected = new List<TimeChartXAxis>();
Staged = new List<TimeChartXAxis>();
}
public List<TimeChartXAxis> Collected { get; set; }
public List<TimeChartXAxis> Staged { get; set; }
}
public class TimeChartXAxis
{
public string x { get; set; }
public string y { get; set; }
}
var queryResults = context.Datasets.ToList();
ProductionDataOverTimeVM obj = new ProductionDataOverTimeVM();
long? collectedBytes = 0;
long? Staged = 0;
foreach (var dataset in queryResults.OrderBy(d => d.Collected))
{
if (dataset.Collected != null)
{
collectedBytes = collectedBytes + dataset.CollectedSize;
obj.Collected.Add(new TimeChartXAxis
{
x = dataset.Collected != null ? BasicHelpers.FromUTCDate(dataset.Collected, parms.Offset).Value.ToString("dd/M/yyyy") : null,
y = BasicHelpers.FormatBytesToSpecificFormat(collectedBytes, format, false)
});
}
}
foreach (var dataset in queryResults.OrderBy(d => d.Staged))
{
if (dataset.Staged != null)
{
Staged = Staged + dataset.StagedSize;
obj.Staged.Add(new TimeChartXAxis
{
x = dataset.Staged != null ? BasicHelpers.FromUTCDate(dataset.Staged, parms.Offset).Value.ToString("dd/M/yyyy") : null,
y = BasicHelpers.FormatBytesToSpecificFormat(Staged, format, false)
});
}
}
What will be the best approach to do that?
What about
var arrayofOrderByString = new []{"Collected","Staged"}
foreach(var key in arrayofOrderByString){
var y=0;
SortList<Datasets>(queryResults, key, SortDirection.Descending);
queryResults.foreach(s =>{
y=s.GetType().GetProperty(key).GetValue(s, null);
obj.Collected.Add(new ProductionDataOverTimeVM{
x =BasicHelpers.FromUTCDate(s.GetType().GetProperty(key).GetValue(s, null), parms.Offset).Value.ToString("dd/M/yyyy"),
y=collectedBytes
})
})
}
public void SortList<T>(List<T> list, string columnName, SortDirection direction)
{
var property = typeof(T).GetProperty(columnName);
var multiplier = direction == SortDirection.Descending ? -1 : 1;
list.Sort((t1, t2) => {
var col1 = property.GetValue(t1);
var col2 = property.GetValue(t2);
return multiplier * Comparer<object>.Default.Compare(col1, col2);
});
}

C# linq query from sql server into one table

I have three tables. I would like to used C# linq to turn into one table.
For example:
Schedule:
+------+--------+--------+
| Name | DateId | TaskId |
+------+--------+--------+
| John | 2 | 32 |
| John | 3 | 31 |
| Mary | 1 | 33 |
| Mary | 2 | 31 |
| Tom | 1 | 34 |
| Tom | 2 | 31 |
| Tom | 3 | 33 |
+------+--------+--------+
Date:
+----+------------+
| Id | Date |
+----+------------+
| 1 | Monday |
| 2 | Tuesday |
| 3 | Wednesday |
| 4 | Thursday |
| 5 | Friday |
+----+------------+
Task:
+----+----------+
| Id | Task |
+----+----------+
| 31 | School |
| 32 | Homework |
| 33 | Break |
| 34 | Teaching |
+----+----------+
I would like to have a table like this:
+--------+----------+----------+-----------+----------+
| Person | Monday | Tuesday | Wednesday | Thursday |
+--------+----------+----------+-----------+----------+
| John | | Homework | School | |
| Mary | Break | School | | |
| Tom | Teaching | School | Break | |
+--------+----------+----------+-----------+----------+
I could not think of any good way doing this.
Any suggestion would be helpful
Thanks
Starting from this set of data:
var schedules = new[] { new { Name = "John", DateId = 2, TaskId = 32},
new { Name = "John", DateId = 3, TaskId = 31},
new { Name = "Mary", DateId = 1, TaskId = 33},
new { Name = "Mary", DateId = 2, TaskId = 31},
new { Name = "Tom", DateId = 1, TaskId = 34},
new { Name = "Tom", DateId = 2, TaskId = 31},
new { Name = "Tom", DateId = 3, TaskId = 33}
};
var dates = new[] { new { DateId = 1, Desc = "Monday"},
new { DateId = 2, Desc = "Tuesday"},
new { DateId = 3, Desc = "Wednesday"},
new { DateId = 4, Desc = "Thursday"},
new { DateId = 5, Desc = "Friday"}
};
var tasks = new[] { new { TaskId = 31, Desc = "School"},
new { TaskId = 32, Desc = "Homework"},
new { TaskId = 33, Desc = "Break"},
new { TaskId = 34, Desc = "Teaching"}
};
You can do as follows:
var result = schedules
// First you join the three tables
.Join(dates, s => s.DateId, d => d.DateId, (s, d) => new {s, d})
.Join(tasks, s => s.s.TaskId, t => t.TaskId, (sd, t ) => new { Person = sd.s, Date = sd.d, Task = t })
// Then you Group by the person name
.GroupBy(j => j.Person.Name)
// Finally you compose the final object extracting from the list of task the correct task for the current day
.Select(group => new
{
Person = group.Key,
Monday = group.Where(g => g.Date.DateId == 1).Select(g => g.Task.Desc).FirstOrDefault(),
Tuesday = group.Where(g => g.Date.DateId == 2).Select(g => g.Task.Desc).FirstOrDefault(),
Wednesday = group.Where(g => g.Date.DateId == 3).Select(g => g.Task.Desc).FirstOrDefault(),
Thursday = group.Where(g => g.Date.DateId == 4).Select(g => g.Task.Desc).FirstOrDefault(),
Friday = group.Where(g => g.Date.DateId == 5).Select(g => g.Task.Desc).FirstOrDefault()
})
.ToList();
If you want to select only some days, you can return an object containing a dictionary instead of an object with a property per day.
The dictionary will contain key-value pairs with the key representing the day and the value representing the task.
See the following code:
var filter = new[] {2, 3};
var filteredResult = schedules
.Join(dates, s => s.DateId, d => d.DateId, (s, d) => new{ s, d})
.Join(tasks, s => s.s.TaskId, t => t.TaskId, (sd, t) => new { Person = sd.s, Date = sd.d, Task = t })
.Where(x => filter.Contains(x.Date.DateId))
.GroupBy(x => x.Person.Name)
.Select(group => new
{
Person = group.Key,
TasksByDay = group.ToDictionary(o => o.Date.Desc, o => o.Task.Desc)
})
.ToList();
foreach (var item in filteredResult)
{
System.Console.WriteLine(item.Person);
foreach (var keyvaluepair in item.TasksByDay)
{
System.Console.WriteLine(keyvaluepair.Key + " - " + keyvaluepair.Value);
}
System.Console.WriteLine("---");
}
It is called "transpose".
var persons = new[] { new { name="John", dateId=2,taskId=32},
new { name="John", dateId=3,taskId=31},
new { name="Mary", dateId=1,taskId=33},
new { name="Mary", dateId=2,taskId=31},
new { name="Tom", dateId=1,taskId=34},
new { name="Tom", dateId=2,taskId=31},
new { name="Tom", dateId=3,taskId=33}
};
var dates = new[] { new { dateId=1, desc="Monday"},
new { dateId=2, desc="Tuesday"},
new { dateId=3, desc="Wednesday"},
new { dateId=4, desc="Thursday"},
new { dateId=5, desc="Friday"}
};
var tasks = new[] { new { taskId=31, desc="School"},
new { taskId=32, desc="Homework"},
new { taskId=33, desc="Break"},
new { taskId=34, desc="Teaching"}
};
var qry = from p in (from p in persons
join d in dates on p.dateId equals d.dateId
join t in tasks on (int)p.taskId equals (int)t.taskId
select new { name = p.name, monday = d.dateId == 1 ? t.desc : "", tuesday = d.dateId == 2 ? t.desc : "", wednesday = d.dateId == 3 ? t.desc : "", thursday = d.dateId == 4 ? t.desc : "", friday = d.dateId == 5 ? t.desc : "" })
group p by p.name into q
select new { q.Key, monday=q.Max(a => a.monday),tuesday=q.Max(a => a.tuesday), wednesday = q.Max(a=>a.wednesday), thursday = q.Max(a => a.thursday), friday=q.Max(a => a.friday)};
foreach ( var a in qry.ToList())
{
Console.WriteLine(String.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}",a.Key, a.monday, a.tuesday, a.wednesday, a.thursday, a.friday));
}

Complex SQL JOIN Query, min, max and date range

I have the following tables:
Readings:
+----+---------------------+-------+----------+
| Id | TimestampLocal | Value | Meter_Id |
+----+---------------------+-------+----------+
| 1 | 2014-08-22 18:05:03 | 50.5 | 1 |
| 2 | 2013-08-12 14:02:09 | 30.2 | 1 |
+----+---------------------+-------+----------+
Meters:
+----+--------+
| Id | Number |
+----+--------+
| 1 | 32223 |
+----+--------+
I need to select 2 readings for each meter, the reading with max DateTime and the reading with min DateTime, in addition to the difference between values of the two readings, something like this:
+----------+------------+----------------+------------+----------------+------------+
| Meter_Id | MaxReading | MaxReadingTime | MinReading | MinReadingTime | Difference |
+----------+------------+----------------+------------+----------------+------------+
I need a single query to achieve this for all meters within a date range in Entity Framework
i was able to get this far (get max and min readings):
SELECT
tt.*
FROM Readings tt
INNER JOIN
(
SELECT
Meter_Id,
MAX(TimeStampLocal) AS MaxDateTime,
MIN(TimeStampLocal) AS MinDateTime
FROM Readings
where TimeStampLocal > '2014-12-08'
GROUP BY Meter_Id
) AS groupedtt
ON (tt.Meter_Id = groupedtt.Meter_Id) AND
(tt.TimeStampLocal = groupedtt.MaxDateTime or tt.TimeStampLocal = groupedtt.MinDateTime)
order by Meter_Id;
Using this mockup of your actual schema and data:
class Reading
{
public int Id { get; set; }
public DateTime TimestampLocal { get; set; }
public double Value { get; set; }
public int Meter_Id { get; set; }
}
List<Reading> Readings = new List<Reading>()
{
new Reading { Id = 1, TimestampLocal = new DateTime(2014, 8, 22), Value = 50.5, Meter_Id = 1 },
new Reading { Id = 2, TimestampLocal = new DateTime(2013, 8, 12), Value = 30.2, Meter_Id = 1 },
new Reading { Id = 3, TimestampLocal = new DateTime(2013, 9, 12), Value = 35.2, Meter_Id = 1 }
};
using this linq query:
var q = from r in Readings
group r by r.Meter_Id into rGroup
select new
{
Meter_Id = rGroup.Key,
MaxReading = rGroup.OrderByDescending(x => x.TimestampLocal).First().Id,
MaxReadingTime = rGroup.OrderByDescending(x => x.TimestampLocal).First().TimestampLocal,
MinReading = rGroup.OrderBy(x => x.TimestampLocal).First().Id,
MinReadingTime = rGroup.OrderBy(x => x.TimestampLocal).First().TimestampLocal,
Difference = rGroup.OrderByDescending(x => x.TimestampLocal).First().Value -
rGroup.OrderBy(x => x.TimestampLocal).First().Value
};
produces this output:
[0] = { Meter_Id = 1, MaxReading = 1, MaxReadingTime = {22/8/2014 12:00:00 πμ},
MinReading = 2, MinReadingTime = {12/8/2013 12:00:00 πμ}, Difference = 20.3 }
which should be close to expected result.
EDIT:
You can considerably simplify the above linq query by making use of the let clause:
var q = from r in Readings
group r by r.Meter_Id into rGroup
let MaxReading = rGroup.OrderByDescending(x => x.TimestampLocal).First()
let MinReading = rGroup.OrderBy(x => x.TimestampLocal).First()
select new
{
Meter_Id = rGroup.Key,
MaxReading = MaxReading.Id,
MaxReadingTime = MaxReading.TimestampLocal,
MinReading = MinReading.Id,
MinReadingTime = MinReading.TimestampLocal,
Difference = MaxReading.Value - MinReading.Value
};
Probably not the most efficient, I admit, but that's the quickest I could come up with something without counter-verifying it myself.
SELECT m.Id AS Meter_Id, MaxReading, MaxReadingTime, MinReading, MinReadingTime, (MaxReading - MinReading) AS Difference
FROM Meters m
CROSS APPLY (SELECT MIN(Value) MinReading, TimestampLocal AS MinReadingTime FROM Readings WHERE Meter_Id = m.Id) min
CROSS APPLY (SELECT MAX(Value) MaxReading, TimestampLocal AS MaxReadingTime FROM Readings WHERE Meter_Id = m.Id) max
edit: formatting.

Categories

Resources