Use INNER JOIN instead of LEFT JOIN when using gorm .Joins - join

I have the following structs:
type User struct {
ID uuid.UUID `gorm:"type:uuid"`
BirthDate *BirthDate `gorm:"<-"`
BirthDateID uuid.UUID
}
type BirthDate struct {
ID uuid.UUID `gorm:"type:uuid"`
Year int
Month int
Day int
}
(Those are make up structs). So basically there is a one to one relationship between user and birthdate, let's assume two users can't be born the same day.
I want to be able to retrieve all the users that were born in 2022, so then I have the following:
var result []*User
birthDateExample := BirthDate{Year:2022}
DB.Debug().Joins("BirthDate", s.Database.Select("id").Where(birthDateExample)).Preload(clause.Associations).Find(&result)
But it's making a left join query, so I am getting more results that I want:
SELECT `users`.`id`,`users`.`birth_date_id`,`BirthDate`.`id` AS `BirthDate__id`,`BirthDate`.`year` AS `BirthDate__year`,`BirthDate`.`month` AS `BirthDate__month`,`BirthDate`.`day` AS `BirthDate__day` FROM `reports` LEFT JOIN `birth_dates` `BirthDate` ON `users`.`birth_date_id` = `BirthDate`.`id` AND `BirthDate`.`year` = "2022"
How can I specify that I want a inner join? Because if I write the query as the following it works:
DB.Debug().Joins("JOIN birth_dates on birth_dates.id = users.billing_month_id and birth_dates.year = ?", 2022).Preload(clause.Associations).Find(&result)
But I would rather use the previous approach, Joins("BirthDates", query).

Related

Get count of child objects in grails Criteria query

Lets Say a domain class A has many Class B objects. I need to do a criteria query which returns
A.id
A.name
B.count(no of B elements associated with A)
B.last Updated(date of most recent update of B elements associated with A considering i have last_updated date for all B elements)
Also the query should be flexible enough to add conditions/restrictions to both A and B domain objects.
Currently I have gotten as far as this:
A.createCriteria().list {
createAlias('b','b')
projections{
property('id')
property('gender')
property('dateOfBirth')
count('b.id')
property('publicId')
}
}
But the problem is that it only returns one object and the count of child objects is for all the elements of B instead of just those associated with A
Recently I was in a similar scenario I needed a query in which one of your rows will store the count of many in a one-to-many relationship
But unlike your scenario I used native sql queries to resolve the query.
The solution was to use derived tables (I do not know how to implement them using criteria query).
In case you find it useful I share a code with the implementation taken from a grails service:
List<Map> resumeInMonth(final String monthName) {
final session = sessionFactory.currentSession
final String query = """
SELECT
t.id AS id,
e.full_name AS fullName,
t.subject AS issue,
CASE t.status
WHEN 'open' THEN 'open'
WHEN 'pending' THEN 'In progress'
WHEN 'closed' THEN 'closed'
END AS status,
CASE t.scheduled
WHEN TRUE THEN 'scheduled'
WHEN FALSE THEN 'non-scheduled'
END AS scheduled,
ifnull(d.name, '') AS device,
DATE(t.date_created) AS dateCreated,
DATE(t.last_updated) AS lastUpdated,
IFNULL(total_tasks, 0) AS tasks
FROM
tickets t
INNER JOIN
employees e ON t.employee_id = e.id
LEFT JOIN
devices d ON d.id = t.device_id
LEFT JOIN
(SELECT
ticket_id, COUNT(1) AS total_tasks
FROM
tasks
GROUP BY ticket_id) ta ON t.id = ta.ticket_id
WHERE
MONTHNAME(t.date_created) = :monthName
ORDER BY dateCreated DESC"""
final sqlQuery = session.createSQLQuery(query)
final results = sqlQuery.with {
resultTransformer = AliasToEntityMapResultTransformer.INSTANCE
setString('monthName', monthName)
list()
}
results
}
The part of interest is to declare a row within the main select and then in the clause from declare the derived query that stores the result in a row with the same name declared in the main select
SELECT ...
total_tasks --Add the count column to your select
FROM ticket t
JOIN (SELECT ticked_id, COUNT(1) as total_tasks
FROM tasks
GROUP BY ticked_id) ta ON t.id = ta.ticked_id
...rest of query
This last example I share from the answer made by the user Aaron Dietz to the question that I also formulate
I hope it is useful for you
Turns out I wasn't very far from the solution and i just needed to do grouping based on the right property which is the foreign key column in the child table which is b.a in this case so the following works now
A.createCriteria().list {
createAlias('b','b')
projections{
property('id')
property('gender')
property('dateOfBirth')
count('b.id')
groupProperty('b.a')
property('publicId')
}
}
In the criteria you need to group by the property which are not aggregate.
Try following:
A.createCriteria().list {
createAlias('b','b')
projections{
groupProperty('id','id')
groupProperty('gender','gender')
groupProperty('dateOfBirth','dateOfBirth')
count('b.id','total')
groupProperty('publicId','publicId')
}
}
or If you want to have a list of map object return you can try add resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
A.createCriteria().list {
resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
createAlias('b','b')
projections{
groupProperty('id','id')
groupProperty('gender','gender')
groupProperty('dateOfBirth','dateOfBirth')
count('b.id','total')
groupProperty('publicId','publicId')
}
}
Hope it can help

