Laravel ManyToMany Join On Condition - php

Greetings to everyone.
How can I add condition to "join on" for belongsToMany (many to many) relation in Laravel 5.3?
For example, let's say here are Products and Parameters of those Products with many to many relation. When I want to get all Parameters for specific Product, it is just fine. I do $product->parameters and it gives me what I need with query like this:
SELECT
parameters.*,
product_parameters.product_id AS pivot_product_id,
product_parameters.parameter_id AS pivot_parameter_id
FROM parameters
INNER JOIN product_parameters ON parameters.id = product_parameters.parameter_id
WHERE product_parameters.product_id = 1;
Problem appears when I want to define some "Global" flag on Parameter which defines that Parameter should be bound to every Product. With plain SQL, all I have to do is to add condition to JOIN, OR parameters.global = 1, like this:
SELECT
parameters.*,
product_parameters.product_id AS pivot_product_id,
product_parameters.parameter_id AS pivot_parameter_id
FROM parameters
INNER JOIN product_parameters ON parameters.id = product_parameters.parameter_id OR parameters.global = 1
WHERE product_parameters.product_id = 1;
But I didn't find if there is correct and clean way to do it with Laravel to be able to get Eloquent\Relation object back from function with additional data I require.
I have found "bad" way, which is to "fetch" join which I need from query builder object and add another "where" to it, but it is very ugly and I'm not sure that it will work in future without problems.
This is what I have tried to do and it almost worked (have problem with incorrect number of PDO parameters, but it generates SQL which I need):
$relation = $this->belongsToMany(Parameter::class, 'product_parameters');
$relation->getQuery()->getQuery()->joins[0]->where('parameters.definition', '=', 0, 'or');
Any help or advice is appreciated. Thank you.

I have left original idea to place everything in queries, tried to do several requests and combine resulting Eloquent Collections. Then I did some research and in the end I decided that what I was going wrong way, and it is better to use patterns like Repository and Specification to achieve required logic.

Related

check if query builder already issued where or like?

I am so convenient using built-in codedigniter query builder.
I have to output searching to an existing page since that page involves multi-table.
Currently, I already have a lot of if there,
I am curious how to check if statement db->where() or db->like() have been called or not?
My second option is to construct new query that join 2 tables, but my table does not have any direct relationship, I try to use $this->db->join() it fails on the join condition. I am lack of example of using join, especially the join type. I am expecting to use something like
SELECT * FROM p JOIN hs ON hs.id=%-p.id-%
I am already stuck here, and I am aware that the db->get() will erase the data filtering.
Any suggestion?

Correct way to handle loading doctrine entities with multiple associations

I'm currently building an eCommerce site using Symfony 3 that supports multiple languages, and have realised they way I've designed the Product entity will require joining multiple other entities on using DQL/the query builder to load up things like the translations, product reviews and discounts/special offers. but this means I am going to have a block of joins that are going to be the same in multiple repositories which seems wrong as that leads to having to hunt out all these blocks if we ever need to add or change a join to load in extra product data.
For example in my CartRepository's loadCart() function I have a DQL query like this:
SELECT c,i,p,pd,pt,ps FROM
AppBundle:Cart c
join c.items i
join i.product p
left join p.productDiscount pd
join p.productTranslation pt
left join p.productSpecial ps
where c.id = :id
I will end up with something similar in the SectionRepository when I'm showing the list of products on that page, what is the correct way to deal with this? Is there some place I can centrally define the list of entities needed to be loaded for the joined entity (Product in this case) to be complete. I realise I could just use lazy loading, but that would lead to a large amount of queries being run on pages like the section page (a section showing 40 products would need to run 121 queries with the above example instead of 1 if I use a properly joined query).
One approach (this is just off the top of my head, someone may have a better approach). You could reasonably easily have a centralised querybuilder function/service that would do that. The querybuilder is very nice for programattically building queries. The key difference would be the root entity and the filtering entity.
E.g. something like this. Note of course these would not all be in the same place (they might be across a few services, repositories etc), it's just an example of an approach to consider.
public function getCartBaseQuery($cartId, $joinAlias = 'o') {
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select($joinAlias)
->from('AppBundle:Cart', 'c')
->join('c.items', $joinAlias)
->where($qb->expr()->eq('c.id', ':cartId'))
->setParameter('cartId', $cartId);
return $qb;
}
public function addProductQueryToItem($qb, $alias) {
/** #var QueryBuilder $query */
$qb
->addSelect('p, pd, pt, ps')
->join($alias.'product', 'p')
->leftJoin('p.productDiscount', 'pd')
->join('p.productTranslation', 'pt')
->join('p.productSpecial', 'ps')
;
return $qb;
}
public function loadCart($cartId) {
$qbcart = $someServiceOrRepository->getCartBaseQuery($cartId);
$qbcart = $someServiceOrRepository->addProductQueryToItem($qbcart);
return $qbcart->getQuery()->getResult();
}
Like I said, just one possible approach, but hopefully it gives you some ideas and a start at solving the issue.
Note: If you religiously use the same join alias for the entity you attach your product data to you would not even have to specify it in the calls (but I would make it configurable myself).
There is no single correct answer to your question.
But if I have to make a suggestion, I'd say to take a look at CQRS (http://martinfowler.com/bliki/CQRS.html) which basically means you have a separated read model.
To make this as simple as possibile, let's say that you build a separate "extended_product" table where all data are already joined and de-normalized. This table may be populated at regular intervals with a background task, or by a command that gets triggered each time you update a product or related entity.
When you need to read products data, you query this table instead of the original one. Of course, nothing prevents you from having many different extended table with your data arranged in a separate way.
In some way it's a concept very similar to database "views", except that:
it is faster, because you query an actual table
since you create that table via code, you are not limited to a single SQL query to process data (think filters, aggregations, and so on)
I am aware this is not exactly an "answer", but hopefully it may give you some good ideas on how to fix your problem.

