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

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)
};

Related

convert two rows into one row in linq [duplicate]

This question already has answers here:
Is it possible to Pivot data using LINQ?
(7 answers)
Closed 2 months ago.
The query result is as follows:
name
CurencyName
Debtor
Creidtor
agent1
Currency1
20
0
agent1
Currency2
0
10
agent2
Currency1
0
50
agent2
Currency2
0
10
However, I want the result in the following format:
name
currency1Debtor
currency1Creidtor
currency2Debtor
currency2Creidtor
agent1
20
0
0
10
agent2
0
50
0
10
The TSQL that the linq would generate would be something like,
;WITH T1 AS
(
SELECT
Name,
Currency1 Currency1Debtor,
Currency2 Currency2Debtor
FROM
(
SELECT
Name,
CurrencyName,
Debtor
FROM
#Temp
) AS SourceTable
PIVOT
(
SUM(Debtor) FOR CurrencyName IN (Currency1, Currency2)
) AS PivotTable
),
T2 AS
(
SELECT
Name ,
Currency1 Currency1Creditor,
Currency2 Currency2Creditor
FROM
(
SELECT
Name,
CurrencyName,
creditor
FROM
#Temp
) AS SourceTable
PIVOT
(
SUM(creditor) FOR CurrencyName IN (Currency1, Currency2)
) AS PivotTable
)
SELECT
T1.*,
T2.Currency1Creditor,
T2.Currency2Creditor
FROM
T1
INNER JOIN
T2
ON T1.Name = T2.Name
You can do it like this, working here.
var results = d.GroupBy(
d => d.name,
d => new
{
d.name,
currency1Debtor = d.CurencyName == "Currency1" ? d.Debtor : 0,
currency1Creditor = d.CurencyName == "Currency1" ? d.Creditor : 0,
currency2Debtor = d.CurencyName == "Currency2" ? d.Debtor : 0,
currency2Creditor = d.CurencyName == "Currency2" ? d.Creditor : 0,
},
(name, g) => new
{
name,
currency1Debtor = g.Sum(d => d.currency1Debtor),
currency1Creditor = g.Sum(d => d.currency1Creditor),
currency2Debtor = g.Sum(d => d.currency2Debtor),
currency2Creditor = g.Sum(d => d.currency2Creditor),
});
We really need to know what your schema and table design is that produces these results.
grouping by agentname, might well be what you are looking for, but you likely also need to know more information. For example, having a column value as the group by name, I am not sure there is a syntax for that, which is valid?
You can sort of do what you are asking, if you know in advance precisely how many currencies you have in the system, and debitors and creditors. But you can't make it dynamically, as far as I know.
So basically what you are asking for can't be done. Unless you are fine with a static query, that can account ONLY for the currency, debitor and creditor values you know in advance you want to see.
You can see a related question here:
SQL row value as column name

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();

join table and get the record which has minimum value?

I have the following collection Model: Hotel
public class Hotel {
int HotelId {get;set;}
decimal Price {get;set;}
int vendorId {get;set;}
int vendorHotelId {get;set;}
}
The records will be like this
HotelId Price VendorId VendorHotelId
1 100 1 0
2 200 2 0
3 300 1 0
4 400 2 1
If the VendorHotelId is equal to HotelId then I need to select the record which has the cheapest price in LINQ.
I want the result like this
HotelId Price VendorId VendorHotelId
1 100 1 0
2 200 2 0
3 300 1 0
Can anyone help me to solve this query?
You can use a conditional expression to group based on your condition, then get the minimum price from each group, which will be the one hotel if no matching vendorHotelId exists.
var ans = (from h in hotels
let hasVendor = h.vendorHotelId > 0
group h by hasVendor ? h.vendorHotelId : h.HotelId into hg
let hmin = hg.OrderBy(h => h.Price).First()
select new {
HotelId = hmin.HotelId,
Price = hmin.Price,
vendorId = hmin.vendorId
})
.ToList();
Update: Since you seem to be using fluent syntax based on your comment, here is a translation:
var ans2 = hotels.Select(h => new { h, hasVendor = h.vendorHotelId > 0 })
.GroupBy(hv => hv.hasVendor ? hv.h.vendorHotelId : hv.h.HotelId, hv => hv.h)
.Select(hg => hg.OrderBy(h => h.Price).First())
.Select(hmin => new {
HotelId = hmin.HotelId,
Price = hmin.Price,
vendorId = hmin.vendorId
})
.ToList();
NB: Somewhere someone should write an article on the advantages of conditional GroupBy expressions for unusual groupings.

