Doctrine select all users that manage other users via column - doctrine

Maybe I am missing something completely, but I cannot get it working. I only want to select User objects that are linked to User objects.
User:
class User implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// other fields ...
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
*/
private $firstManager;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
*/
private $secondManager;
}
I want to select those who are firstManager or secondManager for a User. Sounds simple eh?
I thought, this would do:
public function findAllManagers()
{
$qb = $this->createQueryBuilder('user')
->join('user.firstManager', 'first_manager')
->join('user.secondManager', 'second_manager')
->orWhere('first_manager = user')
->orWhere('second_manager = user');
$qb = $qb->getQuery();
return $qb->getResult();
}
But only got one result, not all three I needed. I think this is valid SQL?

For what i see your sql query should look like:
SELECT user.* FROM user_table user
INNER JOIN user_table first_manager ON first_manager.id = user.first_manager_id
INNER JOIN user_table second_manager ON second_manager.id = user.second_manager_id
WHERE first_manager.id = user.id
OR second_manager.id = user.id
The result of this would be users who are their own first or second manager.
I think what you are looking for is this:
public function findAllManagers()
{
$qb = $this->createQueryBuilder('m')
->leftJoin('u1', 'AppBundle\Entity\User', 'WITH', m = u1.firstManager)
->leftJoin('u2', 'AppBundle\Entity\User', 'WITH', m = u2.secondManager)
->where('u1.firstManager IS NOT NULL')
->orWhere('u2.secondManager IS NOT NULL')
->getQuery()
;
return $qb->getResult();
}
The equivalent SQL query should be:
SELECT m.* FROM user_table m
LEFT JOIN user_table u1 ON u1.first_manager_id = m.id
LEFT JOIN user_table u2 ON u2.second_manager_id = m.id
WHERE u1.first_manager_id IS NOT NULL
OR u2.second_manager_id IS NOT NULL

->join() results in an INNER JOIN. As you can read here, this will only give results, that are "in the center". If you have multiple JOINs, this will only give results of all those joined tables. In your case, this means users that are firstManager and secondManager.
If you change to ->leftJoin(), this will give you all users (with additional info), so it is a good place to start. Then you can filter those out that are neither firstManager or secondManager.
Something like this should work (untested)
$result = $this->createQueryBuilder('user')
->leftJoin('user.firstManager', 'fm')
->leftJoin('user.secondManager', 'sm')
->where('fm.id IS NOT NULL')
->orWhere('sm.id IS NOT NULL')
->getQuery()
->getResult()
;

Related

hasManyThrough query without laravel_through_key

With a Laravel hasManyThrough relationship how can I prevent extra keys added to the select? Is there a withoutPivot or simiar?
I'm trying to perform a union join and need to the columns to be the same.
SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns
SELECT
count(*) AS aggregate
FROM ((
SELECT
`order_payments`.`id`,
`order_payments`.*,
`orders`.`user_id` AS `laravel_through_key`
FROM
`order_payments`
INNER JOIN `orders` ON `orders`.`id` = `order_payments`.`order_id`
WHERE
`orders`.`user_id` = 1)
UNION (
SELECT
`orders`.`id`
FROM
`orders`
WHERE
`orders`.`user_id` = 1
AND `orders`.`user_id` IS NOT NULL)) AS `temp_table`
I'm unable to use the makeHidden as suggested in another question
$payments = auth()->user()->orderPayments()->select('order_payments.id');
$orders = auth()->user()->orders()->select('orders.id');
$payments->union($orders)->paginate(50);
I guess I could do it without Eloquent manually but just wondering if there was another way?
class User extends Authenticatable implements UserContract {
/**
* Orders for this User
*/
public function orders()
{
return $this->hasMany(Order::class);
}
/**
* Order Payments for this User
*/
public function orderPayments()
{
return $this->hasManyThrough(OrderPayment::class, Order::class);
}
}

How to shuffle results in Lighthouse GraphQL (order by random)?