Doctrine2 Lazy vs Eager, or why it makes multiple queries

So I am using QueryBuilder in some of my projects, but others I need to create RAW SQL queries in order to increase performance, as I have more than a million rows with their relations...
What I found awful about QueryBuilder, is the fact that it creates several queries when you have relations, for example, I have a OneToMany relation from Product to Image and a ManyToOne on the inversed side.
My query has a pagination, so it is limited to LIMIT 10 OFFSET 0 and on. My Image entity has about 2.7 million rows, that's why I am using pagination, doing this simple query, which fetches Image i plus Product p because I need p.title I end up with 1 Query for my 10 images, and 10 queries for each Product for Image.
That's unneeded, It can be done with just 2 queries, one for Image and one for Product, using fetch="EAGER" that's what I get. But I need to put fetch="EXTRA_LAZY" inside Product mapping, otherwise I will get 11 queries again..
With just 10 images isn't hard, but when the user filters 500 images, response time gets higher and higher... That's why I've ended up doing RAW queries, best performance, no extra queries (just 1 query that contains everything) BUT unable to work with objects like QueryBuilder does, can't access to image.product.title inside twig to get the title, instead I need to do SELECT p.title AS product_title and call image.product_title etc
So I need to know why QueryBuilder is that sh*t when reading but so marvelous when persisting objects (easy, fast, clean...) and how can I work with huge DB's using QueryBuilder without loosing performance and without getting tons of extra unneeded queries.
An example query, is this one
$qb = $this->createQueryBuilder('i');
$qb->innerJoin('i.product', 'p');
$qb->where('i.X = Y');
return $qb->getQuery()->getResult()
Using $qb->select('i, p'); seems to use only one query, which it's runnable raw has an INNER JOIN (which is actually how it is supossed to work WITHOUT the $qb->select()) but performance is still lower than doing a RAW SQL query... RAW SQL = 500MS for a 10.000 rows query, using QB it's 1,100 MS. I know I wont use 10.000 rows, but there's a chance...
The question is still the same, what advantages and disadvantages, besides the object manipulation, which is gone with a RAW SQL. And when to use LAZY or EAGER and WHY, or why/when you don't need them.
All of this may end a discussion in my DevTeam once for all. As I'm a QB lover.
Did you do something like this:
SELECT i FROM AcmeBundle:Image i JOIN i.product p WHERE ...
?
That would explain numerous queries because Doctrine does not preserve fetched data.
Doing something like this tells Doctrine to actually preserve fetched data of both Image and Product:
SELECT i, p FROM AcmeBundle:Image i JOIN i.product p WHERE ...
Then, you would have no need neither for EAGER nor EAGER_LAZY.
I might have missed the point in your question. If I have, please correct me and I might be able to suggesting something else.
Edit:
$qb = $this->createQueryBuilder('i');
$qb->innerJoin('i.product', 'p');
$qb->addSelect('p'); // Very importang, hints Doctrine to preserve fetched Product
$qb->where('i.X = Y');
return $qb->getQuery()->getResult()
Or using PARTIAL:
$qb = $this->createQueryBuilder('i');
$qb->innerJoin('i.product', 'p');
$qb->select('PARTIAL i.{image_field1, image_field2}', 'PARTIAL p.{product_field1, product_field2}'); // Very importang, hints Doctrine to preserve fetched Product
$qb->where('i.X = Y');
return $qb->getQuery()->getResult()

