how join laravel tables to get non intersected data - php

in my Laravel app I have 3 tables : users, documents and type_documents, the user have multiple documents and document have one type_document
| Documents |
| -------- |
| id |
| file |
| type_document_id|
| user_id |
| type_documents |
| -------- |
| id |
| name |
| users |
| -------- |
| id |
| name |
| type_document_d|
I want select the types that are not used in documents table for the current user with eloquent
I try with this, but it give me the used type_documents :
$document_types = TypeDocument::leftJoin('docments' , function ($join) {
$join->on('type_documents.id', '=', 'douments.type_document_id')
->where('documents.user_id', auth()->id());
})
->applyFilters($request->all())
->latest()
->paginateData($limit);
I use Laravel version 8

Using Eloquent relation defined between TypeDocument::class and User::class like this:
TypeDocument.php
public function users()
{
return $this->belongsToMany(User::class, 'documents', 'type_document_id', 'user_id')->withPivot('file');
}
The you can easily get the type document not linked to a specific user like this
TypeDocument::whereDoesntHave('users', function($userQuery) {
$userQuery->where('users.id', '=', auth()->id());
})
->applyFilters($request->all())
->latest()
->paginateData($limit);
--------------EDIT--------------
IF you want to use two step relation, you can define it like this:
TypeDocument.php
public function documents()
{
return $this->hasMany(Document::class);
}
Document.php
public function user()
{
return $this->belongsTo(User::class);
}
And use the relations to query it like this:
TypeDocument::whereDoesntHave('documents.user', function($userQuery) {
$userQuery->where('users.id', '=', auth()->id());
})
->applyFilters($request->all())
->latest()
->paginateData($limit);

Have you tried whereNotExists() or whereNotIn() instead of leftJoin()?
eg:
<?php
// whereNotIn
$types = TypeDocument::whereNotIn('id', function($query) {
$query->select('type_document_id')
->from('documents')
->where('user_id', auth()->id);
})->get();
<?php
// whereNotExists
$types = TypeDocument::whereNotExists(function($query) {
$query->select(DB::raw(1))
->from('documents')
->whereRaw('type_documents.id = documents.type_document_id')
->where('user_id', '=', Auth::user()->id);
})->get();
<?php
// leftJoin
$types = TypeDocument::leftJoin('documents', function($join) {
$join->on('type_documents.id', '=', 'documents.type_document_id')
->where('user_id', '=', Auth::user()->id);
})->whereNull('documents.type_document_id')->get();
try any of these!

Check this solution
TypeDocument::whereNotIn('id', function($query){
$query->select('type_document_id')
->from(with(new Documents)->getTable())
->where('type_document_id', $query->id)
->where('user_id', auth()->id());
})->get();

Related

Laravel how can get this relation

