SQL giving me headache - multiple joins - c#

First I'll explain my scenario. Here's the 2 general tables in use:
Accounts:
LegacyID = The identifer of the
account
InvoiceAccount = An account can have
multiple subaccounts in the same
table. This field is the LegacyID
of its parent account
Customer = There can be two different
types of accounts, and they can share
ID's - so this column differentiates
between the 2. For the purposes of my
SQL I always want it to be true rather
than false
AllocatedUser = The username of the
person who needs to see this
deliveries. This is only populated on
the parent account, so I need to link
back to get this for the subaccounts
Deliveries:
LegacyID = The deliveries identifier
Customer = The LegacyID of the account
related to the delivery (can be a
subaccount)
OnHold = A flag which for the purposes
of my query needs to be 'true'
Now that's explained, basically I need an SQL which returns any deliveries that are 'OnHold', but only for deliveries for accounts that are allocated the logged in user. The query for selecting the AllocatedUser if the delivery links to a parent account was simple, but I'm having issues with returning rows if the delivery is linked to a subaccount - it's simply not returning any. Here is the SQL below:
SELECT Deliveries_1.LegacyID, Deliveries_1.TripDate, Deliveries_1.OnHoldReason, Account_2.AllocatedUser
FROM Deliveries AS Deliveries_1 INNER JOIN
Account AS Account_1 ON Deliveries_1.Customer = Account_1.InvoiceAccount INNER JOIN
Account AS Account_2 ON Account_1.InvoiceAccount = Account_2.LegacyID
WHERE (Deliveries_1.OnHold = #OnHold) AND (Account_2.Customer = 'True') AND (Account_2.AllocatedUser = #AllocatedUser)
My mind is frazzled from trying to work out why it don't work at the moment - I'd really appreciate any advice.
Thanks!

It sounds like you're looking for records in the Deliveries table that are associated with a user's account, the user's direct parent's account, and the user's direct child's account. If this is true modifying your query as follows should do the trick.
SELECT DISTINCT d.LegacyID, d.TripDate, d.OnHoldReason,
case
when child.LegacyID = d.Customer then child.AllocatedUser
when parent.LegacyID = d.Customer then parent.AllocatedUser
else this.AllocatedUser
end as 'Delivery_AllocatedUser'
FROM Account this
LEFT JOIN Account parent on parent.LegacyID = this.InvoiceAccount
LEFT JOIN Account child on this.LegacyID = child.InvoiceAccount
JOIN Deliveries d on (d.Customer = this.LegacyID AND this.Customer = 'True')
OR (d.Customer = parent.LegacyID AND parent.Customer = 'True')
OR (d.Customer = child.LegacyID AND child.Customer = 'True')
WHERE d.OnHold = #OnHold
AND this.AllocatedUser = #AllocatedUser

First comment is that you should build the joins in steps if you are having issues.
Second comment is that in the query you posted there is unnecessary join - you are not filtering nor selecting nor joining on anything specific from table aliased as Account_1.
Maybe that is an indication of what you are doing wrong. Otherwise, the query looks(!) ok (assuming that all the joins are correct; your descriptions are not exact - for example for Accounts.Customer you say that 'For the purposes of my SQL I always want it to be true rather than false' - well the Customer is not true/false field is it? What do you mean? There are other such inconsistencies...)
Some sample data and a sample of the result could shed some more light.

ON Deliveries_1.Customer = Account_1.InvoiceAccount
ON Account_1.InvoiceAccount = Account_2.LegacyID
If both of these criteria are true, then Delivery.Customer would equal Account2.LegacyId (delivery would have to be on the parent account). I don't think that's what you intended.
Perhaps you meant
ON Deliveries_1.Customer = Account_1.LegacyID
ON Account_1.InvoiceAccount = Account_2.LegacyID

Related

Convert this EF query to SQL

I've got this horrible EF code in a code base and I am scratching my head trying to figure out what it's trying to do. If I were looking to put this in a stored procedure instead how would this query look in SQL?
public void LoadNotifyListItems(UserProfileModel user, DbContext pc)
{
var allowedEvents = (from r in user.Roles
join near in pc.NotifyEventAllowedRoles on r.RoleId equals near.RoleId
join ne in pc.NotifyEvents on near.NotifyEventId equals ne.Id
select ne).Distinct();
var NotifyListItems = from ne in allowedEvents
join pune in pc.UserNotifyEvents
on new { NotifyEventId = ne.Id, UserId = user.Id }
equals new { pune.NotifyEventId, pune.UserId }
into loj
from pune in loj.DefaultIfEmpty()
select new NotifyListItem
{
Event = ne,
Value = pune ?? new UserNotifyEvent
{
NotifyEventId = ne.Id
}
};
}
The issue I am having is the entirety of pc.UserNotifyEvents table is being queried. Monitoring the DB, EF is making this query when the LoadNotifyListItems method is being run:
[Extent1].[UserId] AS [UserId],
[Extent1].[NotifyEventId] AS [NotifyEventId],
[Extent1].[NotifyPrimaryEmail] AS [NotifyPrimaryEmail],
[Extent1].[NotifyAlternateEmail] AS [NotifyAlternateEmail],
[Extent1].[NotifySmsNumber] AS [NotifySmsNumber],
[Extent1].[Threshold1] AS [Threshold1],
[Extent1].[Threshold2] AS [Threshold2]
FROM [UserNotifyEvents] AS [Extent1]
This isn't needed and its ingesting 200,000 rows everytime. I am thinking of moving the query to a Stored Proc and pass in userId as a parameter instead
Please let me know if there is not enough information to go off here.
The first part of the query is joining a list of roles in memory (user.Roles) with an IQueryable from the DbContext. This is probably the cause of your issue. You should use the pc.Roles property instead, and introduce a where clause to filter by the id of the provided user.
If user info is in same database as other tables
Pass the user id to the stored proc. Inside the sproc, join user-roles to the other tables WHERE userid = passed-in parameter.
If user info is NOT in same database as other tables
First, get a list of role ids for the user.
var roleIds = user.Roles.Select(i=>i.RoleId).Distinct().ToList();
Then you're going to pass that list of role IDs to your stored proc. Try something like this Stack Overflow article, which tells you how to pass those role IDs as distinct strings to a stored proc.
The rest of this appears to be regular table joins within the database. The most critical part is getting those roles IDs for the user.

Having trouble constructing the correct Linq query syntax in c#.net for a join after join

I have troubles creating the correct query for a list of applications I have. I need to display them in a dashboard so consultants can handle the applications.
These applications can be uploaded by organisations or individuals.
Every consultant is 'owner' of a few organisations and individuals, in the dashboard they should only see the applications from individuals/organisations that they are also the 'owner' of.
The applications table:
Application
-----------
ID | requestIndividualID | requestOrganisationID | ...
depending on who made the application the requesters ID will be saved in the table. So requestIndividualID or requestOrganisationID, one of them will be null, the other not.
The Individuals table:
Individual
----------
ID | name | ownerID
Now comes the tricky part, the ownerID is not on the Organisation table, but on the OrganisationSegment table, something like this:
Organisation
------------
ID | name | city
OrganisationSegment
-------------------
organisationID | ownerID
The problem comes when I want to join the Organisation table with the OrganisationsSegment table, since it is possible that the organisationID is null.
Currently I have this code that is throwing errors:
var applications = from pa in _context.Applications
join individu in _context.Individu on pa.requestIndividualID equals individu.ID into IndividuResultList
from individu in IndividuResultList.DefaultIfEmpty()
join organisation in _context.Organisations on pa.requestOrganisationID equals organisation.ID into organisationsList
from organisation in organisationsList
join organisationSegment in _context.OrganisationSegment on organisation.ID equals organisationSegment.OrganisationId into organisationSegmentList
from organisationSegment in organisationSegmentList.DefaultIfEmpty()
select new {Data = new {PortalApplications = pa, individu, organisation, organisationSegment}};
After this code I use the result to make a list where I can apply the filter for the owner Id.
When I execute this code, it throws System.InvalidOperationException : Nullable object must have a value. error.
My current solution is making 2 results, one for all the application that have a requestIndividualID and one for all the applications with a requestOrganisationID.
Then I apply the filter (the specific ownerID) to these 2 separate results, and adding the results in a list, since the results came from the same table, thats really easy.
But imo this is a quicky and dirty way of handling it.
I want the correct and clean answer.
Can somebody please help me?
Also i have trouble finding any help on the internet for this, so if you found some info, please link it as well, or tell me how to improve my google skills for issues like this (correct naming of the issue).
I believe this is easier if you join the Orginisation and OrginisationSegment tables first, assuming every Orginisation has an ownerID in OrginisationSegment.
var OrganisationsWithOwner = from o in _context.Organisations
join os in _context.OrganisationSegment on o.ID equals os.OrganisationId
join i in _context.Individu on os.OwnerID equals i.ID
select new { o, i };
Now you can join with Individu and the new OrginisationWithOwner and return the one that has a match.
var applications = from pa in _context.Applications
join indOwner in _context.Individu on pa.requestIndividualID equals indOwner.ID into IndividuResultList
from indOwner in IndividuResultList.DefaultIfEmpty()
join o in OrganisationsWithOwner on pa.requestOrganisationID equals o.o.ID into organisationsList
from o in organisationsList.DefaultIfEmpty()
select new { PortalApplications = pa, owner = (indOwner != null ? indOwner : o.i), organisation = o.o };

TSQL - Select from table with multiple join paths

That title is not very good, so consider the following. I have five tables:
User {
Id,
ProfileId // -> Profiles.Id
}
Profile {
Id
}
ProfilePermissionSets {
ProfileId // -> Profiles.Id
PermissionSetId // -> PermissionSets.Id
}
UserPermissionSets {
UserId // -> Users.Id
PermissionSetId // -> PermissionSets.Id
}
PermissionSets {
Id
}
Permissions {
Id,
PermissionSetId // -> PermissionSets.Id
}
And I want get all of the permissions for a user that are directly linked to it or indirectly through the profile. The not-quite-there SQL I've come up with so far is this:
SELECT [Pe].[Controller],
[Pe].[Action]
FROM [PermissionSets] AS [PS]
JOIN [UserPermissionSets] AS [UPS]
ON ([UPS].[PermissionSetId] = [PS].[Id])
JOIN [Users] AS [U]
ON ([U].[Id] = [UPS].[UserId])
JOIN [Profiles] AS [P]
ON ([P].[Id] = [U].[ProfileId])
JOIN [ProfilePermissionSets] AS [PPS]
ON ([PPS].[ProfileId] = [P].[Id])
JOIN [Permissions] AS [Pe]
ON ([Pe].[PermissionSetId] = [PS].[Id])
WHERE [U].[Id] = 4;
It returns back a correct count of rows, but it's repeating the controller or action over and over, so it's wrong. I'm hoping someone can help me correct it to show all of the distinct permission sets for the user. Ideally, I'd like to also change it so that it's all discovered starting at the user because that is what I have access to in the method I need to do this (the object is an Entity Framework class named User and will be browsed using LINQ).
UPDATED because I forgot that I really wanted the permissions not the permission sets.
Try this SQL
SELECT [Pe].[Controller],
[Pe].[Action]
FROM [Users] AS [U]
LEFT OUTER JOIN [UserPermissionSets] AS [UPS]
ON ([UPS].[UserId] = [U].[Id])
LEFT OUTER JOIN [ProfilePermissionSets] AS [PPS]
ON ([PPS].[ProfileId] = [U].[ProfileId])
LEFT OUTER JOIN [Permissions] AS [Pe]
ON ([Pe].[PermissionSetId] = [UPS].[PermissionSetId])
OR ([Pe].[PermissionSetId] = [PPS].[PermissionSetId])
WHERE [U].[Id] = 4;
So, messing around on LINQPad, I came up with this as far as the LINQ query:
user.PermissionSets.Union(user.Profile.PermissionSets).SelectMany(
ps =>
ps.Permissions.Select(
p =>
p.Controller + "." + p.Action));
And it produces what I want, BUT it does it by composing the results of a bunch of SQL queries. The biggest impact comes from profiles that have multiple permission sets, like say the Administrator. I don't think there's a way around it, and I only have a User object to work with, so I'm ok with the excess SQL queries, at least for now.

LINQ Query using Foreign Keys

Still trying to get my head around the list and navigation concept rather than basic SQL join language. I have a slightly complex query that requires a fair amount of joins, though the bulk of information are navigable tables (Or have been in the past).
To explain the tables and directions of the queries.
At the top level there is a table called Company with a one-to-many relationship with Areas.
Areas has a many-to-one relationship with a table called Shifts.
Another table called Events stores a ShiftID and it was my thinking once the link between Event and Shift was made I could then navigate up the chain of relationships.
There are some other tables begin joined in which makes a pure lambda queries (At my current level of understanding) difficult.
My code thus far is
var UserEvents = from e in _context.Events
join s in _context.Shifts on e.ShiftID equals s.SHFID
where e.UID == userID
select new UserEvents
{
EVTID = e.EVTID,
UID = e.UID,
ShiftID = e.ShiftID,
EVTDate = e.EVTDate,
Notes = e.Notes,
AreaID = s.Area.AreaID,
AreaDesc = s.Area.AreaDesc,
CPYDesc = s.Area.Company.CPYDesc,
StartTime = s.StartTime,
EndTime = s.EndTime,
RequiredResources = s.RequiredResources,
ShiftDesc = s.ShiftDesc,
ShiftDayOfWeek = s.ShiftDayOfWeek
};
The Error that is being given is that Shifts does not contain a definition for Areas. Which in code talk is true, the Shifts.cs does not have a declared field for Area. Though Area has a public List<Shift> SHFID { get; set; }
This Query works fine in LINQ PAD but not in VS
Any help or clarification would be appreciated.

Return a list with members that belong to another list

I have 2 tables:
Users data with PK UserId
UsersOrders with FK UserID
I want to do LINQ query to give me a list of users with member that is the list of the user orders.
I've tried:
var myNestedData = (from ub in db.Users
join ah in db.UsersOrders on ub.UserId equals ah.UserId
into joined
from j in joined.DefaultIfEmpty()
group j by new { ub.UserId, ub.UserName, ub.UserPhone, ub.Approved } into grouped
where grouped.Key.Approved == true
select new
{
UserId= grouped.Key.UserId,
UserName = grouped.Key.UserName,
UserPhone = grouped.Key.UserPhone,
Orders = grouped
}).ToList();
The problem is that I'm getting inside Orders an Object<a,UsersOrders>, which I don't expect.
Is this the right way to approach a solution in terms of performance?
It seems to me you should be able to keep this simple:
var myNestedData = (from u in db.Users
where u.Approved == true
select new
{
User = u,
Orders = u.UserOrders
}).ToList();
You are trying to get a list of all the Approved users and that users orders. Am I missing something?
ALso the name of u.UserOrders will depend on how you have configured your mapping.
Change it to
Orders = grouped.ToList()
To see if it's a potential performance issue, look at the SQL that is generated to see if it is an N+1 scenario (meaning it runs one query for the parent record and N queries for the child records.
I say potential because while may not be the fasted method, it may not be an issue. If there is no noticeable performance problems I wouldn't worry about it and instead focus on making the app better by improving the experience, which may include improving the perfornamce of other areas of the map.

Categories