Sample excluding values from the table - php

I have objects connected mano through the intermediate table.
class City{ /**
* #return \yii\db\ActiveQuery
*/
public function getReviews()
{
return $this->hasMany(Reviews::className(), ['id' => 'review_id'])
->viaTable('city_review', ['city_id' => 'id']);
}
}
And get review for city:
$reviews = $city2->getReviews()->all();
Question:
How select reviews from reviews table, which are not in the table city_review?

I'm not familiar with yii activerecord syntax
But the query you need is below.
SELECT review.* FROM review
LEFT JOIN city_review cr ON cr.city_id = review.id
WHERE cr.id IS NULL
This code should result similar to what you need but you need to test.
$reviews = Reviews::find()
->select('review.*')
->leftJoin('city_review', '`city_review`.`city_id` = `review`.`id`')
->where(['city_review.id' => NULL])
->with('city_reviews')
->all();

Related

How to fetch (join) two records from database using doctrine/symfony4

I am learning about Symfony and Doctrine and created a simple site but I am stuck at this step.
I have two tables: users and languages
Users Contains: id, username ...
Languages Contains: user_id, language...
Here is a image of the two
Now I am trying to fetch by language, like: get user who speaks both english and french and the result would return user id 2
In plain PHP i can do inner join with PDO, but I am trying to follow the doctrine syntax and this does not return the correct result
public function getMatchingLanguages ($a, $b) {
return $this->createQueryBuilder('u')
->andWhere('u.language = :val1 AND u.language = :val2')
->setParameter('val1', $a)
->setParameter('val2', $b)
->getQuery()
->execute();
}
I call this method in my controllers, and the query is pretty basic since I can not find a documentation how to do the joins as per my example
In your User model add next code:
/**
* #ORM\OneToMany(targetEntity="Language", mappedBy="user", fetch="EXTRA_LAZY")
*/
public $languages;
In your Language model add next code:
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="languages")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
public $user;
By this way you define simple One-To-Many relation between User and Language, but it is not enough for getting your user that support both languages. You need to make 2 joins of user table and language table.
That's how it looks like (if you use controller):
$user = $this->get('doctrine')
->getEntityManager()
->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->join('u.languages', 'l_eng', 'WITH', 'l_eng.language = :engCode')
->join('u.languages', 'l_fr', 'WITH', 'l_fr.language = :frCode')
->setParameters([
'engCode' => 'english',
'frCode' => 'french'
])
->getQuery()->execute();
Or, if you use UserRepository class (most preferable):
public function findAllByLangs()
{
return $this->createQueryBuilder('u')
->join('u.languages', 'l_eng', 'WITH', 'l_eng.lang = :engCode')
->join('u.languages', 'l_fr', 'WITH', 'l_fr.lang = :frCode')
->setParameters([
'engCode' => 'english',
'frCode' => 'french'
])
->getQuery()->execute();
}
So main trick is join language table with condition of english to filter users, that support english language AND join language table again but with french in "ON" section to filter users who support french language as well.
By analyzing your DB tables, I assume that your Entities are like this
// User.php
class User implements UserInterface
{
/**
* #ORM\Column(type="guid")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $username;
}
// Language.php
class Language
{
/**
* #ORM\Column(type="guid")
*/
private $userId;
/**
* #ORM\Column(type="string", length=30)
*/
private $language;
}
If you have the same setup (as above Entities), then you can write your query like this in UserRepository.php
public function getUsersForMatchingLanguages ($langOne, $langTwo) {
return $this->createQueryBuilder('user')
->select('user.id, user.username, language.language')
->innerJoin(Language::class, 'language', 'WITH', 'language.user_id = user.id')
->where('language.language = :langOne AND language.language = :langTwo')
->setParameter('langOne ', $langOne )
->setParameter('langTwo', $langTwo)
->getQuery()
->getResult();
}
This will return you array of results.
Maybe I am not understand question correctly, please correct me if I am wrong, but if you need user(s) that speaks BOTH languages you have an error in SQL logic not in doctrine. You should do smth like:
SELECT * FROM user u JOIN language l ON u.id = l.user_id AND l.language = 'english' JOIN language l2 ON u.id = l2.user_id AND l2.language = 'french' GROUP BY u.id;
If query correct for you I can write DQL interpretation for it.
You can:
Inner join with the languages you want
use aggregate functions to count the joined results(joined languages)
group by the user entity
filter the results for count(lang) = 2
Code:
use Doctrine\ORM\Query\Expr\Join;
public function getMatchingLanguages ($a, $b) {
return $this->createQueryBuilder('u')
->addSelect('COUNT(a) as HIDDEN total')
->innerJoin('u.languages', 'a', Join::WITH, 'a.language = :val1 OR a.language = :val2')
->groupBy('u');
->having('total = :total') //or ->having('COUNT(a) = :total')
->setParameter('total', 2)
->setParameter('val1', $a)
->setParameter('val2', $b)
->getQuery()
->execute();
}
$this->getMatchingLanguages('english', 'french');
This works by inner joining user only with rows with english or french and then using mysql having to "check" if we got 2 rows for each user.
If you want also the Languages entities hydrated to your result, you cannot add it to the querybuilder result since you group by:
->addSelect('a')
you will have to do another query.