I want to union together four queries and set this to be the repeater's data source

Based on user design I have to union together four queries and put them in a repeater.
var qryIssuer = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join iss in dbRRSP.Issuer on l.IssuerId equals iss.IssuerId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription,
lat.LOAAccessTypeDescription,
PersonType = "Issuer",
LOAName = iss.CompanyName,
l.DateAdded
};
var qryEMD = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join emd in dbRRSP.Agent on l.AgentId equals emd.AgentId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription,
lat.LOAAccessTypeDescription,
PersonType = "EMD",
LOAName = emd.CompanyName,
l.DateAdded
};
var qryEmdRep = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join ar in dbRRSP.AgentRepresentative on l.EMDRepresentativeId equals ar.AgentRepresentativeId
join arp in dbRRSP.Person on ar.PersonId equals arp.PersonId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription,
lat.LOAAccessTypeDescription,
PersonType = "EMD Rep",
LOAName = arp.FirstName + ' ' + arp.LastName, l.DateAdded
};
var qryLOAPerson = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join lp in dbRRSP.LOAPerson on l.LOAPersonId equals lp.LOAPersonId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription, lat.LOAAccessTypeDescription,
PersonType = "Person",
LOAName = lp.LOAPersonName,
l.DateAdded
};
This is the four queries. And the trickiest part is that the last field is a datetime, which is causing me some issues. I know how to union two of them together like this:
var qryMultipleLOA = qryIssuer.Union(qryEMD).ToList().Select(loa => new ExtendedLOA
{
LOAOrReferredByDescription = loa.LOAOrReferredByDescription,
LOAAccessTypeDescription = loa.LOAAccessTypeDescription,
PersonType = loa.PersonType,
LOAName = loa.LOAName,
DateAdded = DateTime.Parse(loa.DateAdded.ToString()).ToString("MM/dd/yyyy")
});
But I'm at a loss on how to add the last two queries - first I tried wrapping it in brackets and adding a .Union which didn't work, and then when I tried to nest them with appropriate .ToLists, that didn't work either.
Below is the code to bind it to the repeater.
rptLOA.DataSource = qryMultipleLOA;
rptLOA.DataBind();
Suggestions would be greatly appreciated.
Did you try something like?
var qryMultipleLOA = qryIssuer.Union(qryEMD).Union(qryEmdRep).Union(qryLOAPerson).ToList();
Provided your queries' footprints are the same, this shouldn't be an issue to chain them upon each other.
Edit:
I would also recommend the following:
Create a class to hold an instance of the resultant data.
Instead of creating lists of dynamic variables generated from Linq and hoping they all match, funnel the linq results into a List. That way you can tell immediately if you have a type mismatch.
Once you have four lists of the same List, Unions as per my syntax above will be a snap.
Dynamic Linq lists can be a pain, unwieldy and a single property type change can throw of your code at runtime rather than design time. If you follow the steps above, your code will be much more maintainable and clear to you and others.
I hope this helps in some way.

LINQ to Entities query with join inside method for use in MVC app

