I have a MySQL database with a table 'Clicks'. There's a 'Created' column (datetime) which I'd like to group by and select the year, month and day part. I want to count the records per day within a specific range of dates (startDate and endDate).
var query = from c in scope.Entities.Clicks
where c.Created >= startDate && c.Created <= endDate
group c by new {c.Created.Year, c.Created.Month, c.Created.Day}
into grouped
select new {
Year = grouped.Key.Year,
Month = grouped.Key.Month,
Day = grouped.Key.Day,
Clicks = grouped.Count()
};
This produces a bad query:
SELECT
`GroupBy1`.`K1` AS `C1`,
`GroupBy1`.`K2` AS `C2`,
`GroupBy1`.`K3` AS `C3`,
`GroupBy1`.`K4` AS `C4`,
`GroupBy1`.`A1` AS `C5`
FROM (SELECT
COUNT(1) AS `A1`
FROM `Click` AS `Extent1`
WHERE (`Extent1`.`Created` >= #p__linq__0) AND (`Extent1`.`Created` <= #p__linq__1)
GROUP BY
1,
YEAR(`Extent1`.`Created`),
MONTH(`Extent1`.`Created`),
DAY(`Extent1`.`Created`)) AS `GroupBy1`
With an error: MySql.Data.MySqlClient.MySqlException: Can't group on 'A1'
What do I do wrong? Or is this a MySql connector bug? I tried MySQL connectors 6.5.4 and 6.6.5
DJ KRAZE I tried your approach and it turns out that the double "select" is necessary to produce a correct query by EF. Thanks! I post the full answer here if anyone needs it.
I tested both ways and both work:
var subQuery = from c in scope.Entities.Clicks
where c.Created >= startDate && c.Created <= endDate
select new { c.Created.Year, c.Created.Month, c.Created.Day };
var query = from c in subQuery
group c by new {c.Year, c.Month, c.Day}
into grouped
select new {
Year = grouped.Key.Year,
Month = grouped.Key.Month,
Day = grouped.Key.Day,
Clicks = grouped.Count()
};
And
var query = scope.Entities.Clicks.Where(c => c.Created >= startDate && c.Created <= endDate)
.Select(c => new { c.Created.Year, c.Created.Month, c.Created.Day})
.GroupBy(c => new {c.Year, c.Month, c.Day})
.Select(grouped => new { Clicks = grouped.Count(), grouped.Key.Year, grouped.Key.Month, grouped.Key.Day});
These give the correct mysql query:
SELECT
1 AS `C1`,
`GroupBy1`.`K1` AS `C2`,
`GroupBy1`.`K2` AS `C3`,
`GroupBy1`.`K3` AS `C4`,
`GroupBy1`.`A1` AS `C5`
FROM (SELECT
`Project1`.`C1` AS `K1`,
`Project1`.`C2` AS `K2`,
`Project1`.`C3` AS `K3`,
COUNT(1) AS `A1`
FROM (SELECT
YEAR(`Extent1`.`Created`) AS `C1`,
MONTH(`Extent1`.`Created`) AS `C2`,
DAY(`Extent1`.`Created`) AS `C3`
FROM `Click` AS `Extent1`
WHERE (`Extent1`.`Created` >= #p__linq__0) AND (`Extent1`.`Created` <= #p__linq__1)) AS `Project1`
GROUP BY
`Project1`.`C1`,
`Project1`.`C2`,
`Project1`.`C3`) AS `GroupBy1`
(I still think this is a bug and my first query should work as well btw)
Related
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();
I need to count three values on a single table. In plain SQL, it is written like this way:
select
count (*) as num_products,
sum(case when CreatedAt > '{sql.ToSqlDate(_CreatedAfter)}' then 1 else 0 end) num_new,
sum(case when UpdatedAt > '{sql.ToSqlDate(_UpdatedAfter)}' then 1 else 0 end) num_updated
from
Products
While switching to EF Core, I tried to convert it to Linq, like this
var res = (from p in _db.Products
let total = _db.Products.Count()
let NewProducts = _db.Products.Count(s => s.CreatedAt > crDate.Date)
let UpdatedProducts = _db.Products.Count(s => s.UpdatedAt > updDate.Date)
select new { total, NewProducts, UpdatedProducts } );
var response = res.ToList();
but the resulting SQL query seems not optimized
SELECT
(SELECT COUNT(*) FROM [Products] AS [p0]) AS [total],
(SELECT COUNT(*) FROM [Products] AS [s]
WHERE [s].[CreatedAt] > '2019-07-31') AS [NewProducts],
(SELECT COUNT(*) FROM [Products] AS [s0]
WHERE [s0].[UpdatedAt] > '2019-07-01') AS [UpdatedProducts]
FROM
[Products] AS [p]
Maybe somebody can help to translate the original SQL query to linq?
tia
ish
A more literal translation of that query, that generates a query more likely to execute in a single scan of the target table would be:
var q =
from p in db.Products
select new
{
p.Id,
NewProduct = p.CreatedAt > DateTime.Parse("2019-07-31") ? 1 : 0,
UpdatedProduct = p.UpdatedAt > DateTime.Parse("2019-07-01") ? 1 : 0
} into counts
group counts by 1 into grouped
select new
{
ProductCount = grouped.Count(),
NewProductCount = grouped.Sum(r => r.NewProduct),
UpdatedProductCount = grouped.Sum(r => r.UpdatedProduct)
};
Which translates to something like:
SELECT COUNT(*) AS [ProductCount],
SUM([t].[NewProduct]) AS [NewProductCount],
SUM([t].[UpdatedProduct]) AS [UpdatedProductCount]
FROM (
SELECT [p].[Id], CASE
WHEN [p].[CreatedAt] > #__Parse_0
THEN 1 ELSE 0
END AS [NewProduct], CASE
WHEN [p].[UpdatedAt] > #__Parse_1
THEN 1 ELSE 0
END AS [UpdatedProduct], 1 AS [Key]
FROM [Products] AS [p]
) AS [t]
GROUP BY [t].[Key]
You do not need a from clause in your linq because you aren't not going over the rows. just use three statements:
var total = _db.Products.Count();
var NewProducts = _db.Products.Count(s => s.CreatedAt > crDate.Date);
var UpdatedProducts = _db.Products.Count(s => s.UpdatedAt > updDate.Date) ;
Okay...... SO I have a drop down list that work but i wont to spice it up adding more checks.... in the asp format i have managed to check date restriction but i need to count from another table results but in ASP not sure how to count.... Hes the ASP Version I made that works i've done an SQL On to Get an idea
Here is the LINQ version only thing don't work is the get number of tickets already assigned to timetable
var query = from table in db.Timetables
join tick in db.Tickets on table.ID equals tick.TimetableID
where table.Schedual_Date > DateTime.Today
&& table.Advance_Tickets > "NEEDS TO GET NUMBER OF TICKETS ASSIGNED TO TIMETABLE"
select table;
SQL VERSION
SELECT
COUNT(Tickets.TimetableID) AS NoTickets, * FROM Tickets
JOIN Timetable ON Tickets.TimetableID=Timetable.ID
Where timetable.Schedual_Date > (TODAYS DATE)
&& Timetable.Advance_Tickets > NoTickets
If you are looking LINQ equivalent for SQL query as below
SELECT * FROM
Timetable Timetable
Where timetable.Schedual_Date > getDate()
AND Timetable.Advance_Tickets >= (SELECT COUNT(1) FROM Tickets Tickets WHERE Tickets.TimetableID=Timetable.ID)
you can try
var query = from table in timeTables
where table.SchedualDate > DateTime.Today
&& table.AdvanceTickets > (tickets.Count(a => a.TimeTableId == table.Id))
select table;
Use .Count() function to get the count. In your case tick.Count() would help. for example,
var query = from table in db.Timetables
join tick in db.Tickets on table.ID equals tick.TimetableID
where table.Schedual_Date > DateTime.Today
&& table.Advance_Tickets > tick.Count()
select table;
Here's the query I'm trying to convert into Linq:
SELECT R.Code,
R.FlightNumber,
S.[Date],
S.Station,
R.Liters,
SUM(R.Liters) OVER (PARTITION BY Year([Date]), Month([Date]), Day([Date])) AS Total_Liters
FROM S INNER JOIN
R ON S.ID = R.SID
WHERE (R.Code = 'AC')
AND FlightNumber = '124'
GROUP BY Station, Code, FlightNumber, [Date], Liter
ORDER BY R.FlightNumber, [Date]
Thanks for any help.
UPDATE: Here is the Linq code I'm trying it on; I cannot make the OVER PARTITION by Date.
var test =
(from record in ent.Records join ship in ent.Ship on record.ShipID equals ship.ID
orderby ship.Station
where ship.Date > model.StartView && ship.Date < model.EndView && ship.Station == model.Station && record.FlightNumber == model.FlightNumber
group record by new {ship.Station, record.Code, record.FlightNumber, ship.Date, record.AmountType1} into g
select new { g.Key.Station, g.Key.Code, g.Key.FlightNumber, g.Key.Date, AmmountType1Sum = g.Sum(record => record.AmountType1) });
Execute query first without aggregation:
var test =
(from record in ent.Records join ship in ent.Ship on record.ShipID equals ship.ID
orderby ship.Station
where ship.Date > model.StartView && ship.Date < model.EndView && ship.Station == model.Station && record.FlightNumber == model.FlightNumber
select new {ship.Station, record.Code, record.FlightNumber, ship.Date, record.AmountType1};
Then calculate sum
var result =
from row in test
select new {row.Station, row.Code, row.FlightNumber, row.Date, row.AmountType1,
AmountType1Sum = test.Where(r => r.Date == row.Date).Sum(r => r.AmountType1) };
This should produce the same effect as database query. Code above may contain errors, because I wrote it only here.
I've answered a similar thread on: LINQ to SQL and a running total on ordered results
On that thread it was like this:
var withRuningTotals = from i in itemList
select i.Date, i.Amount,
Runningtotal = itemList.Where( x=> x.Date == i.Date).
GroupBy(x=> x.Date).
Select(DateGroup=> DateGroup.Sum(x=> x.Amount)).Single();
In you situation, you might have to join the two tables together first while grouping, then run the same concept above on the joined table result.
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));