LINQ-to-SQL GroupBy across Many-to-Many Divide - linq-to-sql

I have the following issue to solve.
I have an IQueryable list of Invoices, each tied to an Account. Each Account can have multiple Structures, and Accounts and Structures are tied together by a table called StructureAccount. It looks like this:
Invoice ---> Account <-----StructureAccount ----> Structure
I want to query my IQueryable list of Invoices and group by Structure.StructureID or StructureAccount.StructureID. But, because any given invoice can be tied to multiple Structures the best I can get is a LIST of StructureIDs, and therefore my GroupBy is not working.
I feel like I am missing an obvious solution to this.
I should note that I understand that the data in any one Invoice would be counted multiple times if the Invoice were tied to more than one Structure, and this is "solved" by a "PercentAllocationtoStructure" value in the table StructureAccount.
I hope I did a good enough job explaining this problem. Let me know if not.

Hmmm...I might be missing something, but doesn't the following work?
var q = from i in Invoice
join a in Account
on i.AccountID equals a.AccountID
join sa in StructureAccount
on i.AccountID equals sa.AccountID
join s in Structure
on sa.StructureID equals s.StructureID
group i by s.StructureID;
I tested it on the following dummy data:
var Invoice = new [] {
new { InvoiceID = 1, AccountID = 1 },
new { InvoiceID = 2, AccountID = 2 },
new { InvoiceID = 3, AccountID = 3 },
new { InvoiceID = 4, AccountID = 1 },
new { InvoiceID = 5, AccountID = 2 },
new { InvoiceID = 6, AccountID = 3 }
};
var Account = new [] {
new { AccountID = 1 },
new { AccountID = 2 },
new { AccountID = 3 },
};
var StructureAccount = new [] {
new { AccountID = 1, StructureID = 2 },
new { AccountID = 1, StructureID = 3 },
new { AccountID = 2, StructureID = 2 },
new { AccountID = 3, StructureID = 1 },
new { AccountID = 3, StructureID = 2 },
};
var Structure = new [] {
new { StructureID = 1 },
new { StructureID = 2 },
new { StructureID = 3 }
};
And it returns:
StructureID = 2:
InvoiceID's: 1,2,3,4,5,6
StructureID = 3:
InvoiceID's: 1,4
StructureID = 1:
InvoiceID's: 3,6

