C#: Groupby, Difference of data in dataset - c#

Following is my dataset which I get from the database:
Id -- Date -- ClockIn -- ClockOut
1 -- 1/1/2016 -- 1/1/2016:09:00:00 -- 1/1/2016:17:03:00
1 -- 1/2/2016 -- 1/1/2016:09:00:00 -- 1/1/2016:11:30:00
1 -- 1/2/2016 -- 1/1/2016:13:00:00 -- 1/1/2016:15:03:00
Expected result
1 -- 1/1/2016(8 hrs) -- 09:00:00 -- 17:03:00
1 -- 1/2/2016(4 hrs) -- 09:00:00 -- 11:30:00
-- 13:00:00 -- 15:03:00
Question: I want a group-by on Date field and difference of clockIn/out and its sum for the day
Here is the code what I am trying but not able to get it work:
var resultSet = from newSet in Dtinfo.AsEnumerable()
group newSet by new
{
uid = newSet.Field<int>("UID"),
gDate = newSet.Field<DateTime>("wDate"),
inTime = newSet.Field<DateTime>("punchIn"),
outTime = newSet.Field<DateTime>("punchOut"),
location = newSet.Field<String>("locName"),
typeName = newSet.Field<String>("typeName"),
} into counter
select new
{
UID = counter.Key.uid,
wDate=counter.Key.gDate,
punchIn = counter.Key.inTime,
punchOut = counter.Key.outTime,
locName= counter.Key.location,
typeName= counter.Key.typeName,
diff = (counter.Key.outTime - counter.Key.InTime).TotalHours,
};
I am getting the difference but I need the group by and sum to work as well.
Any light on the path would be helpful.

here is the query.
I am returning anonymous class with
Date - date which was grouped by
TotalHours - sum of all entries for that day
Entries - entries, another anonymous class
It is probably better to replace them with named classes.
var res = from newset in Dtinfo.AsEnumerable()
group newset by newset.Field<DateTime>("wDate")
into counter
select new
{
Date = counter.Key,
TotalHours = counter.Select(a => a.Field<DateTime>("punchOut") - a.Field<DateTime>("punchIn")).Sum(a => a.TotalHours),
Entries = counter.Select(a => new
{
uid = a.Field<int>("UID"),
gDate = a.Field<DateTime>("wDate"),
/*other fields here
*
*
*/
})
};

Related

Convert SQL Statement into Linq expression

I am quite new to linq and .net core. I am trying to calculate the next tax return date of a company as a part of my final year’s project.
If there is a newly made company with no tax has been made yet (means no entry in the tax table), Then add 18 months in the company’s incorporated date.
If the company has already paid tax, then pick the latest date TaxReturnDate from tax table and add 9 months into that to get the next TaxReturnDate.
Thats what i have tried in SQL, now i am trying to convert this sql into Linq Query, I need some help to get the desired results.
WITH
cte_company (CompanyID, CompanyName, CompanyNumber, IncorporatedDate, TOTAL_YEARS) AS
(SELECT
CompanyID,
CompanyName,
CompanyNumber,
IncorporatedDate,
DATEDIFF(YEAR, IncorporatedDate, CURRENT_TIMESTAMP) AS TOTAL_YEARS
FROM tbl_Company)
SELECT
cte_company.CompanyID,
CompanyName,
CompanyNumber,
IncorporatedDate,
TOTAL_YEARS,
CASE
WHEN TOTAL_YEARS > 1 THEN (SELECT
DATEADD(MONTH, 9, MAX(TaxReturnDate))
FROM tbl_Tax
WHERE cte_company.CompanyID = tbl_Tax.CompanyID)
ELSE DATEADD(MONTH, 18, IncorporatedDate)
END AS TaxDate
FROM cte_company
Linq Query
IEnumerable<CompanyTaxInfo> result =
from c in this.AcmeDB.tbl_Company
let TotalYears = (DateTime.Now - c.IncorporatedDate).Value.Days / 365
let taxReturnDate = this.AcmeDB.tbl_Tax.Max(tx => tx.TaxReturnDate).Value.AddMonths(9)
select new CompanyTaxInfo
{
CompanyID = c.CompanyID,
CompanyName= c.CompanyName,
CompanyNumber= c.CompanyNumber,
IncorporatedDate= c.IncorporatedDate,
TotalYears = TotalYears,
TaxDate = TotalYears > 1 ? taxReturnDate : c.IncorporatedDate.Value.AddMonths(21)
};
return result;
code is throwing DbArithmeticExpression arguments must have a numeric common type.' exception.
Please help
Try the following query:
var query =
from c in this.AcmeDB.tbl_Company
let TotalYears = EF.Functions.DateDiffYear(c.IncorporatedDate, DateTime.Now)
select new CompanyTaxInfo
{
CompanyID = c.CompanyID,
CompanyName = c.CompanyName,
CompanyNumber = c.CompanyNumber,
IncorporatedDate = c.IncorporatedDate,
TotalYears = TotalYears,
TaxDate = TotalYears > 1 ? this.AcmeDB.tbl_Tax
.Where(tax => c.CompanyId == tax.CompanyId)
.Max(tx => tx.TaxReturnDate).Value.AddMonths(9)
: c.IncorporatedDate.Value.AddMonths(18)
};
return query.ToList();