I'm trying to load only the last 3 comments on every post

i want get all posts with last three comment on each post. my relation is
public function comments()
{
return $this->hasMany('App\Commentpostfeed','post_id')->take(3);
}
This would return only 3 comments total whenever I called it instead of 3 comments per post.
i use this way :
1 :
Postfeed::with(['comment' => function($query) {
$query->orderBy('created_at', 'desc')->take(3); }]);
2 :
$postings = Postfeed::with('comments')->get();
but getting same result. please help me out for this problem.
Can you try like that ?;
Postfeed::with('comment')->orderBy('id','desc')->take(3);
Using plain mysql (If using Mysql) query you can get 3 recent comments per post using following query which rejoins comment table by matching created_at
SELECT p.*,c.*
FROM posts p
JOIN comments c ON p.`id` = c.`post_id`
LEFT JOIN comments c1 ON c.`post_id` = c1.`post_id` AND c.`created_at` <= c1.`created_at`
GROUP BY p.`id`,c.`id`
HAVING COUNT(*) <=3
ORDER BY p.`id`,c.`created_at` DESC
Sample Demo
Using laravel's query builder you can write similar to
$posts = DB::table('posts as p')
->select('p.*,c.*')
->join('comments c', 'p.id', '=', 'c.post_id')
->leftJoin('comments as c1', function ($join) {
$join->on('c.post_id', '=', 'c1.post_id')->where('c.created_at', '<=', 'c1.created_at');
})
->groupBy('p.id')
->groupBy('c.id')
->having('COUNT(*)', '<=', 3)
->orderBy('p.id', 'asc')
->orderBy('c.created_at', 'desc')
->get();
You can create a scope in the BaseModel like this :
<?php
class BaseModel extends \Eloquent {
/**
* query scope nPerGroup
*
* #return void
*/
public function scopeNPerGroup($query, $group, $n = 10)
{
// queried table
$table = ($this->getTable());
// initialize MySQL variables inline
$query->from( DB::raw("(SELECT #rank:=0, #group:=0) as vars, {$table}") );
// if no columns already selected, let's select *
if ( ! $query->getQuery()->columns)
{
$query->select("{$table}.*");
}
// make sure column aliases are unique
$groupAlias = 'group_'.md5(time());
$rankAlias = 'rank_'.md5(time());
// apply mysql variables
$query->addSelect(DB::raw(
"#rank := IF(#group = {$group}, #rank+1, 1) as {$rankAlias}, #group := {$group} as {$groupAlias}"
));
// make sure first order clause is the group order
$query->getQuery()->orders = (array) $query->getQuery()->orders;
array_unshift($query->getQuery()->orders, ['column' => $group, 'direction' => 'asc']);
// prepare subquery
$subQuery = $query->toSql();
// prepare new main base Query\Builder
$newBase = $this->newQuery()
->from(DB::raw("({$subQuery}) as {$table}"))
->mergeBindings($query->getQuery())
->where($rankAlias, '<=', $n)
->getQuery();
// replace underlying builder to get rid of previous clauses
$query->setQuery($newBase);
}
}
And in the Postfeed Model :
<?php
class Postfeed extends BaseModel {
/**
* Get latest 3 comments from hasMany relation.
*
* #return Illuminate\Database\Eloquent\Relations\HasMany
*/
public function latestComments()
{
return $this->comments()->latest()->nPerGroup('post_id', 3);
}
/**
* Postfeed has many Commentpostfeeds
*
* #return Illuminate\Database\Eloquent\Relations\HasMany
*/
public function comments()
{
return $this->hasMany('App\Commentpostfeed','post_id');
}
}
And to get the posts with the latest comments :
$posts = Postfeed::with('latestComments')->get();
Ps :
Source
For many to many relationships
You can do it like this,
Postfeed::with('comments',function($query){
$query->orderBy('created_at', 'desc')->take(3);
})
->get();