I'll assume you have the following starting point:
IQueryable<Invoice> _invoices;
First, you need to get a list of all the items that you will be iterating over:
IQueryable<Account> _accounts = _invoices.Select(myInvoice => myInvoice.Account).Distinct();
IQueryable<StructuredAccount> _structuredAccounts = _accounts.SelectMany(myAccount => myAccount.StructuredAccounts);
IQueryable<Structure> _structures = _structuredAccounts.Select(myStructuredAccount => myStructuredAccount.Structure).Distinct();
Next, you need to go back and join your Structure objects to the respective Invoice objects.
For this, you'll:
Get a set of {Structure, Account} pairs:
var structureAccountJoin = _structures.Join(_structuredAccounts, _structure => structure.StructuredID, _structuredAccount => _structuredAccount.StructuredID, (structure, structuredAccount) => new { Structure = structure, Account = structuredAccount.Account });
Get a set of {Structure, Invoice} pairs:
var structureInvoiceJoin = structureAccountJoin.Join(_invoices, myObj => myObj.Account.AccountID, invoice => invoice.AccountID, (myObj, invoice) => new { Structure = myObj.Structure, Invoice = invoice});
Finally, you can group everything by the Structure object:
IQueryable<IGrouping<Structure, Invoice>> groupByStructure = structureInvoiceJoin.GroupBy(obj => obj.Structure, result => result.Invoice);
(GroupBy documentation: http://msdn.microsoft.com/en-us/library/bb534304.aspx)
Now, you can access everything as follows:
foreach(IGrouping<Structure, Invoice> groupEntry in groupByStructure)
{
Structure currentGrouping = groupEntry.Key;
foreach(Invoice inv in groupEntry)
{
// do something;
}
}
As a note, this is a very complex script that requires a lot of steps if you don't have access to the tables directly. You may want to look into creating a StoredProcedure for this instead, as it will be more efficient and you'll be able to use SQL Joins instead. If you have only an IQueryable<Invoice> to work with and access to nothing else, there is probably a design problem somewhere in your architecture.
Nevertheless, this is the way to make it work based on your requirements, if I read them correctly.

Related

Fetching TypeORM data with limit and order by and child relationship with getMany()

I need a help with nestjs typeorm query. Scenario is as follows
Suppose i have 3 tables say Teachers, Students and Parents
Teachers = One to many relation w.r.t Students , i.e one teacher can
teach multiple students.
Students = Many to one relation w.r.t
Teachers.
Students = One to Many w.r.t Parents , assuming 1 child
of Parents
Parents = Many to One w.r.t Students
const records = await this.createQueryBuilder('teachers').leftJoinAndSelect('teachers.students', 'students').leftJoinAndSelect('students.parents', 'parents').orderBy('teachers.id', 'DESC').limit(1);
let res = await records.getMany();
output something like this
{
teacher_id: '4',
teacher_name: 'abc',
students: [
{
'id': 1,
'name': 'pqr',
status: 'failed',
parents: [
{
id: 1,
name: 'mom',
relationship: 'mom'
},
{
id: 2,
name: 'dad',
relationship: 'dad'
}
]
}
]
}
Let us say there are 100 teachers. Each teacher has 20 students and each student has 2 parents.
Total records which the query would return = 100 * 20 * 2 = 4000
But while showing the results in a paginated way we need to group records by teacher.id.
Total count using group by teacher.id = 100
Example code -
async getTeacher(): Promise<any[]> {
return await this.repo
.createQueryBuilder('teachers')
.leftJoinAndSelect('teachers.students', 'students')
.leftJoinAndSelect('students.parents', 'parents')
.groupBy('teachers.id')
.orderBy('teachers.id','DESC')
.limit(1)
.getMany();
}

Retrive all the value that satisfy the condition of first table

I have two tables users and location. I need to join both tables
what i need is get all the area number of all the users which are present in the user table.
ie user 1 has 3 entries in the second table so i need to join the table in such a way that is,
id1 = 1
area = 2,3
area 2 is repeating so do not include it twice
i tried the join but now getting the correct way to doing it.
What i tried?
$location = User::
join('addresses','users.id1','=','addresses.id1') ->select('users.id1','addresses.area')
->get();
Expected Output
User 1 -> area ->2,3
Here are the two ways to do this.
Firstly you can use Laravel relationship:-
In your model User create relationship:-
function addresses()
{
return $this->hasMany(Address::class, 'id1', 'id1');
}
Now in your User controller you can get User addresses (areas) like this
$users = User::with('addresses')->get();
dd($users->toArray());
This will print something like this
[
{
id1: 1,
name: abaa
pwd: 12345
addresses: [
{
id2: 1,
id1: 1,
area: 2
},
{
id2: 2,
id1: 1,
area: 3
},
{
id2: 3,
id1: 1,
area: 3
}
]
},
{
...
}
]
Second you can use Laravel relationship:-
$builder = new User;
$builder->join('addresses','users.id1','=','addresses.id1')
->selectRaw("users.*, GROUP_CONCAT(DISTINCT addresses.area SEPARATOR ',') as distinct_areas")
->groupBy("users.id1")
->get();
This query will give you result something like this
[
{
id1: 1,
name: abaa,
pwd: 12345,
distinct_areas: 2,3
},
{
...
}
]
I think this will help you.

How to return nested JSON in node js

I have three different tables.
1. Order details (id, itemId,date,userId)
2. Item details (id, itemName, quantity)
3. User details (id, userName)
I want to return a JSON as:
{[
{
orderId = 1,
items = [
{
itemId = 1,
itemName = ITEM_DEMO,
},
{
itemId = 2,
itemName = ITEM_DEMO2,
}
],
userDetails = {
userId = 1,
userName = TEST_USER
}
}
]}
How can we do this in Node JS. Im using MySQL.
First of all you should read about object and json in javascript.
For parsing your data from mySql to json do the following steps:
Create an object that is filled with your data from mySql.
const ArrayObjectFilledWithMysqlData = [
{
orderId: 1,
items: [{
itemId: 1,
itemName: ITEM_DEMO,
},
{
itemId: 2,
itemName: ITEM_DEMO2,
}]
},
{
userDetails: {
userId: 1,
userName: TEST_USER
}
}
]
Parse the Array to json.
const jsonFromData = JSON.stringify(ArrayObjectFilledWithMysqlData)

Using Linq to shape structure for Json serialization

I have a pretty simple structure that looks something like this:
var list = new List<CategoryInTimeItem>
{
new CategoryInTimeItem { Name = "Food", Year = 2012, Month = 1, Amount = 100 },
new CategoryInTimeItem { Name = "Food", Year = 2012, Month = 2, Amount = 110 },
new CategoryInTimeItem { Name = "Food", Year = 2012, Month = 3, Amount = 130 },
new CategoryInTimeItem { Name = "Food", Year = 2012, Month = 4, Amount = 130 },
new CategoryInTimeItem { Name = "Transport", Year = 2012, Month = 1, Amount = 1000 },
new CategoryInTimeItem { Name = "Transport", Year = 2012, Month = 2, Amount = 1101 },
new CategoryInTimeItem { Name = "Transport", Year = 2012, Month = 3, Amount = 1301 },
new CategoryInTimeItem { Name = "Transport", Year = 2012, Month = 4, Amount = 1301 }
};
I want to reshape this structure so that when it get's serialized to json the result should look like this, one array for each name:
[
[["2012-1", 100], ["2012-2", 110], ["2012-3", 130], ["2012-4", 130]],
[["2012-1", 1000], ["2012-2", 1101], ["2012-3", 1301], ["2012-4", 1301]]
]
My linq query looks like this:
result.Values =
from d in list
orderby d.Name , d.Year , d.Month
group d by d.Name
into grp
select new[]
{
grp.Select(y => new object[] {y.DateName, y.Amount})
};
This almost works, however I get an extra "level" of arrays, so when serialized to json the result looks like this:
[
[[["2012-1", 100], ["2012-2", 110], ["2012-3", 130], ["2012-4", 130]]],
[[["2012-1", 1000], ["2012-2", 1101], ["2012-3", 1301], ["2012-4", 1301]]]
]
What am I doing wrong here?
You've almost been there, just instead of
from d in list
...
select new[]
{
grp.Select(y => new object[] {y.DateName, y.Amount})
}
simply:
from d in list
...
select grp.Select(y => new object[] {y.DateName, y.Amount}).ToList()
You just added an unnecessary level of array at the end.

Does linq to sql have a with ties option?

I'm trying to move the following query to Linq-to-sql, is it possible?
select * from (
Select top (#Percent) percent with ties *
from(
Select distinct
LoanNumber as LoanNo
From CHE
Left Join RecordingInfo as Rec
On CHE.LoanNumber = Rec.LoanNo
Where Channel = 'LINX'
and CHE.Doc in ('MTG','MOD')
and Rec.LoanNo is null
and LoanNumber >= '#LoanNo'
) A
order by LoanNo #Order
) B
order by LoanNo
I have not seen anyway to do with ties in linq.
I think something like this will work for you.
public static IQueryable<T> TopPercentWithTies<T, TKey>(this IOrderedQueryable<T> query, Expression<Func<T, TKey>> groupByExpression, double percent)
{
var groupedQuery = query.GroupBy(groupByExpression);
int numberToTake = groupedQuery.Count() * percent / 100;
return groupedQuery.Take(numberToTake).SelectMany(t => t);
}
I only tested it with IEnumerable, so I don't know for sure that it'll work properly with IQueryable. I also sorted the list before calling TopPercentWithTies().
Here's the code I used to test it.
int percent = 50;
var people = new []
{
new { Age = 99, Name = "Adam" },
new { Age = 99, Name = "Andrew" },
new { Age = 89, Name = "Bob" },
new { Age = 50, Name = "Cecil" },
new { Age = 50, Name = "Doug" },
new { Age = 50, Name = "Everett" },
new { Age = 35, Name = "Frank" },
new { Age = 25, Name = "Greg" },
new { Age = 15, Name = "Hank" }
};
var sortedPeople = people.AsQueryable().OrderByDescending(person => person.Age);
var results = sortedPeople.TopPercentWithTies(person => person.Age, percent);
foreach (var person in results)
Console.WriteLine(person);
Hope it helps or at least gets you in the right direction. You may want to tweak the logic for calculating numberToTake.