In my Person table is a RequestedLocation column which stores location IDs. The IDs match the LocationId column in the Locations table, the Locations table also has the text location names, in the LocatioName column.
In my view, I need to display the string LocationName in the view which has the Person model passed to it. The view will be displaying a List of people in a telerik grid. CUrrently it works great, except the RequestedLocation column is all integers.
I am populating all my grids with methods containing LINQ queries. Here is the method that currently works:
public List<Person> GetPeople()
{
var query = from p in _DB.Person.ToList()
select p;
return query.ToList();
}
Here is the regular SQL query that works, and I need to convert into LINQ:
SELECT ApplicantID
,FirstName
,LastName
,MiddleName
,DateofBirth
,Gender
,RequestedVolunteerRole
,RequestedVolunteerLocation
,l.LocationName
FROM Form.Person p
JOIN dbo.Location l ON p.RequestedVolunteerLocation = l.LocationID
Order BY ApplicantID
Here is my attempt to convert to LINQ:
public List<NewApplicantViewModel> GetPeople()
{
var query = from pl in _DB.Person.ToList()
join l in _Elig_DB.Locations.ToList() on pl.RequestedVolunteerLocation equals l.LocationID
select new
{
pl.RequestedVolunteerLocation = l.LocationName
};
return query.ToList();
The number of errors I get from this are numerous, but most are along the lines of:
Cannot convert from type Annonymous to Type List<NewAPplicantModel>
and
Invalid annonymous type declarator.
Please help, and thank you for reading my post.
Oh, and I have only been programming for a couple months, so if I am going about this all wrong, please let me know. Only thing I have to stick with is the table structure because it is an existing app that I am updating, and changing the location or person tables would have large consequences.
public List<NewApplicantViewModel> GetPeople()
{
var query = from pl in _DB.Person
join l in _Elig_DB.Locations on pl.RequestedVolunteerLocation
equals l.LocationID
select new NewApplicantViewModel
{
LocationName = l.LocationName,
otherPropery = p.Property
};
return query.ToList();
}
Beware of calling _DB.Person.ToList() it will load all persons from DB because ToList() immediately executes the query and the join would be performed in memory (not in DB).
The reason you are getting an error is you are projecting an anonymous type
select new
{
pl.RequestedVolunteerLocation = l.LocationName
};
Instead, you need to project a NewApplicantViewModel
select new NewApplicantViewModel
{
RequestedVolunteerLocation = l.LocationName
};

Translating SQL statement with dates to Linq-to-Sql for use with EF4

I have the following SQL command:
SELECT CONVERT(varchar, Logged, 103) AS Visited, COUNT(ID) AS Totals
FROM tblStats
GROUP BY CONVERT(varchar, Logged, 103)
ORDER BY Visited DESC
I want to translate this into a L2S statement that can be used with the Entity Framework, but in working with datetime types, I'm getting various errors depending on how I try to attack the problem.
Approach:
var results = from s in db.Stats
group s by (s.Logged.Date.ToString()) into Grp
select new { Day = Grp.Key, Total = Grp.Count() };
Error:
LINQ to Entities does not recognize
the method 'System.String ToString()'
method, and this method cannot be
translated into a store expression.
Approach:
var results = from s in db.Stats
group s by (s.Logged.Date) into Grp
select new { Day = Grp.Key, Total = Grp.Count() };
Error:
The specified type member 'Date' is
not supported in LINQ to Entities.
Only initializers, entity members, and
entity navigation properties are
supported.
What syntax do I need to make the query work?
Try using the EntityFunctions.TruncateTime method:
var results = from s in db.Stats
group s by EntityFunctions.TruncateTime(s.Logged) into Grp
select new { Day = Grp.Key, Total = Grp.Count() };
Do you need the Date section in s.Logged.Date?
Have you tried this:
var results = from s in db.Stats
group s by (s.Logged) into Grp
select new { Day = Grp.Key, Total = Grp.Count() };
I'm assuming that Logged is the property (column in the table).
EDIT: you guys are way to quick.

How to do multiple Group By's in linq to sql?

how can you do multiple "group by's" in linq to sql?
Can you please show me in both linq query syntax and linq method syntax.
Thanks
Edit.
I am talking about multiple parameters say grouping by "sex" and "age".
Also I forgot to mention how would I say add up all the ages before I group them.
If i had this example how would I do this
Table Product
ProductId
ProductName
ProductQty
ProductPrice
Now imagine for whatever reason I had tons of rows each with the same ProductName, different ProductQty and ProductPrice.
How would I groupt hem up by Product Name and add together ProductQty and ProductPrice?
I know in this example it probably makes no sense why there would row after row with the same product name but in my database it makes sense(it is not products).
To group by multiple properties, you need to create a new object to group by:
var groupedResult = from person in db.People
group by new { person.Sex, person.Age } into personGroup
select new
{
personGroup.Key.Sex,
personGroup.Key.Age,
NumberInGroup = personGroup.Count()
}
Apologies, I didn't see your final edit. I may be misunderstanding, but if you sum the age, you can't group by it. You could group by sex, sum or average the age...but you couldn't group by sex and summed age at the same time in a single statement. It might be possible to use a nested LINQ query to get the summed or average age for any given sex...bit more complex though.
EDIT:
To solve your specific problem, it should be pretty simple and straightforward. You are grouping only by name, so the rest is elementary (example updated with service and concrete dto type):
class ProductInventoryInfo
{
public string Name { get; set; }
public decimal Total { get; set; }
}
class ProductService: IProductService
{
public IList<ProductInventoryInfo> GetProductInventory()
{
// ...
var groupedResult = from product in db.Products
group by product.ProductName into productGroup
select new ProductInventoryInfo
{
Name = productGroup.Key,
Total = productGroup.Sum(p => p.ProductCost * p.ProductQty)
}
return groupedResult.ToList();
}
}

Resources