Order by descending group by Max date, then order by single row in the group by date ascending using linq Method sintax

I have a table like this
Id Date GroupId Text Column1 Column2 ...
1 2020-02-02 1 .... .... ...
2 2020-02-04 1 .... .... ...
3 2020-02-03 1 .... .... ...
4 2020-02-02 2 .... .... ...
5 2020-02-05 2 .... .... ...
I need to obtain this result:
Id Date GroupId Text Column1 Column2 ...
5 2020-02-05 2 .... .... ...
4 2020-02-02 2 .... .... ...
1 2020-02-02 1 .... .... ...
3 2020-02-03 1 .... .... ...
2 2020-02-04 1 .... .... ...
I explain I need to get before all rows of the group 2 because the max date is in the group 2... I need to order the group by date descending but every single group should be ordered by date ascending.
I find difficult to write it in sql too. Can anyone help me please?
Thank you
EDIT:
Here what I have tried to do:
var result = messages.Select(m => new MyViewModel()
{
Id = m.Id,
Date = m.Date,
Text = m.Text,
GroupId = m.GroupId
}).GroupBy(d => d.GroupId)
.SelectMany(g => g)
.OrderByDescending(x => x.GroupId)
.ThenBy(x => x.Date);
But it does not work
I suggest creating an intermediate result having a GroupDate property. In my example I am using C# 7.0 Tuples. You could use anonymous types as well, if you are using an older C# version.
var result = messages
.GroupBy(m => m.GroupId) // Create Tuple ...
.Select(g => (Group: g, GroupDate: g.Max(m => m.Date))) // ... with group and group date.
.SelectMany(a => // Expand the group
a.Group.Select(m => // Create new tuple with group date and model.
(a.GroupDate,
Model: new MyViewModel() {
Id = m.Id,
Date = m.Date,
Text = m.Text,
GroupId = m.GroupId
})))
.OrderByDescending(x => x.GroupDate)
.ThenBy(x => x.Model.Date)
.Select(x => x.Model); // Extract the view model from the tuple.
Result:
Id = 2, Date = 2020-02-02, GroupId = 2
Id = 2, Date = 2020-02-05, GroupId = 2
Id = 1, Date = 2020-02-02, GroupId = 1
Id = 1, Date = 2020-02-03, GroupId = 1
Id = 1, Date = 2020-02-04, GroupId = 1
Example for tuple:
var t = (X: 15, Name: "axis");
Print($"X = {t.X}, Name = {t.Name}");
The name of a tuple property can also be inferred like in (a.GroupDate, Model: ...). The first property will automatically be called GroupDate. The second is named explicitly Model.
In SQL, you can use window functions:
order by
max(date) over(partition by groupId),
case when max(date) over(partition by groupId) = max(date) over() then date end desc,
date
You want to order the groups by their max date. Then within each group, order by the date descending.
I would recommend these keys for the order by:
maximum date for each group (desc)
groupId to keep each group together (order doesn't matter)
flag to put the overall maximum date first
date for all other rows (asc)
So:
order by max(date) over (partition by groupId) desc,
groupId, -- put each group together
(case when date = max(date) over() then 1 else 2 end),
date

Speed up the linq group by statement

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.

How to sum a field grouped by another in LINQ?

I am trying to find a away to SUM all the QUANTITY for a specific RECIPE (all its ingredients) into a single value to get the TOTAL QUANTITY
Assuming I have the following dataset:
RecipeName IngredientName ReceiptWeight
Food1 Ingredient1 5
Food1 Ingredient2 2
Food2 Ingredient1 12
Food2 Ingredient3 1
And I would expect to get the following:
RecipeName ReceiptWeight
Food1 7
Food2 13
The code I have so far is:
Grouping =
(
from data in dataset
group data by data.RecipeName into recipeGroup
let fullIngredientGroups = recipeGroup.GroupBy(x => x.IngredientName)
select new ViewFullRecipe()
{
RecipeName = recipeGroup.Key,
ReceiptWeight = ????
How can I get the value for RecipeWeight?
Thanks,
LINQ does have sum
from d in dataset
group d by new { d.RecipeName } into g
select new {
g.Key.RecipeName,
ReceiptWeight = g.sum(o => o.ReceiptWeight)
}

How to perform aggregate function on last 4 rows of data?

I've got a table off the following model.
public class WeeklyNums
{
public int FranchiseId { get; set; }
public DateTime WeekEnding { get; set; }
public decimal Sales { get; set; }
}
I need a fourth column that calculates the minimum for this week and the previous three weeks. So the output would look like this.
1 7-Jan $1
1 14-Jan $2
1 21-Jan $3
1 28-Jan $4 **1**
1 4-Feb $4 **2**
1 11-Feb $6 **3**
1 18-Feb $4 **4**
1 25-Feb $8 **4**
1 3-Mar $7 **4**
I have no idea where to even start. Even some help with solving it in SQL would be helpful.
thx!
Consider using outer apply:
select yt1.*
, hist.four_week_min
from YourTable yt1
outer apply
(
select min(col1) as four_week_min
from YourTable yt2
where yt2.dt between dateadd(wk, -3, yt1.dt) and yt1.dt
) hist
Working example at SQL Fiddle.
var runningMins = from weekNum in data
select new
{
FranchiseId = weekNum.FranchiseId,
WeekEnding = weekNum.WeekEnding,
Sales = weekNum.Sales,
LastThreeWeeks = data.OrderByDescending( x => x.WeekEnding )
.Where( x => x.WeekEnding <= weekNum.WeekEnding )
.Take( 4 )
.Min( x => x.Sales )
};
SQL Query that will return minimum of the current and the three previous regardless of whether the dates are exactly three weeks apart:
With RnkItems As
(
Select DateVal, Sales
, Row_Number() Over ( Order By DateVal ) As Rnk
From SourceData
)
Select *
, (
Select Min(Sales)
From RnkItems As R1
Where R1.Rnk Between R.Rnk - 3 And R.Rnk
)
From RnkItems R
Order By 1
SQL Fiddle version
I know I'm too late, but here's the linq version:
var result = from w1 in db.Table
from w2 in db.Table.Where(x => x.WeekEnding >= w1.WeekEnding.AddDays(-28))
select new
{
FranchiseId = w1.FranchiseId,
WeekEnding = w1.WeekEnding,
Sales = w1.Sales,
SalesMin = w2.Min(x => x.Sales)
};

Categories