Consider a digital store
Definition:
Buyer->all buyer
Products->all products
Downloads->store those products that buyer bought
a Buyer can buy a Product and it store in Downloads, now I want to show to buyer list of downloads.
ProductController.php
public function buyerproducts()
{
$user = auth()->guard('buyer')->user();
if ($user) {
$files = Product::where('deleted', 0)
->where('deleted', 0)
->with('files', 'province:id,name', 'city:id,name')
->get();
// and here I got a loop.. to add some extra data
return response()->json(['data' => $files], 200);
} else {
return response()->json(['success' => 'no content'], 204);
}
}
Product.php
function files()
{
return $this->hasManyThrough('App\Download', 'App\Buyer', 'id', 'product_id', 'buyer_id', 'id');
}
But it return all data, not what buyer bought. any idea?
Note, that I have to get this data in product controller not download.
Products:
-----------------------
|id | name | seller_id |
-----------------------
| 1 | bmw | 1 |
-----------------------
| 2 | benz | 1 |
-----------------------
| 2 | reno | 2 |
-----------------------
Downloads:
------------------------------
|id | product_id | buyer_id |
------------------------------
| 1 | 1 | 1 |
------------------------------
| 2 | 1 | 2 |
------------------------------
| 3 | 2 | 22 |
------------------------------
Buyer:
------------------------------
|id | name | email |
------------------------------
| 1 | john | # |
------------------------------
| 2 | mike | # |
------------------------------
| 3 | dave | # |
------------------------------
The HasManyThrough relationship is expecting to go through 2 hasMany relationships, however, looking at your table definition, the first relationship is hasMany but the second would be a belongsTo. Since both keys relate to a single row in a different table (both belongsTo) we can instead create a belongsToMany relationship instead and treat the downloads table as a pivot.
There are a couple of different ways you can go about this.
Firstly, I would suggest setting up the relationships between Buyer and Product (is you haven't already done so):
Product
public function buyers()
{
return $this->belongsToMany(Buyer::class, 'downloads')->withTimestamps();
}
Buyer
public function products()
{
return $this->belongsToMany(Product::class, 'downloads')->withTimestamps();
}
Then in your controller method you could either keep the same query and use whereHas():
public function buyerproducts()
{
$user = auth()->guard('buyer')->user();
if ($user) {
$files = Product::where('deleted', 0)
->whereHas('buyers', function ($query) use ($user) {
$query->where('buyers.id', $user->id);
})
->with('files', 'province:id,name', 'city:id,name')
->get();
// and here I got a loop.. to add some extra data
return response()->json(['data' => $files], 200);
}
return response()->json(['success' => 'no content'], 204);
}
or alternatively you could not just query the products straight from the $user (buyer):
public function buyerproducts()
{
$user = auth()->guard('buyer')->user();
if ($user) {
$files = $user->products()->where('deleted', 0)
->whereHas('buyers', function ($query) use ($user) {
$query->where('buyers.id', $user->id);
})
->with('files', 'province:id,name', 'city:id,name')
->get();
// and here I got a loop.. to add some extra data
return response()->json(['data' => $files], 200);
}
return response()->json(['success' => 'no content'], 204);
}
Why don't you go with getting the products as Buyer object relationship.
So you define products in Buyer.php:
function products()
{
return $this->hasManyThrough('App\Download', 'App\Product', 'id', 'buyer_id', 'product_id', 'id');
}
And in the controller you can call it like:
$buyer->with([
'products.province:id,name',
'products.city:id,name'
])
->whereHas('products', function($query){
$query->where('deleted', 0)
})
->get()
then you can go with return response()->json(['data' => $buyer->products], 200);

What should i do to get all the users with exactly two roles? as user 1 has 2 roles in database

User.php
public function roles(){ return $this->belongsToMany(Role::class); }
Role.php
public function users(){ return $this->belongsToMany(User::class); }
UserController.php
$user = User::whereHas('roles', function ($q) use ($request) { $q->where('role_id', $request->roles); })
================Request========================
{ "roles":[1,2] }
===============Response=========================
[]
===============Table===============================
role_user
+===+===+
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
What should i do to get all the users with exactly two roles?
as user 1 has 2 roles in database
You can use whereIn to only get users with the provided role_ids:
Role 1 or 2:
$user = User::whereHas('roles', function ($q) use ($request) {
$q->whereIn('role_id', $request->roles); // $request->roles needs to be an array
});
Role 1 and 2:
$user = User::whereHas('roles', function ($q) use ($request) {
$q->whereIn('role_id', $request->roles)
->groupBy('user_id')
->havingRaw('COUNT(DISTINCT role_id) = '.count($request->roles));
});
From the docs:
The whereIn method verifies that a given column's value is contained
within the given array
$users = DB::table('users')
->whereIn('id', [1, 2, 3])
->get();
You can count the amount of roles in your query and then use the result as an attribute
$users = User::withCount('roles')->get()->where('roles_count',2);
You can try this
Public function getRole()
$users=User::with('roles') - >first () ;
foreach ($users->roles as $user)
{
$list[] =$user->name;
}
dd($list) ;

Laravel join with LIKE CONCAT

Laravel 5.1.11
I have a simple search in my shop:
public function search($request $request)
{
return Catalog::where('title', 'like', '%'.$request->searchQuery.'%')
->orderBy('id', 'desc')
->paginate(50);
}
This is worked!
But people are looking for goods so: Pump BOSCH. And search result is null.
Because I changed my select:
public function search($request $request)
{
$searchQ = preg_split('/\s+/', $request->searchQuery, -1, PREG_SPLIT_NO_EMPTY);
return Catalog::where(function ($q) use ($searchQ) {
foreach ($searchQ as $value) {
$q->orWhere('title', 'like', "%{$value}%");
}
})
->orderBy('id', 'desc')
->paginate(50);
}
And this is worked), and show all Pumps!
But i want show only one Pump bosch.
My DB table manufactories:
| id | title |
| :--: | :----- |
| 1 | AlKO |
| 2 | BOSCH |
AND catalogs, where m_id is id from table manufactory:
| id | m_id | title | url |
| :--: | :---: | :---- | :--------- |
| 1 | 1 | pump | pump-alko |
| 2 | 2 | pump | pump-bosch |
How do I change the MySQL search query (adding LEFT JOIN and CONCAT) to find Pump bosch?
To make it look like this: CONCAT('catalogs.title', ' ', 'manufactories.title') LIKE '%'.$request->searchQuery.'%'
You can do simply like this,
$data = DB::table('manufactories')
->join('catalogs','manufactories.id','=','catalogs.m_id')
->select('manufactories.title','catalogs.title')
->where(DB::raw('CONCAT(manufactories.title," ",catalogs.title)'), 'like',"%{$value}%")
->get();
I hope it helps,
You can use the Query Builder to do joins e.g.
DB::table('catalogs')
->select('catalogs.*')
->join('manufactories', 'catalogs.m_id', '=', 'manufactories.id', 'left')
->where(function($q) use ($searchQ) {
foreach ($searchQ as $value) {
$q->orWhere('manufactories.title', 'like', "%{$value}%");
$q->orWhere('catalogs.title', 'like', "%{$value}%");
}
})
->get();