Finding consecutive attendance for a series of events

I am trying to find a SQL only solution to an issue related to calculating consecutive event attendance. The events occur on different days so I cannot use any sequential date method for determining consecutive attendance. To count consecutive attendance for a single person I would start with the most recent event and work my way back in time. I would count each event that the person attended and when I hit an event the person did not attend I would stop. This allows me to have a count of recent consecutive attendance of events. Currently, all of the data is hosted in SQL tables and below is sample schema with data:
USERS
ID UserName MinutesWatched
--- -------- --------------
1 jdoe 30
2 ssmith 400
3 bbaker 350
4 tduke 285
EVENTS
ID Name StartDate
-- ----------- ---------
1 1st Event 07/15/2018
2 2nd Event 07/16/2018
3 3rd Event 07/18/2018
4 4th Event 07/20/2018
ATTENDANCE
ID User_ID Event_ID
-- ------- --------
1 1 1
2 1 2
3 1 3
4 1 4
5 2 4
6 2 3
7 3 4
8 3 2
9 3 1
10 4 4
11 4 3
12 4 2
For an output I am trying to get:
OUTPUT
User_ID Consecutive WatchedMinutes
------- ----------- --------------
1 4 30
2 2 400
3 1 350
4 3 285
I have built out C# code to do this in an iterative fashion but it is slow when I am dealing with 300,000+ users and hundreds of events. I would love to see a SQL version of this.
Below is the method that calculates top event viewers as requested by Dan. The output is actually just a string that lists the Top X event viewers.
public string GetUsersTopWatchedConsecutiveStreams(int topUserCount)
{
string results = "Top " + topUserCount + " consecutive viewers - ";
Dictionary<ChatUser, int> userinfo = new Dictionary<ChatUser, int>();
using (StorageModelContext db = new StorageModelContext())
{
IQueryable<ChatUser> allUsers = null;
if (mainViewModel.CurrentStream != null)
allUsers = db.ViewerHistory.Include("Stream").Include("User").Where(x => x.Stream.Id == mainViewModel.CurrentStream.Id).Select(x => x.User);
else
allUsers = db.ViewerHistory.Include("Stream").Include("User").Where(x => x.Stream.Id == (db.StreamHistory.OrderByDescending(s => s.StreamEnd).FirstOrDefault().Id)).Select(x => x.User);
foreach (var u in allUsers)
{
int totalStreams = 0;
var user = db.Users.Include("History").Where(x => x.UserName == u.UserName).FirstOrDefault();
if (user != null)
{
var streams = user.History;
if (streams != null)
{
var allStreams = db.StreamHistory.OrderByDescending(x => x.StreamStart);
foreach (var s in allStreams)
{
var vs = streams.Where(x => x.Stream == s);
if (vs.Count() > 0)
totalStreams++;
else
break;
}
}
}
userinfo.Add(u, totalStreams);
totalStreams = 0;
}
var top = userinfo.OrderByDescending(x => x.Value).ThenByDescending(x => x.Key.MinutesWatched).Take(topUserCount);
int cnt = 1;
foreach (var t in top)
{
results += "#" + cnt + ": " + t.Key + "(" + t.Value.ToString() + "), ";
cnt++;
}
if (cnt > 1)
results = results.Substring(0, results.Length - 2);
}
return results;
}
mainViewModel.CurrentStream is null when no event is actively running. When a live event is occurring it will contain an object with information related to the live stream event.
Maybe you want to give this one a try:
Events get a row number in descending order (by StartDate), also the attendances by user get a number in descending StartDate order. Now, the differences of the event numbers and the attendance numbers will be the same for consecutive attendances. I use these differences for grouping, count the attendances in the group and return the group with the lowest difference (by user):
WITH
evt (ID, StartDate, evt_no) AS (
SELECT ID, StartDate,
ROW_NUMBER() OVER (ORDER BY StartDate DESC)
FROM EVENTS
),
att ([User_ID], grp_no) AS (
SELECT [User_ID], evt_no -
ROW_NUMBER() OVER (PARTITION BY [User_ID] ORDER BY StartDate DESC)
FROM ATTENDANCE a
INNER JOIN evt ON a.Event_ID = evt.ID
),
con ([User_ID], Consecutive, rn) AS (
SELECT [User_ID], COUNT(*),
ROW_NUMBER() OVER (PARTITION BY User_ID ORDER BY grp_no)
FROM att
GROUP BY [User_ID], grp_no
)
SELECT u.ID AS [User_ID], u.UserName, u.MinutesWatched, con.Consecutive
FROM con
INNER JOIN USERS u ON con.[User_ID] = u.ID
WHERE con.rn = 1;
Would be interested in how long this query runs on your system.
You seem to want the largest event id that a person did not attend, which is smaller than the largest id the person did attend. Then you want to count the number the person attended.
The following approach handles this as:
Combine the users with all events up to the maximum event
Get the largest event that doesn't match
Bring back the rows where the count is 0 and count them
So, this gives the events with the count:
select u.user_id,
sum(case when a.event_id is null then e.id end) over (partition by user_id) as max_nonmatch_event_id
from (select user_id, max(event_id) as max_event_id
from attendance
group by user_id
) u join
events e
on e.id <= u.max_event_id left join
attendance a
on a.user_id = u.id and a.event_id = e.id
order by num_nulls_gt;
One more subquery should do the rest:
select u.user_id, count(*) as num_consecutive
from (select u.user_id,
sum(case when a.event_id is null then e.id end) over (partition by user_id) as max_nonmatch_event_id
from (select user_id, max(event_id) as max_event_id
from attendance
group by user_id
) u join
events e
on e.id <= u.max_event_id left join
attendance a
on a.user_id = u.id and a.event_id = e.id
) ue
where event_id > max_nonmatch_event_id
group by user_id;