Model Primary Key not being Inserted into Query using WhereHas

A Little Background...
My User model has the following two relations:
/**
* Get the connections added by the user.
*
* #param boolean $accepted
* #return BelongsToMany
*/
public function addedConnections($accepted = true)
{
return $this->belongsToMany('App\Models\User\User', 'user_connections', 'user_id', 'connection_id')
->wherePivot('connection_type', '=', 'user')
->wherePivot('accepted', '=', $accepted)
->withTimestamps();
}
/**
* Get the connections the user was invited to.
*
* #param boolean $accepted
* #return BelongsToMany
*/
public function invitedConnections($accepted = true)
{
return $this->belongsToMany('App\Models\User\User', 'user_connections', 'connection_id', 'user_id')
->wherePivot('connection_type', '=', 'user')
->wherePivot('accepted', '=', $accepted)
->withTimestamps();
}
I have attempted to write a method that merges the above two methods to return connections. However, instead of returning this as a Laravel Collection, I want to return it as a query Builder so I can add to the query...
This is what I have implemented:
/**
* Get the connections the user was invited to.
*
* #return Builder
*/
public function connections()
{
return $this
->where(function ($query) {
$query
->whereHas('addedConnections')
->orWhereHas('invitedConnections');
})
->whereDoesntHave('blocked');
}
The Issue...
The problem with this function is that it is not building the query correctly (from what I can see). Instead of returning the connections for the current user, it is returning all connections. This is because of the following two lines in the query:
WHERE user_connections.user_id = users.id
WHERE user_connections.connection_id = users.id
users.id should be the id of the current user, not a reference to the users table.
My Question(s)
Why is this happening? Why is the model id (1, 2, 3 etc) not being inserted into the query instead of the reference to the table?
Regardless of what is causing the issue, I am keen to know if there are any better methods of achieving the above?
Update 1
I think I may have fixed this with the following:
/**
* Get the connections the user was invited to.
*
* #return Builder
*/
public function connections()
{
return $this
->where(function ($query) {
$query
->whereHas('addedConnections', function ($query) {
$query->where('id', '=', $this->id);
})
->orWhereHas('invitedConnections', function ($query) {
$query->where('id', '=', $this->id);
});
})
->whereDoesntHave('blocked');
}
Is this the right solution?
Additional Information
If needed, this is the full SQL query:
SELECT
*
FROM
`users`
WHERE
(
EXISTS
(
SELECT
*
FROM
`users` AS `laravel_reserved_3`
INNER JOIN
`user_connections`
ON `laravel_reserved_3`.`id` = `user_connections`.`connection_id`
WHERE
`user_connections`.`user_id` = `users`.`id` -- THIS IS THE ISSUE
AND `user_connections`.`connection_type` = 'user'
AND `user_connections`.`accepted` = TRUE
AND `laravel_reserved_3`.`deleted_at` IS NULL
)
OR EXISTS
(
SELECT
*
FROM
`users` AS `laravel_reserved_4`
INNER JOIN
`user_connections`
ON `laravel_reserved_4`.`id` = `user_connections`.`user_id`
WHERE
`user_connections`.`connection_id` = `users`.`id` -- THIS IS THE ISSUE
AND `user_connections`.`connection_type` = 'user'
AND `user_connections`.`accepted` = TRUE
AND `laravel_reserved_4`.`deleted_at` IS NULL
)
)
AND NOT EXISTS
(
SELECT
*
FROM
`users` AS `laravel_reserved_5`
INNER JOIN
`user_blocks`
ON `laravel_reserved_5`.`id` = `user_blocks`.`blocked_id`
WHERE
`user_blocks`.`user_id` = `users`.`id`
AND `laravel_reserved_5`.`deleted_at` IS NULL
)
AND `users`.`deleted_at` IS NULL