problems with validating input and query optimization

im creating a management system where teachers can manage students final projects and the formers can see what other students created
im a laravel newbie and im having problems optimizing queries and validating urls
here are my table schemas:
Cursos
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| curso | varchar(255) | NO | | NULL | |
+-------+------------------+------+-----+---------+----------------+
Trienios
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| data_trienio | varchar(255) | NO | | NULL | |
| curso_id | int(11) | NO | | NULL | |
| oe_id | int(11) | NO | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
Alunos
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| id_cartao | int(10) unsigned | NO | UNI | NULL | |
| nome | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| trienio_id | int(11) | NO | | NULL | |
+------------+------------------+------+-----+---------+----------------+
PAP
+-----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| nome | varchar(255) | NO | | NULL | |
| descricao | text | NO | | NULL | |
| nota | int(11) | NO | | NULL | |
| aluno_id | int(11) | NO | | NULL | |
+-----------+------------------+------+-----+---------+----------------+
so far i've managed to set up dynamic urls based on the records defined on the cursos and trienios table, like this: http://localhost:8000/TGEI/2014-2017
(TGEI being a record in the cursos table that fetches the associated trienio records and 2014-2017 being a record in the trienios table that's related to a curso record in a 1-to-many relationship and fetches the related pap records)
this is all working nice and smooth, but i'm having trouble with optimizing hugely inefficient queries that will become a very significant problem when the database grows
here are my relationships:
Curso.php
public function trienio()
{
return $this->hasMany('App\Trienio');
}
Trienio.php
public function curso()
{
return $this->belongsTo('App\Curso');
}
public function oe()
{
return $this->belongsTo('App\OE');
}
public function aluno()
{
return $this->hasMany('App\Aluno');
}
Aluno.php
public function trienio()
{
return $this->belongsTo('App\Trienio');
}
public function pap()
{
return $this->hasOne('App\PAP');
}
PAP.php
protected $table = 'pap';
public function aluno()
{
return $this->belongsTo('App\Aluno');
}
and these are the controllers that are in charge of serving the user-accessible pages:
CursoController.php
public function index(Curso $curso)
{
$cursos = $curso->all();
return view('curso')->withCursos($cursos);
}
TrienioController.php
public function index(Trienio $trienio, $curso)
{
$trienios = $trienio->whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
})->get();
return view('trienio')->withTrienios($trienios);
}
PapController.php
public function index(Pap $pap, $curso, $trienio)
{
$pap = $pap->whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
$query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
});
})->toSql();
dd($pap);
return view('pap')->withPap($pap);
}
public function show(Pap $pap, $curso, $trienio, $id)
{
$pap = $pap->find($id);
dd($pap);
return view('show')->withPap($pap);
}
as you can see, in the case of the index method of the PAP controller, the query that requests the data is a huge mess that is the epitome of the n+1 problem:
"select * from `pap` where exists (select * from `alunos` where `pap`.`aluno_id` = `alunos`.`id` and exists (select * from `trienios` where `alunos`.`trienio_id` = `trienios`.`id` and `data_trienio` = ? and exists (select * from `cursos` where `trienios`.`curso_id` = `cursos`.`id` and `curso` = ?)))"
what i intend with this query is to fetch the PAP records that are related to a trienio record, which in turn is related to a curso record, based on the input the user enters in the url (i've shown an example above), the problem is, as i'm a newbie to this stuff in general, i was unable to apply the eager loading concepts to the query i want to run
also i'm having a problem with validating urls in which the user can input the following:
http://localhost:8000/qwfkjnfwq/qjqtikjn/1
and the controller method show will fetch a pap record without regard to the parameters that the user inputed 2 levels above, and this obviously will pose a "security" problem
and what i wanted to do was:
http://localhost:8000/TGEI/2014-2017/1
the controller method show will load the aluno.trienio nested relationship, then fetch the trienio id related to the aluno model in accordance to the 2014-2017 parameter, then fetch the curso id related to the trienio model in accordance to the TGEI parameter
and so, stuff like this
http://localhost:8000/qwfkjnfwq/qjqtikjn/1
would be invalidated instead of going through.
this may be a tricky question but whoever that can help me, i would thank so. i understand that some parts of my question may be unclear(even more so because english isnt my first language) and in that case, i can clarify them all you want.
and for better information, here is my web.php file
Route::get('/', 'CursoController#index');
Route::get('/{curso}', 'TrienioController#index');
Route::get('/{curso}/{trienio}', 'PapController#index');
Route::get('/{curso}/{trienio}/{id}', 'PapController#show');
Okay to expand on my comment.
With Laravel 5.2 came route model binding which enables you to inject the model in the controller method (like this: public function show(Pap $pap)) and Laravel will automatically fetch the Pap model with the id in the url (bascially doing Pap::find($id) and saving the return into $pap variable). This is not always something you want, because often you want to perform more complex queries.
I would recommend you not to use the route model binding in you case and just do the queries on your own. Something like this (see how I've removed the Models from controller functions)
// CursoController.php
public function index()
{
$cursos = Curso::all();
return view('curso')->withCursos($cursos);
}
// TrienioController.php
public function index($curso)
{
$trienios = Trienio::whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
})->get();
return view('trienio')->withTrienios($trienios);
}
// Pap controller
public function index($curso, $trienio)
{
$pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
$query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
});
})->get();
return view('pap')->withPap($pap);
}
public function show($curso, $trienio, $id)
{
$pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
$query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
});
})->findOrFail($id);
return view('show')->withPap($pap);
}
Also note that in the show() method I've pretty much copied the index() query which is the validation.
And regarding the optimization of queries - the queries as you have them are absolutely fine. There's no n+1 problem as is.
You will have the n+1 problem if you will be performing a foreach on one of the index results and calling child's properties. For example if you will do something like this in a pap view:
#foreach($pap as $p)
<div>{{ $p->aluno->id }}</div>
#endforeach
This would make a new query for every $p in $pap to fetch the related aluno results.
To avoid this n+1 problem you have to load the data before using it in a loop. You would eager load the data using the ->with(relationship) method. Something like this:
// Pap controller
public function index($curso, $trienio)
{
$pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
$query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
});
})
->with('aluno.trienio') // You might need some additional checks here, depending on you needs
->get();
return view('pap')->withPap($pap);
}
It's not completely intuitive, but ->whereHas(relationship) will not eager load the relationship. So often you will find yourself writing statement like this:
// Pap controller
public function index($curso, $trienio)
{
$pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
$query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
});
})
->with(['aluno.trienio' => function ($q) use ($curso, $trienio) {
$query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
$query->where('curso', '=', $curso);
}]); // These are the additional checks
->get();
return view('pap')->withPap($pap);
}