I see docs for #orderBy but am curious how I could sort my Lighthouse GraphQL results randomly, kind of like inRandomOrder in Laravel:
$randomUser = DB::table('users')
->inRandomOrder()
->first();
Or like RAND() in MySQL:
SELECT col1, col2
FROM mytable
ORDER BY RAND();
Currently it is not possible out of the box with lighthouse since there is no RAND SortOrder Enum.
You could use a scope for that.
Suppose you want to grab randomly some users from your table. Create a scope in you user query in your schema.graphql
type Query {
posts(
random: Boolean #scope(name: "random")
): [User!]!
}
Create the scope in your App\User.php:
// ...
/**
* Shuffles the users randomly
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeRandom($query) {
return $query->inRandomOrder();
}
// ...
Utilize the scope in your query:
{
users(random: true)
{
id,
email,
username
}
}
This is fine for small datasets but keep in mind that for larger datasets this could be a possible performance bottleneck.

Laravel Pivot Table, Get Room belonging to users

The structure of my pivot table is
room_id - user_id
I have 2 users that exist in the same room.
How can I get the rooms they both have in common?
It would be nice to create a static class to have something like this.
Room::commonToUsers([1, 5]);
Potentially I could check more users so the logic must not restrict to a certain number of users.
Room::commonToUsers([1, 5, 6, 33, ...]);
I created a Laravel project and make users, 'rooms', 'room_users' tables and their models
and defined a static function in RoomUser Model as below :
public static function commonToUsers($ids)
{
$sql = 'SELECT room_id FROM room_users WHERE user_id IN (' . implode(',', $ids) . ') GROUP BY room_id HAVING COUNT(*) = ' . count($ids);
$roomsIds = DB::select($sql);
$roomsIds = array_map(function ($item){
return $item->room_id;
}, $roomsIds);
return Room::whereIn('id', $roomsIds)->get();
}
in this method, I use self join that the table is joined with itself, A and B are different table aliases for the same table, then I applied the where condition between these two tables (A and B) and work for me.
I hope be useful.
I don't know the names of your relations, but I guess you can do like this :
$usersIds = [1, 5];
$rooms = Room::whereHas('users', function($query) use ($usersIds) {
foreach ($usersIds as $userId) {
$query->where('users.id', $userId);
}
})->get();
It should work. whereHas allows you to query your relation. If you need to have a static method, you can add a method in your model.
There might be a more efficient way but laravel collection does have an intersect method. You could create a static function that retrieves and loop through each object and only retain all intersecting rooms. something like this
public static function commonToUsers($userArr){
$users = User::whereIn('id',$userArr)->get();
$rooms = null;
foreach($users as $user){
if($rooms === null){
$rooms = $user->rooms;
}else{
$rooms = $rooms->intersect($user->rooms);
}
}
return $rooms;
}
This code is untested but it should work.
Room has many users, user has many rooms, so you can find the room which have those two users.
If your pivot table's name is room_users, then you can easily get the common room like this:
public static function commonToUsers($user_ids) {
$room = new Room();
foreach($user_ids as $user_id) {
$room->whereHas('users', function($query) use ($user_id) {
$query->where('room_users.user_id', $user_id);
});
}
return $room->get();
}
This code will convert to raw sql:
select *
from `rooms`
where exists (
select * from `rooms` inner join `room_users` on `rooms`.`id` = `room_users`.`room_id` where `rooms`.`id` = `room_users`.`room_id` and `room_users`.`user_id` = 1
)
and exists
(
select * from `rooms` inner join `room_users` on `rooms`.`id` = `room_users`.`room_id` where `rooms`.`id` = `room_users`.`room_id` and `room_users`.`user_id` = 5
)

Join with OneToMany relation

I am trying to query an entity with a OneToMany relation but it doesn't work as the subquery DQL is not well converted.
I expect this code :
$subquery = $manager->createQueryBuilder();
$subquery
->select('s.occupant')
->from('MyBundle:Stay', 's')
->where('s.dateDeparture IS NULL')
;
$qb
->where($qb->expr()->notIn('o.id', ':subQuery'))
->setParameter('subQuery', $subquery->getDQL())
;
to produce :
WHERE o0_.id NOT IN (
SELECT s.occupant_id FROM Stay s WHERE s.date_departure IS NULL
)
But instead, I have this :
WHERE o0_.id NOT IN (
'SELECT s.occupant FROM MyBundle:Stay s WHERE s.dateDeparture IS NULL'
)
Here are the problems :
- The subquery is encapsulated between commas
- The SQL fields are not translated from their names in the entity (occupant, dateDeparture) to their MySQL equivalent (occupant_id, date_departure)
- The entity name is used (MyBundle:Stay) and is not converted to its SQL equivalent (Stay)
My other queries work perfectly, as well as the main query encapsulating this one.
I also tried to use the OneToMany relation to do this, as there is an Occupant.stays relation, but I couldn't make it work either.
Here is my Occupant class :
class Occupant
{
...
/**
* #ORM\OneToMany(targetEntity="EmmausBundle\Entity\Stay", mappedBy="occupant", cascade={"persist"})
* #ORM\OrderBy({"dateArrival" = "DESC"})
*/
private $stays;
...
}
And my Stay class :
class Stay
{
...
/**
* #ORM\ManyToOne(targetEntity="Occupant", inversedBy="stays")
* #ORM\JoinColumn(name="occupant_id", referencedColumnName="id")
*/
private $occupant;
/**
* #var \DateTime
*
* #ORM\Column(name="date_departure", type="datetime", nullable=true)
*/
private $dateDeparture;
...
}
Thanks for you help !
Thanks to this answer I found the solution :
$qb
->where(
$qb->expr()->notIn(
'o.id',
$manager->createQueryBuilder()
->select('IDENTITY (s.occupant)')
->from('EmmausBundle:Stay', 's')
->where('s.dateDeparture IS NULL')
->getDQL()
)
)
;