Doctrine 2.1: Getting and assigning COUNT(t.id) from a subquery?

I have two entities in Doctrine 2.1: Category and Site each category has many sites and each site has a parent category.
I would like to make a single update query (in DQL) which will update a field called count of the Category entity with the number of related sites.
So in SQL I would do something like this:
UPDATE categories c SET c.count = (SELECT COUNT(s.id) FROM sites s WHERE s.category_id = c.id);
This would work beautifuly, in DQL it might something like this:
UPDATE PackageNameBundle:Category c SET c.count = (SELECT COUNT(s.id) FROM PackageNameBundle:Site s WHERE s.category = c)
Such attempt raises [Syntax Error] line 0, col 61: Error: Expected Literal, got 'SELECT'.
Subqueries DO work in DQL, but the problem here (as far as I see it) is that Doctrine cannot assign the returned value from the subquery, to the c.count. This is understandable since I might fetch more than 1 field in the subquery and even more than one row. It magicaly works in MySQL since it sees one row, one field and for convenience returns a single integer value. Doctrine on the other hand has to be object oriented and has to work with different engines where such convertions might not be supported.
Finally, my question is:
What is the best way to do this in Doctrine, should I go with Native SQL or it can be done with DQL and how?
Thanks in advance!
EDIT: I just found this quote in the DQL Docs:
References to related entities are only possible in the WHERE clause and using sub-selects.
So, I guess assigning anything but a scalar value is impossible?
The main question remains though..
You can use native sql queries in Doctrine also, for that kind of specific queries. DQL is powerful in its own way, but it's also limited due to performance constraints. Using native sql queries and mapping the results will achieve the same thing, and there is no disadvantage in doing that.
The documentation explains it in detail.

Behavior Difference between find('count') and find('all') cakePHP

I am having an odd problem in CakePHP where
$this->something->find('count');
works perfectly, yet
$this->something->find('all');
returns nothing (not even an empty array, any errors, or anything).
edit: turns out I am getting an sql error: "SQL Error: 1054: Unknown column" - for a column that does indeed exist. (users.display_name in the sql query below):
SELECT item.id, item.name, item.description, item.user_id, users.display_name FROM item LEFT JOIN users ON (item.user_id = users.id);
I also tried using findAllBy as well as paginate (paginate is actually what I am trying to do - although from what I've gathered, paginate and find('all') are pretty similar in functionality).
The odd thing is that find('all') works everywhere else - it's just in this specific controller that it is acting odd. I am not getting any errors, simply an empty result.
I'm thinking that I may be overlooking something quite simple, but any help is appreciated. Thanks!
So, as per our discussion, the problem you're having is with the virtual fields. Have a look at the documentation, more specifically at the virtual fields and model aliases and limitation of virtualFields sections.
From your description above, it looks like you have a join specified your virtual field which would be causing the error you're seeing because it'll add the JOIN before the FROM. If you insist on using the virtual field, I'd suggest you rewrite it to use a subquery. Make sure your subquery only returns 1 column.
Example: (http://web-development-blog.co.uk/2011/03/08/cakephp-virtual-field-count-another-modeltable/)
public $virtualFields = array(
'count' => 'SELECT COUNT(*) FROM stacks Stack'
);
Alternatively, you can use the Model::beforeFind to bind the necessary models (if necessary) and change the query parameters.
If you can't figure it out, please post your model and I'll help you.
The specific problem you're having with the difference in behaviour is that find('count') will run a basic COUNT(*) query on your database to determine the number of rows.
find('all'), however, runs a different query, and if the SQL you've provided is what it's trying to use, it's invalid:
SELECT item.id, item.name, item.description, item.user_id, users.display_name LEFT JOIN users ON (item.user_id = users.id);
There's no FROM declaration (SELECT from what, exactly?), and if you've not customised your Item or User or App models, (setting $useTable = false maybe?) you're dealing with an unusual error.
One thing to look out for, if these models work fine in other controllers, is any alteration to the properties of each model in each controller. They won't act differently on a per-controller basis unless you have code in each controller that tells it to.

Categories