Select entities assuming related entity does not exist - php

My entity (Product) has a unidirectional many-many relation with another entity (Company) called deletedByCompanies.
I wish to select all Products that has not been deleted by a particular company. That is all products that are not connected through that many-many relation.
Tried:
$this->em->getRepository(Product::class)->createQueryBuilder('t')
->leftJoin('t.deletedByCompanies', 'deletedCompany')
->andWhere('deletedCompany.id not in (:companyId)')
->setParameter('companyId', [$companyId]);
But this simply does not return anything.
Schema is pretty straghtforward:
Product:
id: int PK
Company:
id: int PK
DeletedProducts
product_id: int FK
company_id: int FK
Entity definition in Product class:
/**
* #var Company[]
* #ORM\ManyToMany(targetEntity="Company", indexBy="id")
* #ORM\JoinTable(name="DeletedProducts")
*/
protected $deletedByCompanies;

I think you can solve your problem with a NOT EXISTS clause on the deletedby table.
In the SQL dialect:
SELECT * FROM product p WHERE NOT EXISTS
(SELECT * FROM DeletedProducts d WHERE p.id=d.product_id AND company_id = 2 );
In the Doctrine2 DQL, we haven't the entity DeletedProducts so we have to do a bit more stuff like:
$qb = $this->em->getRepository("AcmeDemoBundle:Product")->createQueryBuilder('t')
->Join('t.deletedByCompanies', 'deletedCompany')
->andWhere('deletedCompany.id in (:companyId)')
->andWhere("p=t");
$mainQb = $this->em->getRepository("AcmeDemoBundle:Product")->createQueryBuilder('p');
$mainQb->where($mainQb->expr()->not($mainQb->expr()->exists($qb->getDQL())));
$mainQb->setParameter('companyId', [$companyId]);
var_dump($mainQb->getQuery()->getSql());
$res =$mainQb->getQuery()->execute();
Let me know if I don't understand your problem.
Hope this help

I'm not familiar with Doctrine, but I'm trying to help with some SQL knowledge. Following query should do what you want:
SELECT DISTINCT Product.* FROM Product
LEFT JOIN DeletedProducts on product_id = product.id
WHERE product_id IS NULL OR product_id !=
ALL( SELECT product_id FROM DeletedProducts WHERE company_id = 2 )
Some Explanation...
DISTINCT: Nessacary keyword to prevent redundancy. The same product can appear multiple times in the result of the Left Join if it is deleted by . DISTINCT eliminates those duplicates.
WHERE product_id IS NULL: The Left Join will also list the products which are not related to any company over the "DeletedProducts"-table. Since there is no relation the fields product_id and company_id are NULL.
OR product_id != ALL( [...] ): Now that we have got the products which aren't deleted by any company, we need additionally those which aren't deleted by a particular company. So we use an OR with a subquery which selects all deleted products of a certain company (e.g. company id = 2 as in the code example). Since we want to have the not-deleted products we have to use the "!="-Operator.
I hope this helps a little. Now it's up to you to "translate" the query for the usage within Doctrine.

Related

Selecting from SQL database where row exists with user id then get all other rows with similar value