codeigniter active record get last 10 records and sort ASC

I would like to know if this SQL statement is done in Codeigniter active record.
SELECT * FROM (
SELECT *
FROM chat
WHERE (userID = $session AND toID = $friendID)
OR (userID = $friendID AND toID = $session)
ORDER BY id DESC
LIMIT 10
) AS `table` ORDER by id ASC
You have to use the _compile_select() and _reset_select() methods of the active record class.
$this->db->select('*');
$this->db->from('chat');
$this->db->where("(userID='{$session}' AND toID='{$friendID}')");
$this->db->or_where("(userID='{$friendID}' AND toID='{$session}')");
$this->db->order_by('id', 'DESC');
$this->db->limit('10');
$subQuery = $this->db->_compile_select();
$this->db->_reset_select();
$this->db->select('*');
$this->db->from("{$subQuery} AS table");
$this->db->order_by('id', 'ASC');
$query = $this->db->get();
Unfortunately, in CI 2.0+, _compile_select() and _reset_select() are protected methods. Bummer. You'll have to follow this tutorial on extending the DB driver where you can write methods like the following:
function get_compiled_select()
{
return $this->db->_compile_select();
}
function do_reset_select()
{
$this->db->_reset_select();
}
I would like to take the time to point out that this type of action would be better served by joins. You should consider changing your db structure so that joins are possible and efficient.
This will work, though is not the active record variant.
$this->db->query( "SELECT * FROM (
SELECT *
FROM chat
WHERE (userID = ? AND toID = ?)
OR (userID = ? AND toID = ?)
ORDER BY id DESC
LIMIT 10
) AS `table` ORDER by id ASC", array( $session, $friendID, $friendID, $session) );"
You can use query as:
$this->db->select('SELECT *
FROM chat
WHERE (userID = $session AND toID = $friendID)
OR (userID = $friendID AND toID = $session)
ORDER BY id DESC
LIMIT 10') AS `table` ORDER by id ASC', FALSE);

Resources