Symfony Doctrine Query For Many to Many Releationships

I have Many to Many relationship between Institutes and Courses. I want to build query that returns only the institutes list whom some courses has been assigned. I have wrote queries in this situation for one to many. but for not many to many. here is the relationships,
class Institutes {
/**
* #ORM\ManyToMany(targetEntity="Courses", inversedBy="institutes")
* #ORM\JoinTable(name="institute_courses",
* joinColumns={#ORM\JoinColumn(name="institute_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="course_id", referencedColumnName="id")}
* )
*/
protected $courses;
}
class Courses {
/**
* #ORM\ManyToMany(targetEntity="Institutes", mappedBy="courses")
*/
protected $institutes;
}
here is the query that i have written, but didn't work properly.
$repository->createQueryBuilder('s')
->leftJoin('CoursesBundle:Courses','c', 'ON c.institutes = s.courses')
->where('s.active = :active')
->andWhere('s.verified = :active')
->setParameter('active', true)
->orderBy('s.name', 'ASC');
This should do the trick:
$repository->createQueryBuilder('i')
->innerJoin('i.courses','c')
->where('i.active = TRUE')
->andWhere('i.verified = TRUE')
->orderBy('i.name', 'ASC');
You can use a JOIN as you would with other kinds of associations. The following query will find all courses which have been assigned at least to one institute:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i
You can filter the results further by adding a join condition:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i WITH i.something = :someParam

Yii2 Query Builder with ActiveRecord

I have three models Employer , Job, and Transaction
Employer can have many Job
Job can have many Transaction
I am trying to use ActiveRecord to get all Employer that do not have a Transaction record.
Within my Employer model, I have defined relations to find all jobs and transactions linked to this employer:
/**
* #return \yii\db\ActiveQuery
*/
public function getJobs() {
return $this->hasMany(Job::className(), ['employer_id' => 'employer_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getTransactions() {
return $this->hasMany(Transaction::className(), ['job_id' => 'job_id'])->via("jobs");
}
Any ideas on the best way to do this?
SQL:
SELECT employer.*
FROM employer
WHERE employer.employer_id NOT IN
(
SELECT employer.employer_id
FROM employer
INNER JOIN job ON employer.employer_id = job.employer_id
INNER JOIN transaction ON job.job_id = transaction.job_id
)
With Yii2:
function getThoseWithoutTransaction() {
return Employer::find()->where(['not in', 'employer_id',
(new Query())
->select('employer.employer_id')
->from('employer')
->innerJoin('job', 'employer.employer_id = job.employer_id')
->innerJoin('transaction', 'job.job_id = transaction.job_id')
)]
);
}
But I didn't test it. Hope it is correct, though. And there might be better solutions.
Try with
$query = MyModel::find()->where(['not in','attribute',$array]);

Categories