Right Join in Doctrine2 for Symfony2 - sql

I have the following working MySQL query:
SELECT *
FROM bogenantworten a
RIGHT JOIN
bogenfragen f ON f.id = a.bogenfragen_id
AND a.personen_id = 3,
BogenTyp t,
BogenFragenGruppe g
WHERE
t.id = f.fragentyp_id AND
g.id = f.fragengruppen_id AND
t.id = 1
ORDER BY f.sortierung ASC
Now I need this in Doctrine2 DQL or QueryBuilder. I already learned that D2 is forcing me to think in objects, but I couldn't find any advice how to tag my entities to make this work.
So I'd like to either have the above MySQL query working in my Symfony2 app or some help how to annotate my entities right so I have a working right join connection between BogenAntworten and BogenFragen (the 3 and the 1 are parameters, just so you know). I already set the OneToMany and ManyToOne annotations for all my entities, but I need something to make a right/left join working.
If you want to help me with my entity design:
I have persons (table Person) who answers (table BogenAntworten) questions (table BogenFragen), and when I show the list of questions I either get the last answer from that question (need UPDATE when saving) or there is none and I have to create it (INSERT when saving). Questions also are in one of many types (table BogenTyp) and are in one of many groups (table BogenFragenGruppe)
Any Ideas?

OK, found it out myself again. The QueryBuilder of Doctrine2 supports a leftJoin (which is identical to the RIGHT JOIN if you switch the two tables). For those need some code, here is the above SQL statement build with QueryBuilder:
$query = $em->createQueryBuilder()
->select(array('f.id', 'f.frage', 'f.sortierung', 'a.antwort', 'g.name'))
->from('MySuperBundle:BogenFragen', 'f')
->leftJoin('f.bogenantworten', 'a', 'WITH', 'a.personen = :pid')
->from('MySuperBundle:BogenTyp', 't')
->from('MySuperBundle:BogenFragenGruppe', 'g')
->where('t.id = :tid')
->andWhere('t.id = f.bogentypen')
->andWhere('g.id = f.bogenfragengruppe')
->orderBy('f.sortierung', 'ASC')
->setParameter('tid', 1)
->setParameter('pid', 3)
->getQuery();
(The parameters are actually dynamic, but for easier reading I used the numbers of the original SQL statement)

Related

How to join on subqueries using ARel?

