doctrine query for chained one-to-many relationships - php

I have three tables A, B, C. A and B have a one-to-many relationship. B and C have another one -to-many relationship. In another word, each A can have multiple Bs, while each B can have multiple Cs.
Now I want to do a query for a given record of A, to get all the related Bs which have related Cs. In another word, for a given a, which is a record in table A, I want to get all of the related Bs from table B, providing that each of the Bs also has more than zero related Cs in table C.
How to write the statement in PHP doctrine? I have some code which doesn't work:
Doctrine_Query::create()->from('B b')->leftJoin('C c')
->andWhere('b.A_id = ?', a.id)->andWhere('c.b_id = b.id');

Why don't you just use an innerJoin ?
With Foo, Bar and Baz (respectively A, B and C) :
Doctrine_Query::create()
->from('Bar bar')
->where('bar.foo_id = ?', $foo->id)
->innerJoin('bar.Baz baz');
This way you'll only get Bar which belongs to Foo, and which have one or many Baz.

From my workings with Doctrine, this is not possible (Doctrine 1 is what I am speaking to).
The work around, which I know sucks, is to do multiple queries. IE take all of the B id's and use them in the whereIN clause and pull them up in a separate query. If someone else has a better method I would be interested in it :)

As premiso noted you need to write at least 2 queries. I would also do it the way he suggested (take all of B id's and use an IN).
To make it more Doctrine-line, look at DQL Subqueries. They already show an example that uses IN in connection with selecting ID`s.
edit: Reading DuoSRX's answer, I think you may have meant what he shows with Inner Joins but not quite sure if I understood the question correct.

Related

Access field of entity included in hidden join

Two entities, A and B, Bextends A. I'm interested in the field B.isOk.
I have this query that joins A with other entities(and because B extends A, if I look at the pure sql, a join is done between A and B even though it is not specified in the query builder-because of the inheritance, great-).
How can I then access that B.isOk if I only have A my query builder? I tried using INSTANCE OF but to no success.
In my yml for A there's specified :
inheritanceType: joined
discriminatorColumn:
name: type
type: text
discriminatorMap:
internal-b: \C\BBundle\Entity\B
Any other suggestions on how could I use that property? I could add an extra join but what's the point since the join I need is already happening behind the curtains.
One alternative to this would be to give up DQL and write my own SQL query.
since this can not be done because OOP principles, it means something might be wrong with my model so another solution would be to refactor the model and design a correct one that fits the current needs.

Multiple Level Discriminator Mapping Doctrine

I've got a 3 level table inheritance, and when I attempt to insert into the database, Doctrine is sensible enough to insert at the lowest level, and the highest level - but not at the middle level.
I'm unsure whether what we're attempting to do is properly supported by Doctrine - the lack of examples on the docs and Google suggest that it might not be. If it is supported, what could I be doing wrong?
EDIT:
A slightly better explanation:
I have 3 tables (A, B and C). I've got a discriminator map such that C extends B, and B extends A. When I try an insert into Table A then Doctrine is clever enough to insert into C and A, but not B.

Doctrine 2 find pages having attributes

I need to fill query builder with filter conditions of page attributes. I got Enities "Page", "Attribute" and "Value"
Page: id, name, etc
Attribute: id, name, etc
Value: page_id, date, string, numeric
I need to get all pages having attribute values selected in form.
So i get Query Builder:
$qb->select('p')
->from(Page p)
->leftJoin(p.values);
Can I use "having" or "where" clauses to do like?
$qb->add('having', 'a.id = :attr1_id AND a.value = :attr1_val')
$qb->add('having', 'a.id = :attr2_id AND a.value = :attr2_val')
UPDATE: No i can't. in this way it can get page with attr1_id = attr2_value AND attr2_id = attr1_value and all conditions will be true, but result is wrong.
or I should add custom join for each value?
UPDATE: here's working exapmle:
//attr1.intval=:a1 (or LIKE, BETWEEN, etc compare)
$comp_expression=$aliace.'.'.$value_field.'=:a'.$this->getId();
//INNER JOIN attribute attr1 ON attr1.type=5 AND attr1.intval=:a1
$qb->innerJoin($value_class, $aliace , 'WITH', $aliace.'.type='.$this->getId().' AND '.$comp_expression);
//where attr1.intval IS NOT NULL
$qb->andWhere($aliace.'.'.$value_field.' IS NOT NULL');
$qb->setParameter('a'.$this->getId(),$value);
UPDATE: So the only way to do key+value filtring is to add join for each filter condition? I got 27 attributes now, so need to build query with 27 joins?
Is there another way to this better? Sorry if this is duplicate, cant find keywords to find same questions.
UPDATE: Maybe i shuld get out of sql query limits and create mysql procedure for it?
UPDATE: Can i use somthing like this in doctrine? MySQL optimization on filtering key-value pairs as records
Model
The model you are using here is called Entity-Attribute-Value (EAV). If you weren't aware of that I suggest you read about the pro's and con's first.
One of the con's is that searching in a EAV model is difficult and very inefficient.
AND queries
It seems you want to query for Entities that have a specific Value for a specific Attribute and another specific Value for a specific Attribute. These types of queries are not very efficient in the EAV model.
Let's assume your entities are set up as:
Entity: id, attributes (one-to-many), values (one-to-many)
Attribute: id, name, entity (many-to-one), values (one-to-many)
Value: id, content, entity (many-to-one), attribute (many-to-one)
Let's assume you want to query for all Entities that have an Attribute color with the value blue. A Doctrine query would look like this:
SELECT e FROM Entity e
JOIN e.values v JOIN v.attribute a
WHERE a.name = 'color' AND v.content = 'blue'
(Of course you should never use values in the DQL like this, but bind them as parameters.)
Now let's assume you want to query for all Entities that have an Attribute color with the value blue and an Attribute shape with the value square. The query becomes:
SELECT e FROM Entity e
JOIN e.values v1 JOIN v.attribute a1
JOIN e.values v2 JOIN v.attribute a2
WHERE a1.name = 'color' AND v1.content = 'blue'
AND a2.name = 'shape' AND v2.content = 'square'
Now if you want to query for 3 Attribute/Value pairs, you'll need 3 of those JOIN sets. If you want to query for 4 Attribute/Value pairs, you'll need 4 of those JOIN sets. Etc.
OR queries
Let's assume you want to query for all Entities that have an Attribute color with the value blue or an Attribute shape with the value square. The query becomes:
SELECT e FROM Entity e
JOIN e.values v JOIN v.attribute a
WHERE (a.name = 'color' AND v.content = 'blue')
OR (a.name = 'shape' AND v.content = 'square')
You can see this is more efficient, you won't need those additional JOIN sets for every additional Attribute/Value pair.
Mixing AND OR
I'm going to be short here: this will become a query nightmare. I strongly advise you not to do this.
Alternative
If your application is going to rely heavily on these types of queries (especially AND and mixing AND OR), I suggest you consider a different type of storage engine. Relational Databases are not really suited for this kind of thing.
You'll probably be better of using a Document Oriented Database, something like Elasticsearch, MongoDB, CouchDB, etc.
Hybrid alternative
You can also "copy" the EAV part to a Document Oriented Database and use it only for search functionality. I suggest you set up events for when changes are made to an Entity (create, update, delete). Then create listeners that persist those changes into the Document store.
This way your application can work with the Relational Database for normal operations, and the Document store for searching. In this case I advise Elasticsearch, which is extremely suitable for this kind of thing.
There is no such difference between the two methods, the two methods do exactly the same thing.
By the central point of question, I got the doubt is about the construction of query using like.
The first example you provide is workfull.
The second example you propose is not right.
On the second example, you need to define All the where clauses on andWhere method.
$qb->innerJoin('a1 ON p.id=a1.page_id');
$qb->andWhere('a1.attribute_id=:attr1_id ');
$qb->andWhere('a1.value=:attr1_val');
$qb->andWhere('a1 is not null');
All are you doing is creating a sql by a third api, Doctrine ORM is one of the most successful orm tool for php, knowing how to use it will let you to experience the ORM power, by doctrine implementation all you need to know are there:
Doctrine Create Query Builder -> The same are you using.
Doctrine Query Builder
Doctrine Create Query -> Simplest form
Query Language

Search Logic to access more than 6 related tables efficient

in my current project, the task is to be able to search through 6 tables.
Tables are:
A, B, C, D, E, F
All tables are related to each other through another table in the list.
E.g. A->B B->A->C C->F F->D
Like User->Phonenumber Phonenumber->Country etc.
Since the searchmask/form the user gets presented leaves the possibility open to send only certain parameters to the server it occurs that the search must find a result R no matter which parameter the user passes through.
In this case a parameter is a column value of any of the above listed tables, e.g. Phonenumber->Provider = Verizon means, the user wants all entries with the provider = Verizon.
The actual problem is not searching in specific tables, it is searching in specific tables an retrieving the right result from the results you found in the specific tables.
Let me explain: Result, in our case is NOT a table object of the table we currently searched through.
A Result should always be the same object Result which must have at least one entry in A and B.
Example Requirements:
A = User exists
B = he has a Phonenumber
Since all tables have relations to each other directly or indirectly we could access a result without searching for columns in A or B only by looking into the related tables retrieving the rows with Provider=Verizon and JOINing on the related tables via ids/keys/constraints.
Currently I do more than one query.
I first check the type of the parameters.
"Are they columns from A to C"
Yes, then do a direct SELECT on A, B or C and take the result as Result.
"Are they columns from E"
Yes, then do a SELECT on E, after that another SELECT on C with c_id from E.
As Result (in PHP) I always want to have an object/array containing A.value1, B.value1
I never want any information about C, D, E or F.
I only want to get Result consisting of A.value1 and B.value1 by any allowed/searchable parameter that is represented in a column in one or more of the tables, e.g. Provider=Verizon AND Country=China AND OnVaccation=True
I am sorry for the complexity.
I think, for this you could use Sphinx Search Engine at http://sphinxsearch.com/ . You should set up SQL query which will be used for building search index. After this from PHP you could use simple API for performing search.

Doctrine DQL, class table inheritance and access to subclass fields

I have a problem with a DQL query and entity specialization.
I have an Entity called Auction, which is OneToOne relation with Item. Item is a mappedSuperclass for Film and Book. I need a query that could back a search engine, allowing the user to look for auctions with different properties AND selling items with different properties (it is the AND part that makes it challenging).
The problem is that even though Auction has an association pointing to Item as such, I need to have access to Film- and Book-specific fields. The users will specify the Item type they're looking for, but I don't see any way of using this information other than using INSTANCE OF in my DQL query.
So far, I have tried using a query like:
SELECT a FROM Entities\Auction a
INNER JOIN a.item i
INNER JOIN i.bookTypes b
WHERE i INSTANCE OF Entities\Book
AND b.type = 'Fantasy'
AND ...".
Such a query results in an error saying that:
Class Entities\Item has no field or association named bookTypes
which is false for Book, yet true for Item.
I have also tried
SELECT a FROM Entities\Book i
INNER JOIN i.auction a ...
but I reckon Doctrine requires that I refer to the same Entity in SELECT and FROM statements.
If that's of importance, I am using class table inheritance. Still, I don't think switching to single table inheritance would do the trick.
Any ideas?
As Matt stated, this is an old issue that Doctrine Project won't fix (DDC-16).
The problem is that doctrine's DQL is a statically typed language that comes with a certain amount of complexity in its internals.
We thought about allowing upcasting a couple of times, but the effort to get that working is simply not worth it, and people would simply abuse the syntax doing very dangerous things.
As stated on DDC-16, it is also indeed not possible to understand which class the property belongs to without incurring in nasty problems such as multiple subclasses defining same properties with different column names.
If you want to filter data in subclasses in a CTI or JTI, you may use the technique that I've described at https://stackoverflow.com/a/14854067/347063 . That couples your DQL with all involved subclasses.
The DQL you would need in your case is most probably (assuming that Entities\Book is a subclass of Entities\Item):
SELECT
a
FROM
Entities\Auction a
INNER JOIN
a.item i
INNER JOIN
i.bookTypes b
WHERE
i.id IN (
SELECT
b.id
FROM
Entities\Book b
WHERE
b.type = 'Fantasy'
)
That is the pseudo-code for your problem. It is not nice, but keep in mind that SQL and DQL are very different and follow different rules.
Updated:
I've discovered a solution for this. See my answer for this related question:
Doctrine2: Polymorphic Queries: Searching on properties of subclasses
You can easily solve this by left-joining your base entity with your inheritance class using the id:
SELECT a FROM Entities\Auction a
INNER JOIN a.item i
INNER JOIN Entities\Book b WITH b.id = i.id
INNER JOIN b.bookTypes bt
WHERE bt.type = 'Fantasy'
AND...
or with a queryBuilder:
$queryBuilderb->select('a')
->from('Entities\Auction', 'a')
->innerJoin('a.item', 'i')
->innerJoin('Entities\Book', 'b', 'WITH', 'b.id = i.id')
->innerJoin('b.bookTypes', 'bt')
->where('bt.type = :type')
->andWhere(...
->setParameter('type', 'Fantasy');
This is based on the answer given by Ian Philips in the question here
The Doctrine team has stated that they're not going to add support for this:
https://github.com/doctrine/orm/issues/2237
Pertinent comments from that page:
Thats indeed tricky. That syntax alone can, however, never work,
because there might be several subclasses that have a field named "d",
so Doctrine would not know which field you mean.
I am closing this one.
The requirement of this issue is basically violating OO principles.
If you really need to filter across multiple child-entities in your
inheritance, then try something as following instead:
SELECT
r FROM
Root r WHERE
r.id IN (
SELECT
c.id
FROM
Child c
WHERE
c.field = :value
)
I had the same issue, and didn't find a solution without using separate queries for each subclass and merging them later on the application level.
One thing I'm sure, single table inheritance will not solve this, completely the same thing.
There is another alternative, although being logically dirty.
Define all the fields (the ones you need) in the superclass. If the record logically doesn't have that field it will be empty. Not a pretty sight, but hey, more optimized than 2-3-4-... queries. Also in this scenario single table inheritance is definitely the better way to go

Categories