Linq-to-Sql query with group by

There is a table in database:
[id] [int] IDENTITY(1,1) NOT NULL,
[name] [varchar](150) NOT NULL,
[date_of_birth] [date] NOT NULL,
I need to get a datasource for GridView which contains 2 columns.
----------------
| age || count |
----------------
| 20 || 3 |
| 21 || 4 |
| 25 || 5 |
----------------
Is it possible to do it with one query?
What I've tried:
var dates = (from u in db.Users
group u by u.date_of_birth into g
select new { age = calculateAge(g.Key) }).ToList();
var dates1 = from d in dates
group d by d.age into g
select new { age = g.Key, count = g.Count() };
GridView1.DataSource = dates1;
GridView1.DataBind();
This works but I think there is a way to make it more simpler. Or not?
P.S. calculateAge has the following signature
private int calculateAge(DateTime date_of_birth)
First of all you have a logic flaw in the first query. You are using grouping by date, not by age, so peoples who born in same year but on different day will be grouped into different sets. This 'group by' is superseded by 2nd query 'group by' clause.
2nd thing to note is that your 2nd query will use actual results of 1st query (see ToList() call) so it will be running on .NET, not on SQL side.
Here is my vision for you query (count number of peoples with same age):
var dates = from u in db.Users
select new { age = calculateAge(g.Key) } into ages
group ages by ages.age into g
select new { age = g.Key, count = g.Count() };
Or even without anonymous type declaration:
var dates = from u in db.Users
select calculateAge(g.Key) into ages
group ages by ages into g
select new { age = g.Key, count = g.Count() };

Categories