I have a few massive SQL request involving join across various models in my rails application.
A single request can involve 6 to 10 tables.
To run the request faster I want to use sub-queries in the joins (that way I can filter these tables before the join and reduce the columns to the ones I need). I'm trying to achieve this using ARel.
I thought I found the solution to my problem there: How to do joins on subqueries in AREL within Rails,
but things must have changed because I get undefined method '[]' for Arel::SelectManager.
Does anybody have any idea how to achieve this (without using strings) ?
Pierre, I thought a better solution could be the following (inspiration from this gist):
a = A.arel_table
b = B.arel_table
subquery = b.project(b[:a_id].as('A_id')).where{c > 4}
subquery = subquery.as('intm_table')
query = A.join(subquery).on(subquery[:A_id].eq(a[:id]))
No particular reason for naming the alias as "intm_table", I just thought it would be less confusing.
OK so my main problem was that you can't join a Arel::SelectManager ... BUT you can join a table aliasing.
So to generate the request in my comment above:
a = A.arel_table
b = B.arel_table
subquery = B.select(:a_id).where{c > 4}
query = A.join(subquery.as('B')).on(b[:a_id].eq(a[:id])
query.to_sql # SELECT A.* INNER JOIN (SELECT B.a_id FROM B WHERE B.c > 4) B ON A.id = B.a_id
Was looking for this, and was helped by the other answers, but there are some error in both, e.g. A.join(... should be a.join(....
And I also missed how to build an ActiveRecord::Relation.
Here is how to build an ActiveRecord::Relation, in Rails 4
a = A.arel_table
b = B.arel_table
subsel = B.select(b[:a_id]).where(b[:c].gt('4')).as('sub_select')
joins = a.join(subsel).on(subsel[:a_id].eq(a[:id])).join_sources
rel = A.joins(joins)
rel.to_sql
#=> "SELECT `a`.* FROM `a` INNER JOIN (SELECT `b`.`a_id` FROM `b` WHERE (`b`.`c` > 4)) sub_select ON sub_select.`a_id` = `a`.`id`"

How to get Django QuerySet 'exclude' to work right?

I have a database that contains schemas for skus, kits, kit_contents, and checklists. Here is a query for "Give me all the SKUs defined for kitcontent records defined for kit records defined in checklist 1":
SELECT DISTINCT s.* FROM skus s
JOIN kit_contents kc ON kc.sku_id = s.id
JOIN kits k ON k.id = kc.kit_id
JOIN checklists c ON k.checklist_id = 1;
I'm using Django, and I mostly really like the ORM because I can express that query by:
skus = SKU.objects.filter(kitcontent__kit__checklist_id=1).distinct()
which is such a slick way to navigate all those foreign keys. Django's ORM produces basically the same as the SQL written above. The trouble is that it's not clear to me how to get all the SKUs not defined for checklist 1. In the SQL query above, I'd do this by replacing the "=" with "!=". But Django's models don't have a not equals operator. You're supposed to use the exclude() method, which one might guess would look like this:
skus = SKU.objects.filter().exclude(kitcontent__kit__checklist_id=1).distinct()
but Django produces this query, which isn't the same thing:
SELECT distinct s.* FROM skus s
WHERE NOT ((skus.id IN
(SELECT kc.sku_id FROM kit_contents kc
INNER JOIN kits k ON (kc.kit_id = k.id)
WHERE (k.checklist_id = 1 AND kc.sku_id IS NOT NULL))
AND skus.id IS NOT NULL))
(I've cleaned up the query for easier reading and comparison.)
I'm a beginner to the Django ORM, and I'd like to use it when possible. Is there a way to get what I want here?
EDIT:
karthikr gave an answer that doesn't work for the same reason the original ORM .exclude() solution doesn't work: a SKU can be in kit_contents in kits that exist on both checklist_id=1 and checklist_id=2. Using the by-hand query I opened my post with, using "checklist_id = 1" produces 34 results, using "checklist_id = 2" produces 53 results, and the following query produces 26 results:
SELECT DISTINCT s.* FROM skus s
JOIN kit_contents kc ON kc.sku_id = s.id
JOIN kits k ON k.id = kc.kit_id
JOIN checklists c ON k.checklist_id = 1
JOIN kit_contents kc2 ON kc2.sku_id = s.id
JOIN kits k2 ON k2.id = kc2.kit_id
JOIN checklists c2 ON k2.checklist_id = 2;
I think this is one reason why people don't seem to find the .exclude() solution a reasonable replacement for some kind of not_equals filter -- the latter allows you to say, succinctly, exactly what you mean. Presumably the former could also allow the query to be expressed, but I increasingly despair of such a solution being simple.
You could do this - get all the objects for checklist 1, and exclude it from the complete list.
sku_ids = skus.values_list('pk', flat=True)
non_checklist_1 = SKU.objects.exclude(pk__in=sku_ids).distinct()

LINQ not returning all child records

I have a query in the DB:
SELECT GreenInventoryBlendGradeID,bgx.blendgradeid,
bgX.GreenBlendGradeTypeID,[Description]
FROM [GreenInventory] gi
INNER JOIN [GreenInventoryBlendGradeXref] bgX
ON bgX.[GreenInventoryID] = gi.[GreenInventoryID]
INNER JOIN [BlendGrade] bg
ON bg.[BlendGradeID]=bgx.[BlendGradeID]
That returns 3 records:
TypeID Desc
1 XR
2 XR
1 XF2
The LINQ:
var GreenInventory = (from g in Session.GreenInventory
.Include("GreenInventoryBlendGradeXref")
.Include("GreenInventoryBlendGradeXref.BlendGrade")
.Include("GreenInventoryBlendGradeXref.GreenBlendGradeType")
.Include("GreenInventoryWeightXref")
.Where(x => x.GreenInventoryID == id && x.GreenInventoryBlendGradeXref.Any(bg=>bg.GreenBlendGradeTypeID > 0) )
select g);
I have tried different Where clauses including the simple - (x => x.GreenInventoryID == id)
but always have only the first 2 records returned.
Any Ideas?
If I try the following:
var GreenInventory = (from gi in Session.GreenInventory.Where(y => y.GreenInventoryID == id)
join bgX in Session.GreenInventoryBlendGradeXref.DefaultIfEmpty() on gi.GreenInventoryID equals bgX.GreenInventoryID
join bg in Session.BlendGrade.DefaultIfEmpty() on bgX.BlendGradeID equals g.BlendGradeID
select new { GreenInventory = gi, GreenInventoryBlendGradeXref = bgX, BlendGrade = bg });
I Get back 3 of each objects and the correct information is in the BlendGrade objects. It looks like the 3 GreenInventory objects are the same. They each include 2 of the GreenInventoryBlendGradeXref objects which show the the same 2 records as before.
So I not clear on what the original problem was. Also dont know if this is the best way to resolve it.
Thanks for the answers. If anyone has a further thoughts please let us know.
Based on the few details you present, I would assume that you are missing a join. I have no experience with EntityFramework (I assume that you use this ORM), but as far as I know, the ".Include" tries to ensure that the set of root entities will not change and will not contain duplicates.
Your manually created query seems to indicate that there is at least one 1:n relationship in the model. The result you get from LINQ show that only distinct GreenInventory entities are returned.
Therefore you need to adjust your query and explicitly declare that you want all results (and not only distinct root entities) - I would assume that with an explicit join EntityFramework will yield all expected results - or you need to adjust your mapping.
The first place I'd look in would be your model and joins you have defined between the entities. You might also want to check your generated SQL statement:
Trace.WriteLine(GreenInventory.Provider.ToString())
or use Visual Studio IntelliTrace to investigate what was sent to the database.

Inconsistent results between NHibernate Query and intended results

I have the following query in HQL :
public IEnumerable<Player> PlayersNotInTeam(Team team)
{
return Session.CreateQuery("from Player p where p.Sex = :teamSex and p.Visible and p.Id not in (select pit.Player from PlayerInTeam as pit join pit.Roster as roster join roster.Team as team where team = :teamId)")
.SetParameter("teamId", team.Id)
.SetParameter("teamSex", team.Sex)
.Enumerable<Player>();
}
When I run this query with NHibernate, it will return 2 rows.
If I run the SQL script generated by NH in my database browser (SQLite Explorer):
select player0_.Id as Id26_, player0_.Sex as Sex26_, player0_.FirstName as FirstName26_, player0_.LastName as LastName26_, player0_.DefaultNumber as DefaultN5_26_, player0_.Visible as Visible26_, player0_.DefaultPosition_id as DefaultP7_26_
from Players player0_
where player0_.Sex='Male'
and player0_.Visible=1
and (player0_.Id not in
(select playerinte1_.Player_id
from "PlayerInTeam" playerinte1_
inner join "Roster" roster2_ on playerinte1_.Roster_id=roster2_.Id
inner join Teams team3_ on roster2_.Team_id=team3_.Id,
Players player4_
where playerinte1_.Player_id=player4_.Id
and team3_.Id=2));
I have 3 rows, which is what I should have.
Why are my results different?
Thanks in advance
Mike
I have noticed that sometimes the logged SQL is not exactly the same as the one being really used against the database. The last time I had this issue, it was a problem with trimming the Id value, e.g., where the generated SQL has something like and team3_.Id=2, the SQL being used was actually and team3_.Id='2 ' (or perhaps, player_0.Sex='Male '), which would always fail.
I would suggest you try this HQL:
string hql = #"from Player p where p.Sex = 'Male'
and p.Visible and p.Id not in
(select pit.Player from PlayerInTeam as pit join pit.Roster as roster join roster.Team as team where team = 2)";
return Session.CreateQuery(hql).Enumerable<Player>();
If that works, you need to check if your values have spare whitespaces in them.
I've changed my query like this:
return Session.CreateQuery("from Player p where p.Sex = :teamSex and p.Visible and not exists (from PlayerInTeam pit where pit.Player = p and pit.Roster.Team = :teamId)")
.SetParameter("teamId", team.Id)
.SetParameter("teamSex", team.Sex)
.Enumerable<Player>();
And it now works. I had the idea to use "not exists" after I changed my mappings to try to use LINQ, which gave me the hint.
If you ask why I don't keep LINQ, that's because currently I hide the relationships between my entities as private fields, to force the users of the entities to use the helper functions which associate them. But the wrong thing is that in most cases, that forbids me to use LINQ in my repositories.
But I'm wondering if this wouldn't be better to "un-hide" my relationships and expose them as public properties, but keep my helper functions. This would allow me to use LINQ in my queries.
What do you do in your apps using NH?
Do you think this would be an acceptable trade-off to maintain easy mappings and queries (with the use of LINQ), but with the cost of some potential misuses of the entities if the user doesn't use the helper functions which keep the relationships?

Exclusive filtering by tag

I'm using rails 3.0 and MySql 5.1
I have these three models:
Question, Tag and QuestionTag.
Tag has a column called name.
Question has many Tags through QuestionTags and vice versa.
Suppose I have n tag names. How do I find only the questions that have all n tags, identified by tag name.
And how do I do it in a single query.
(If you can convince me that doing it in more than one query is optimal, I'll be open to that)
A pure rails 3 solution would be preferred, but I am not adverse to a pure SQL solution either.
Please notice that the difficulty is in making a query which does not give all the questions that have any of the tags, but only the questions that have all the tags.
This is the solution I found for myself. Unmodified, it will only work in Rails 3 (or higher).
In the Tag model:
scope :find_by_names, lambda { |names|
unless names.empty?
where("tags.name IN (#{Array.new(names.length, "?").join(",")})", *names)
else
where("false")
end
}
In the Question model:
scope :tagged_with, lambda { |tag_names|
unless tag_names.blank?
joins(:question_tags).
where("questions.id = question_tags.question_id").
joins(:tags).where("tags.id = question_tags.tag_id").
group("questions.id").
having("count(questions.id) = ?", tag_names.count) & Tag.find_by_names(tag_names)
else
scoped
end
}
The & Tag.find_by_names(tag_names) combines the two scopes such that the join on tags is really a join on the scoped model.
[Update]
My sql-fu has improved a little, so I thought I'd offer a pure SQL solution also:
SELECT q.*
FROM (
SELECT DISTINCT q.*
FROM `questions` q
JOIN question_tags qt
ON qt.question_id = q.id
JOIN tags t
ON t.id = qt.tag_id
WHERE t.name = 'dogs'
) AS q
JOIN question_tags qt
ON qt.question_id = q.id
JOIN tags t
ON t.id = qt.tag_id
WHERE t.name = 'cats'
This finds all the questions that have been tagged with both 'cats' and 'dogs'. The idea is to have a nested subquery for each tag I want to filter by.
There are several other ways to this. I'm not sure if it makes a difference to have the subquery in the FROM clause instead of the WHERE clause. Any insight would be appreciated.