Laravel join query using Laravel eloquent

i have two tables 'users' and 'projects'
Table users
-----------------------------------
user_id | name | email | password |
-----------------------------------
1 |name1 |email1 | password1|
-----------------------------------
2 |name2 |email2 | password2|
Table projects
project_id |user_id | name |
--------------------------------------
1 | 1 | project_name1 |
-----------------------------------
2 | 1 | project_name2 |
There is no relation between these two tables but user_id in projects are the id from user table.
Normal Query
SELECT a.user_id,a.name AS username,b.name AS project_name FROM users a LEFT JOIN projects b ON a.user_id=b.user_id;
I want the same query execution in laravel eloquent.
Have searched in google got some idea about "with" function but not able to get the output.
Thanks.
This is the way you should use Eloquent:
create User.php
<?php
class User extends Eloquent
{
public function projects()
{
return $this->hasMany('Project');
}
}
create Project.php
<?php
class Project extends Eloquent
{
}
On controller, write this to get all users, with related projects inserted inside each users:
$users = User::with('projects')->all();
foreach($users as $user)
{
$project_of_this_user = $user->projects;
}
You can simply do as following,
DB::table('users')
->join('projects =', 'users.id', '=', 'posts.user_id')
->select('*')
->get();
For more details you can refer laravel Docs
This should be what you need:
DB::table('users')
->join('projects', 'projects.user_id', '=', 'users.user_id')
->select('users.user_id', 'users.name as username', 'projects.name as project_name')
->get();
I think, this one helps you out
DB::table('users')
->leftJoin('projects', 'users.user_id', '=', 'projects.user_id')
->select('users.user_id', 'users.name as username', 'projects.name as project_name')
->get();

Categories