Laravel ORM + Raw Query Table alias problem - mysql

This is my base query:
$base_query = TableOne::join('table_two as p1', 'p1.order_id', '=', 'table_ones.id')
->join('table_threes as j1', 'p1.id', '=', 'j1.partner_order_id')
->select('table_ones.*')
->groupBy('table_ones.id', 'j1.status');
When someone need to filter some data like partner_id on table_two table, we add some extra column like this,
$base_query->where(function ($query) {
$query->whereNull('p1.cancelled_at');
$query->orWhere('p1.cancelled_at', '=', DB::select(DB::raw("SELECT MAX(p2.cancelled_at) FROM partner_orders p2 WHERE p2.order_id = p1.order_id")));
$query->whereNotExists(function ($query) {
DB::select(DB::raw("SELECT * FROM partner_orders p3 WHERE p3.order_id = p1.order_id AND p3.cancelled_at IS NULL"));
});
});
But after run this query, their is an error
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'p1.order_id'
in 'where clause' (SQL: SELECT MAX(p2.cancelled_at) FROM
partner_orders p2 WHERE p2.order_id = p1.order_id)
i think, their is some issue on that query.
$base_query->where(function ($query) {
$query->whereNull('p1.cancelled_at');
$query->orWhere('p1.cancelled_at', '=', DB::select(DB::raw("SELECT MAX(p2.cancelled_at) FROM partner_orders p2 WHERE p2.order_id = p1.order_id")));
$query->whereNotExists(function ($query) {
DB::select(DB::raw("SELECT * FROM partner_orders p3 WHERE
p3.order_id = p1.order_id AND p3.cancelled_at IS NULL"));
});
});
`

DB::select() directly executes the query.
In the case of orWhere(), only use a raw expression.
$query->orWhere('p1.cancelled_at', '=', DB::raw("(SELECT MAX(p2.cancelled_at) [...])"));
In the case of whereNotExists(), use whereRaw():
$query->whereRaw("NOT EXISTS(SELECT * [...])");
In both cases, you can also use a closure and build the query manually:
$query->orWhere('p1.cancelled_at', '=', function($query) {
$query->from('partner_orders')->select([...])->where([...]);
})
$query->whereNotExists(function($query) {
$query->from('partner_orders as p3')->where([...]);
})

Related

Laravel. How to get a record, that has all user ids in related table?

There is a chat table and intermediate table.
I need to get from the chat table only those chats for which all user identifiers are found.
Now I am getting records if at least one match is found
My query
$chat = $this->select('chats.*')->distinct()
->rightJoin("chat_user", function ($query) {
$query->on("chats.id", "=", "chat_user.chat_id")
->whereIn("chat_user.user_id", [2, 17]);
})
->where('chats.type', '=', 'single')
->get();
Result
But I need a chat width id 4, because only it matches my request
I also tried to do it like this
$chat = $this->select('chats.*')->distinct()
->rightJoin("chat_user", function ($query) use ($members_ids) {
$query->on("chats.id", "=", "chat_user.chat_id")
->where("chat_user.user_id", 2)
->where("chat_user.user_id", 17);
})
->where('chats.type', '=', 'single')->get();
But result is empty
In plain SQL, this could be achieved with the following query:
SELECT chats.*
FROM chats
WHERE chats.type = 'single'
AND EXISTS (SELECT 1 FROM chat_user WHERE user_id = 2 AND chat_id = chats.id)
AND EXISTS (SELECT 1 FROM chat_user WHERE user_id = 7 AND chat_id = chats.id)
Translated into a Laravel query, we get the following:
$userIds = [2, 7];
$chats = DB::table('chats')
->where('chats.type', 'single')
->where(function (Builder $query) use ($userIds) {
foreach ($userIds as $userId) {
$query->whereExists(fn (Builder $query) => $query
->select(DB::raw(1))
->from('chat_user')
->where('chat_user.user_id', $userId)
->whereColumn('chat_user.chat_id', 'chats.id'));
}
})
->select('chats.*')
->get();
You should do something like this instead:
$chat = $this->select('chats.*')->distinct()
->rightJoin('chat_user', function ($query) {
$query->on('chats.id', '=', 'chat_user.chat_id')
->where('chat_user.user_id', 2)
->where('chat_user.user_id', 17);
})
->where('chats.type', '=', 'single')
->get();
Specifying multiple where works as an AND condition. If you use whereIn() will work as an OR conditions for the supplied values.

Laravel subquery in from clause

I need to use subquery in from clause but i can not find such thing in Laravel docs
Laravel version 5.4
$sub = Chat::join("chats as _chats", function ($query) {
$query->on('chats.room_id', "=", "_chats.room_id")
->on('chats.user_type', "<>", "_chats.user_type")
->on('chats.created_at', "=", DB::raw("(SELECT MIN(created_at) FROM chats WHERE created_at > '_chats.created_at')"));
})
->selectRaw('TIMESTAMPDIFF(MINUTE, _chats.created_at, chats.created_at) as res')
->where('chats.user_type', 'pharmacy_consultant')
->where('chats.user_id', 26)
->toSql();
dd(
DB::connection('mysql2')
->table(DB::raw("({$sub}) as sub"))
->select('res')
->get()
);
(2/2) QueryException SQLSTATE[HY000]: General error: 2031
(SQL: select `res` from (select TIMESTAMPDIFF(MINUTE, _chats.created_at, chats.created_at) as res
from `chats` inner join `chats` as `_chats` on `chats`.`room_id` = `_chats`.`room_id` and `chats`.`user_type` <> `_chats`.`user_type` and `chats`.`created_at` =
(SELECT MIN(created_at) FROM chats WHERE created_at > _chats.created_at) where `chats`.`user_type` = ? and `chats`.`user_id` = ?) as sub)
Try passing the builder instance instead of the raw query.
// $sub = Query Builder instance
$sub = Chat::join("chats as _chats", function ($query) {
$query->on('chats.room_id', "=", "_chats.room_id")
->on('chats.user_type', "<>", "_chats.user_type")
->on('chats.created_at', "=", DB::raw("(SELECT MIN(created_at) FROM chats WHERE created_at > '_chats.created_at')"));
})
->selectRaw('TIMESTAMPDIFF(MINUTE, _chats.created_at, chats.created_at) as res')
->where('chats.user_type', 'pharmacy_consultant')
->where('chats.user_id', 26);
// ->toSql();
DB::connection('mysql2')
->table($sub, "sub")
->select('res')
->get()
Since you're not doing anything else than a select in your final query, why not just do that in the first query instead?
$results = Chat::join("chats as _chats", function ($query) {
$query->on('chats.room_id', "=", "_chats.room_id")
->on('chats.user_type', "<>", "_chats.user_type")
->on('chats.created_at', "=", DB::raw("(SELECT MIN(created_at) FROM chats WHERE created_at > '_chats.created_at')"));
})
->selectRaw('TIMESTAMPDIFF(MINUTE, _chats.created_at, chats.created_at) as res')
->where('chats.user_type', 'pharmacy_consultant')
->where('chats.user_id', 26)
->select('res')
->get();
Make it fit to your needs:
...
->addSelect([res' => ChartModel::select('//whatever')
->whereColumn('//sub-query column', 'parent-table.field')
->whereColumn('//and whatever')
->latest()
->take(1)
)]
...

how to run mysql query in Laravel

I want to transform my MySql query into a Query in Laravel but I really don't know how to do this. I don't know how to rename in FROM like in SQL
My query is the following one :
SELECT f2.* FROM formation f2 WHERE f2.theme_id IN
(SELECT f.theme_id FROM user_formation uf JOIN formation f ON uf.formation_id = f.id WHERE uf.user_id = 2)
AND f2.id NOT IN
(SELECT formation_id FROM user_formation WHERE user_id = 2);
I tried something like this but ...
$q = Formation::query()
->from('formation AS f2')
->whereIn('f2.theme_id', function($r)
{
$r->select('f.theme_id')->from('user_formation AS uf')
->join('formation', function($join)
{
$join->on('uf.formation_id', '=', 'f.id')
->where ('uf.user_id', '=', $id)
});
});
->whereNotIn('f2.id', function($s){
$s->select('formation.id')
->from('user_formation')
->where('user_id', '=', $id)
})->get();
thanks for help.
If you want to run this raw query you can run:
$res = DB::select('
SELECT f2.*
FROM formation f2
WHERE f2.theme_id IN
(SELECT f.theme_id FROM user_formation uf JOIN formation f ON uf.formation_id = f.id WHERE uf.user_id = 2)
AND f2.id NOT IN
(SELECT formation_id FROM user_formation WHERE user_id = 2)');
Or you can rewrite this query in laravel query builder Eloquent ORM:
Formations::query()
->whereIn('formations.theme_id', function($q){
$user_formations_table = (new UserFormation)->getTable();
$formation_table = (new Formation)->getTable();
$q->select('paper_type_id')
->from($user_formations_table)
->join($formation_table, "$user_formations_table.formation_id", '=', "$formation_table.id")
->where("$user_formations_table.user_id", 2);
})->whereNotIn('formations.id', function($q){
$user_formations_table = (new UserFormation)->getTable();
$q->select('formation_id')
->where("$user_formations_table.user_id", 2);
})
->get();
Note that I have used models Formations, UserFormation, Formation Because you have used 3 different tables, you should add this models and specify tables to run ORM query
I advice to run first RAW query if there is no another need to run it with Eloquent
Hope this helps you
First of all, you need to fix your code indentations so you don't confuse yourself. Second, you placed semicolon in the wrong places. Third, you need to pass $id inside function because of the variable scope.
$q = Formation::query()
->whereIn('f2.theme_id', function($r) use ($id) {
$r->select('f.theme_id')->from('user_formation AS uf')
->join('formation', function($join) use ($id) {
$join->on('uf.formation_id', '=', 'f.id')
->where('uf.user_id', '=', $id);
}
);
})
->whereNotIn('f2.id', function($s) use ($id) {
$s->select('formation.id')
->from('user_formation')
->where('user_id', '=', $id);
})->get();
Note : If you are using VSCode, I suggest to use PHP Intelephense as it will help with autocomplete, syntax check, etc.

Why is DB::raw variable being amended from a string to an integer in Laravel 5.7

I have the following part of a larger elequent query, which all works when I'm not injecting a variable into DB::raw.
$tests->join('test_categories', function ($join) use($request) {
$join->on('test_categories.id', '=', DB::raw('(select category_id
from test_category
join test_categories on test_category.category_id = test_categories.id
where test_category.test_id = tests.id
and test_categories.name = ?
limit 1)', ['Cars']
));
});
But when the SQL executes it amends the '?' to 4, as in:
and test_categories.name = 4
Rather than being the expected:
and test_categories.name = 'Cars'
Any help would be appreciated.
UPDATED
I also tried the following before:
$tests->join('test_categories', function ($join) use ($category) {
$join->on('test_categories.id', '=', DB::raw("(select category_id
from test_category
join test_categories on test_category.category_id = test_categories.id
where test_category.test_id = tests.id
and test_categories.name = :category
limit 1)",array('category' => $category)));
});
But in the query, this just outputs the following:
and test_categories.name = :category
This comes with a "SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters" error.
Still the same issue.
And another variation which tries the same technique with the $join->on function.
$tests->join('test_categories', function ($join) use ($category) {
$join->on('test_categories.id', '=', DB::raw("(select category_id
from test_category
join test_categories on test_category.category_id = test_categories.id
where test_category.test_id = tests.id
and test_categories.name = :category
limit 1)"),array('category' => $category));
});
This results in an ErrorException (E_NOTICE) Array to string conversion:
protected function compileWheresToArray($query)
{
return collect($query->wheres)->map(function ($where) use ($query) {
return $where['boolean'].' '.$this->{"where{$where['type']}"}($query, $where);
})->all();
}
Based on this link you could use
$results = DB::select( DB::raw("SELECT * FROM some_table WHERE some_col = :somevariable"), array(
'somevariable' => $someVariable,
));
Try replacing :category with ".$category.".
Not sure if it's a good practice to do it this way but it should work.
$tests->join('test_categories', function ($join) use ($category) {
$join->on('test_categories.id', '=', DB::raw("(select category_id
from test_category
join test_categories on test_category.category_id = test_categories.id
where test_category.test_id = tests.id
and test_categories.name = ".$category."
limit 1)"));
});

Select SUM from subquery while using whereHas in Laravel

I have 2 tables, customers and customer_invoices, and I want to get all the customers with a condition on their invoices, and select specific columns (customers.id, customers.last_name, and the sum of the total_price of the invoices for each customer), I have this query :
$result = Customer::whereHas('customerInvoices', function(Builder $q) {
$q->where('customer_invoices.status', 1);
})->select([
'customers.id',
'customers.last_name',
\DB::raw('SUM(customer_invoices.total_price) as sum')
])->get();
customerInvoices is the relation :
public function customerInvoices() {
return $this->hasMany(CustomerInvoice::class);
}
I want to use subqueries instead of joins, so here I can't select this \DB::raw('SUM(customer_invoices.total_price) as sum'), or else I get this error of course :
"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'customer_invoices.total_price' in 'field list' (SQL: select `customers`.`id`, `customers`.`last_name`, SUM(customer_invoices.total_price) as sum from `customers` where exists (select * from `customer_invoices` where `customers`.`id` = `customer_invoices`.`customer_id` and `customer_invoices`.`status` = 1))"
How can I achieve this without using joins ?
You can use withCount() to get sum from related model as
$result = Customer::select([
'customers.id',
'customers.last_name'
])->withCount([
'customerInvoices as invoice_sum' => function($query) {
$query->select(DB::raw('SUM(total_price)'));
}
])->whereHas('customerInvoices', function(Builder $q) {
$q->where('customer_invoices.status', 1);
})->get();
Another approach to get sum, you can define a hasOne() relation in your Customer model like
public function invoice_sum()
{
return $this->hasOne(CustomerInvoice::class)
->select('customer_id',
DB::raw('sum(total_price)')
)->groupBy('customer_id');
}
And in query builder
$result = Customer::select([
'customers.id',
'customers.last_name',
])->with('invoice_sum')
->whereHas('customerInvoices', function(Builder $q) {
$q->where('customer_invoices.status', 1);
})->get();
As per Eloquent : withCount() overrides the $columns on get() issue first put select() mehtod and then use with() function