I'm not sure why but I'm hitting an absolute wall trying to come up with this select statement. Maybe there is a PHP/MYSQL function that I'm not familiar with that would help. The idea is simple for this user management software: there are managers, and managers can (but do not have to) share clients. Amongst the manager and shared client relationship, one of the managers can be assigned as a lead. So here's how the basic example of what the database looks like for 1 client that is shared between 2 managers and assigned, and another client that is also shared but NOT assigned (represented by zero).
DROP TABLE IF EXISTS clients;
CREATE TABLE clients
(client_id SERIAL PRIMARY KEY
,client_name VARCHAR(12) UNIQUE
);
INSERT INTO clients VALUES
(555,'Jimmy'),
(789,'Tyler');
DROP TABLE IF EXISTS managers;
CREATE TABLE managers
(manager_id SERIAL PRIMARY KEY
,manager_name VARCHAR(12)UNIQUE
);
INSERT INTO managers VALUES
(123,'Michael'),
(456,'David');
DROP TABLE IF EXISTS relationships;
CREATE TABLE relationships
(client_id INT NOT NULL
,manager_id INT NOT NULL
,assigned INT NOT NULL
,PRIMARY KEY(client_id,manager_id)
);
INSERT INTO relationships VALUES
(555, 123, 0),
(555, 456, 1),
(789, 123, 0),
(789, 456, 0);
To get to the point: the statement I'm trying to make is for a manager to be shown all the clients that he has a relationship with, but are NOT assigned to him or anyone else on his team, i.e. select all of my clients where no one is assigned as the lead.
Expected input: Show all clients that manager 123 has a relationship with, but have yet to be assigned to any manager
Expected result: client 789
Happy to clarify as I can see this being overtly confusing as described.
SELECT c.*
FROM managers m
JOIN relationships r
ON r.manager_id = m.manager_id
JOIN clients c
ON c.client_id = r.client_id
LEFT
JOIN relationships x
ON x.client_id = c.client_id
AND x.assigned = 1
WHERE m.manager_id = 123
AND r.assigned = 0
AND x.client_id IS NULL;
+-----------+-------------+
| client_id | client_name |
+-----------+-------------+
| 789 | Tyler |
+-----------+-------------+
So you will have to start with finding all clientid's from the manager in RELATIONS, but remove all that have a manager assigned already.
Depending on the size of the table you might want to rewrite this, but here is one approach:
1) Get all clientids that have no manager:
SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships AS R1 GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0)
Now it is easier, you just join, eg:
SELECT R2.client_id, R2.manager_id FROM relationships AS R2
INNER JOIN
(SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships AS R1 GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0) ) AS DRVNOMANAGER
ON (R2.client_id = DRVNOMANAGER.client_id)
WHERE (R2.manager_id = 123)
Not tested. (meaning you might have to fix it)
The idea is to create a derived (temp) table containing all clients without a manager, then do an inner join on your original question ("what clients does this manager 123 know that do not have another assigned)
Does that solve your problem?
PS: Such things are much easier solved in PHP, but if your dataset is huge, that is not feasible.
OP asked for clientnames, so just add that:
SELECT R2.client_id, R2.manager_id, C.client_name FROM relationships AS R2
INNER JOIN
(SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships
AS R1 GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0) ) AS DRVNOMANAGER
ON (R2.client_id = DRVNOMANAGER.client_id)
INNER JOIN clients AS C ON (C.client_id = R2.client_id)
WHERE (R2.manager_id = 123)
And last request: removed deleted relations:
Simply add a WHERE clause to the inner DRVNOMANAGER with your restriction on the rows that are used in the GROUP BY. eg:
SELECT R2.client_id, R2.manager_id, C.client_name FROM relationships AS R2
INNER JOIN
(SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships
AS R1 WHERE (NOT(R1.deleted = 1) ) GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0) ) AS DRVNOMANAGER
ON (R2.client_id = DRVNOMANAGER.client_id)
INNER JOIN clients AS C ON (C.client_id = R2.client_id)
WHERE (R2.manager_id = 123)
==============================================
THIS WAS MY OLD ANSWER. NOT RELEVANT ANYMORE.
"select all of my clients where no one is assigned as the lead."
If I read you well that means: Get all clientid from RELATIONS where some managerid is given, AND assigned=0. (assigned=0 meaning "no one is assigned as the lead.")
Is that correct?
Then you end up with something like this (for managerid 123):
SELECT R.clientid, C.clientname FROM RELATIONSHIPS AS R WHERE ( (R.managerid = 123) AND (R.assigned=0))
INNER JOIN CLIENTS AS C ON (C.clientid = R.clientid)
I removed the spaces in the columnnames because I hate spaces in columnnames.

How get needed rows from 3 table in Laravel 5.5

I have 3 tables, Order, Products, Order_Products. I need get all field from order and products, thats ok using hasManyThrough(). But i need 1 more field from Order_products. How can i get this field ?
public function products()
{
//in order model
return $this->hasManyThrough('App\Models\Product','App\Models\OrderProduct','order_id','id','id','product_id');
}
using sql i need query like
SELECT
products.*, order_products.order_id, order_products.count as order_count
FROM
products
INNER JOIN order_products ON order_products.product_id = products.id
WHERE
order_products.order_id = 2
You can access intermediate table fields by using pivot attribute on model.
Lets say you have product, Then you can access count field of orders_products
$product->pivot->count;

Left join with filter

I'm trying to display a list of books from a table (books) but I want to display the name of the book in green if the user connected added the book in his collection. The collections are in the table collection. So I need to retrieve all the informations from the books table but I need to differentiate the books owned by the user.
I tried something like this :
SELECT * FROM books LEFT JOIN collection ON books.ID = collection.ID_book AND collection.ID_member = :ID_member WHERE books.ID_author = :ID_author
:ID_member is the ID in session of the member logged in and ID_author is the name of the author I want to display the list. But with that query, I cannot retrieve the ID of books that the user doesn't own. Do you have an idea to retrieve these ID too ?
I thought of a second query inside the while() but that would means a query PER book...
EDIT (more informations) :
I have a table books with all the informations concerning the books.
Then, I have a table collection with these columns : ID, ID_member, ID_book
With my query, if I want to display all books from... let's say Stephen King, I have something like this :
book || ID of the book || book owned by the user
1 || 1 || 0
2 || 2 || 0
3 || || 1
4 || 4 || 0
5 || || 1
6 || 6 || 0
The ID I want to display is the collection.ID_book because if I display ID, it will show collection.ID
So as you can see when the user own the book I can't have the ID of the book... but I can display the other informations (like the book title) because the other informations are taken from the books table and the ID from the collection table... Too bad that it's not possible to chose the table in the
I understand that the problem is that the ID column exists in both tables. One solution may be to duplicate the ID column, like ID2. Then it will work because ID2 doesn't exist in the collection table. But that is maybe too much...
Thank you for your help.
Regards.
It is very easy to set columns up with aliases:
SELECT books.ID AS book_id, collections.id AS collection_id
FROM books
LEFT JOIN collection ON books.ID = collection.ID_book AND collection.ID_member = :ID_member
WHERE books.ID_author = :ID_author
After you have done this your column names will be book_id and collection_id and since they are no longer duplicate names (with one overwriting the other) you can access each of them.
Then if you know that you want all the columns from books but only the idcolumn from collection you can do something like this:
SELECT books.*, collections.id AS collection_id
FROM books
LEFT JOIN collection ON books.ID = collection.ID_book AND collection.ID_member = :ID_member
WHERE books.ID_author = :ID_author
I think your query should be doing what you want. I suspect that the problem may be the select * and multiple columns with the same name. Does this query do what you want?
SELECT b.*,
(case when c.ID_book is not null then 'GREEN' else 'NOT OWNED' end) as color
FROM books b LEFT JOIN
collection c
ON b.ID = c.ID_book AND c.ID_member = :ID_member
WHERE b.ID_author = :ID_author;
I think you can solve this problem by changing your query slightly, maybe give this a try.
SELECT * FROM books
LEFT JOIN collection ON books.ID = collection.ID_book
WHERE books.ID_author = :ID_author OR collection.ID_member = :ID_member
When joining on the condition of the user id, you're only going to have those rows available in the result set. By moving that condition to the WHERE clause using the OR operator, you can have both conditions met.
NOTE: Try not to use SELECT *.
SELECT * FROM books LEFT JOIN collection
ON books.ID = collection.ID_book
INNER JOIN member ON collection.ID_member = member.:ID_member WHERE books.ID_author = :ID_author
would you try that sir.. I'm not sure though. I'm assuming that the ID_member fields came from a member table.. and the ID_member to collection table is a foreign key..

How to write IF ELSE statements in MySQL

I have tables posts and users
basically i want to query if the column privacy of posts table is 0 it means post is public and i want to show this to everyone. but if privacy column is 1,2,3,4 it means this post is only for users with ID 1, 2, 3 and 4
This is not a conditional problem it's a sql join through table association problem.
You need to create a join table that associates a post to a user. You can then use a join to select posts based upon their assignment to a user.
The SQL might look something like this
To select posts assigned to user ID = 4
SELECT * FROM `posts`
LEFT JOIN `posts_users` ON (`posts`.`id` = `posts_users`.`post_id`)
WHERE `posts_users`.`user_id` = 4;
You have also made the assumption that if a post has no users assigned to it, then it's a public post. That requires a check for NULL associations.
SELECT * FROM `posts`
LEFT JOIN `posts_users` ON (`posts`.`id` = `posts_users`.`post_id`)
WHERE `posts_users`.`user_id` IS NULL;
If you want to find both public and associated posts, then you use a logical OR to match both rules.
SELECT * FROM `posts`
LEFT JOIN `posts_users` ON (`posts`.`id` = `posts_users`.`post_id`)
WHERE `posts_users`.`user_id` = 4
OR `posts_users`.`user_id` IS NULL;

Where query from other table + Doctrine

I'm stuck with creating a where query. I have table item with item_id, item_title, item_description, item_created, item_approved. I also have a table article with PK item_id (from item table) and article_body. The last table is media with medium_id, item_id (FK), medium_url and medium_type.
Now I would like to select all the data from media where item.item_approved is not NULL and where item.item_id ins't present in the article table.
Now I can select all the data from media where item.item_approved is not NULL. But now I need to do another check that he doesn't select the items that are also in article table. My query so far:
$repository = $entityManager->getRepository('VolleyScoutBundle:Media');
$query = $repository->createQueryBuilder('m')
->join('m.item', 'i')
->where('i.itemApproved is not NULL')
->getQuery();
Most likely that you must use 2 queries. With JOINs it can not be done.

Categories