How to Write sub Query in laravel - laravel

Here is my Query
SELECT
SUM(A.total_price) AS total_sum
FROM
(
SELECT
*, (qty * cost) AS total_price
FROM
services_p_o_items_management
WHERE
services_pos_id = '.$id.'
) AS A
How to write in laravel anyone here to help me

If you want to run the query in a loop:
DB::table('services_p_o_items_management')
->selectRaw("SUM(qty * cost) as total_sum")
->where('services_pos_id', $id)
->first();
If you want to execute a single query for all the ids:
DB::table('services_p_o_items_management')
->selectRaw("services_pos_id, SUM(qty * cost) as total_sum")
->whereIn('services_pos_id', $ids)
->groupBy("services_pos_id")
->get();

You can use raw query in from() method
DB::query()->from('SELECT *, (qty * cost) AS total_price FROM services_p_o_items_management WHERE services_pos_id = '.$id, 'A')
->get();
Beside that, first argument of from() also accept a Builder object
/**
* Set the table which the query is targeting.
*
* #param \Closure|\Illuminate\Database\Query\Builder|string $table
* #param string|null $as
* #return $this
*/
public function from($table, $as = null)
Thus, you can change your raw query into a Builder object
$query = DB::table('services_p_o_items_management')
->select(['*', DB::raw('(qty * cost) AS total_price')])
->where('services_pos_id', $id);
DB::query()->from($query, 'A')->get();

Related

Laravel 8 - Left Outer Join with multiple conditions

In a certain portion of my Laravel apps, I need to fetch data using Left outer join. If the join requires only one condition then I can easily handle this (by adding left outer as a param in the Laravel join clause), but I need to use two conditions in the left outer join. So far I write the following query:
$events = DB::table('events AS ev')
->join('event_registrations AS er', function ($join) {
$join->on('ev.id', '=', 'er.event_id')
->where('er.status', '=', 'confirmed');
})
->select('ev.id', 'ev.event_name', 'ev.event_link', 'ev.description', 'ev.total_tickets', 'ev.logo_path', DB::raw("IFNULL( count(er.id), 0 ) as total_confirmed"))
->groupByRaw("ev.id, ev.event_name, ev.event_link, ev.description, ev.total_tickets, ev.logo_path, ev.total_tickets")
->get();
Which creates an inner join query. I have tried to add left outer as the following way:
$events = DB::table('events AS ev')
->join('event_registrations AS er', function ($join) {
$join->on('ev.id', '=', 'er.event_id')
->where('er.status', '=', 'confirmed');
}, 'left outer')
->select('ev.id', 'ev.event_name', 'ev.event_link', 'ev.description', 'ev.total_tickets', 'ev.logo_path', DB::raw("IFNULL( count(er.id), 0 ) as total_confirmed"))
->groupByRaw("ev.id, ev.event_name, ev.event_link, ev.description, ev.total_tickets, ev.logo_path, ev.total_tickets")
->get();
But it still produces inner join.
Does anyone know how to create a left outer join query using multiple conditions in Laravel?
If you look at the source code at Illuminate\Database\Query\Builder.php
The join method is defined like this.
/**
* Add a join clause to the query.
*
* #param string $table
* #param \Closure|string $first
* #param string|null $operator
* #param string|null $second
* #param string $type
* #param bool $where
* #return $this
*/
public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
So type is actually the fifth parameter then your join should be
->join('event_registrations AS er', function ($join) {}, null, null, 'left outer')

In Laravel Eloquent why are some SQL parameters not bound in?

Question:
I Noticed interesting behavior in Laravel 7.x where eager loaded relationships don't always have bindings. Is this expected behavior and why would that be the case?
Code:
Actual Queries Laravel Runs:
select top 100 * from task_view
select id, name from task_view where active = ? and student_id in (?, ?, ?)
select id, name from task_view where active = ? and teacher_id in (1 ,2 ,3)
Relationships on Model:
public function studentTasks()
{
return $this->hasMany(StudentTasks::class, 'student_id', 'id');
}
public function teacherTasks()
{
return $this->hasMany(TeacherTasks::class, 'teacher_id', 'teacher_id');
}
Calling Code:
TaskView::query()->with(['studentTasks', 'teacherTasks']);
Additional Points:
I think it may have to do with that where the localkey of the relationship (the 3rd argument) is 'id' then the values aren't bound.
My assumption is that bindings are to prevent sql injection and the Docs seem to confirm that. If that's the case then why would id's of the model that the relationship is on not need to be bound? I would assume there's still an issue of SQL Injection there.
I have not seen anyone discussing this from my searching around, (Stackoverflow, Laracasts, Laravel docs)
(I printed out the queries using the below code in AppServiceProvider:boot)
$counter = 0;
\DB::listen(function ($query) use (&$counter) {
echo 'count: '.++$counter.PHP_EOL;
// echo memory_get_usage();
echo $query->sql.PHP_EOL;
echo implode(',', $query->bindings).PHP_EOL;
});
This is a change introduced into Laravel 5.7.14. The initial pull request can be found here. From there you can find more pull requests making updates to the functionality.
It was done as a performance enhancement when needing to eager load a large number of records (many thousands). Instead of having thousands of bound parameters, it puts the raw ids directly in the query. Initially it was done to work around a MySQL PDO bug, but really all database drivers can benefit with not having thousands of bound parameters.
The reason why it does not introduce a SQL injection vulnerability is that:
It only replaces the bindings with the raw values when the ids are integers, and
It runs all the ids through an integer conversion before adding them to the query.
This is the function that ultimately determines if parameters will be used or if raw ids will be used (https://github.com/laravel/framework/blob/7.x/src/Illuminate/Database/Eloquent/Relations/Relation.php#L310-L323):
/**
* Get the name of the "where in" method for eager loading.
*
* #param \Illuminate\Database\Eloquent\Model $model
* #param string $key
* #return string
*/
protected function whereInMethod(Model $model, $key)
{
return $model->getKeyName() === last(explode('.', $key))
&& in_array($model->getKeyType(), ['int', 'integer'])
? 'whereIntegerInRaw'
: 'whereIn';
}
And here is the whereIntegerInRaw() function that shows the keys are int cast before being added into the raw query (https://github.com/laravel/framework/blob/7.x/src/Illuminate/Database/Query/Builder.php#L961-L985):
/**
* Add a "where in raw" clause for integer values to the query.
*
* #param string $column
* #param \Illuminate\Contracts\Support\Arrayable|array $values
* #param string $boolean
* #param bool $not
* #return $this
*/
public function whereIntegerInRaw($column, $values, $boolean = 'and', $not = false)
{
$type = $not ? 'NotInRaw' : 'InRaw';
if ($values instanceof Arrayable) {
$values = $values->toArray();
}
foreach ($values as &$value) {
$value = (int) $value;
}
$this->wheres[] = compact('type', 'column', 'values', 'boolean');
return $this;
}

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.

Why delete method of Eloquent (Laravel) fires a select?

I have got this:
$obj = DeliveryNote::find($row->note_id);
$obj->products()->delete();
$obj->delete();
$obj = Order::find($row->order_id);
$obj->delete();
The log file of sql queries is this:
- select * from `delivery_notes` where `delivery_notes`.`id` = ? limit 1
- delete from `delivery_note_products` where `delivery_note_products`.`delivery_note_id` = ? and `delivery_note_products`.`delivery_note_id` is not null
- delete from `delivery_notes` where `id` = ?
- select * from `orders` where `orders`.`id` = ? limit 1
- delete from `order_products` where `order_products`.`order_id` = ? and `order_products`.`order_id` is not null
¿¿?? ==> select * from `delivery_notes` where `delivery_notes`.`id` = ? limit 1
I dont understand that, the Eloquent models havent got any strange.
class DeliveryNote extends Model
{
protected $table = 'delivery_notes';
/**
* #var array
*/
protected $fillable = ['...'];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function order()
{
return $this->belongsTo('App\Models\Order');
}
...
I thought to comment "order()" method but it does nothing.
Any ideas???
You are using find which queries the database and returns an Eloquent model you then use delete on. If you use this way you can utilize Eloquent events.
If you want to delete an entry in your database without getting an Eloquent response you need to skip get and find and just use delete instead.
Model::where('id', $id)->delete();

How to use WHERE in yii2 joinWith() that is doing eager loading

I have tables: document and document_content. One document can have many contents.
I am using joinWith() method to get data from document_content table together with document using model relations.
The queries executed are these :
SELECT document.* FROM document INNER JOIN document_content ON document.id = document_content.document_id WHERE (lang='1') ORDER BY id DESC LIMIT 10
SELECT * FROM document_content WHERE document_id IN (665566, 665034, 664961, 664918, 664910, 664898, 664896, 664893, 664882, 664880)
I have a problem with this second query. I want it to include this WHERE clause from the first one: WHERE (lang='1')
So I want yii to generate this query:
SELECT * FROM document_content WHERE (lang='1') AND document_id IN (665566, 665034, 664961, 664918, 664910, 664898, 664896, 664893, 664882, 664880)
I have managed somehow to achieve this, but I have code repetition and I do not like it. There must be some better way to do this. This is my code that works, but it's not that good I think:
/**
* Returns documents by params.
*
* #param array $params the query params.
* #return ActiveDataProvider
*/
public function findDocuments($params)
{
/** #var $query ActiveQuery */
$query = Document::find();
// store params to use in other class methods.
self::$_params = $params;
// build dynamic conditions for document table
$this->buildDocumentQuery($query);
// build dynamic conditions for document_content table
$this->buildDocumentContentQuery($query);
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort' => ['defaultOrder' => ['id' => SORT_DESC]],
'pagination' => [
'pageSize' => 10,
],
]);
return $dataProvider;
}
/**
* Relation with document_content table.
*
* #return DocumentContent
*/
public function getDocumentContent()
{
$query = $this->hasMany(DocumentContent::className(), ['document_id' => 'id']);
if (isset(self::$_params['lang'])) {
$query->andFilterWhere([
'lang' => self::$_params['lang'],
]);
}
}
/**
* Method that is responsible for building query conditions for document_content table.
*
* #param object $query ActiveQuery instance.
* #return ActiveQuery
*/
public function buildDocumentContentQuery($query)
{
if (isset(self::$_params['lang'])) {
$query->innerJoinWith('documentContent');
}
return $query;
}
As you can see I am checking for params['lang'] on two places. In my relation method and in buildDocumentContentQuery() method. So I am repeating same code on two places, and lang param is not going to be the only one that I want to test, there can be 10 or more.
Basically, I had to do all of this because I could not send any params through yii2 joinWith() method. I do not know what is the best way to add WHERE to query that is generated by eager loading of joinWith(). I made it work somehow, but I think this is dirty.
Does anyone have any idea for better/cleaner solution to this problem ?
Model#Document
public function getDocuments($params)
{
/** #var $query ActiveQuery */
$query = Document::find();
$query->getDocumentContentsByLanguage($params['lang']);
}
public function getDocumentContentsByLanguage($lang = null)
{
return $this->hasMany(DocumentContent::className(), ['document_id' => 'id'])->where('lang = :lang', [':lang'=>$lang]);
}
Try this:
$query = $this
->hasMany(DocumentContent::className(), ['document_id' => 'id']);
if (isset(self::$_params['lang']) && self::$_params['lang']==1) {
$query
->joinWith('document')
->andWhere([
Document::tablename().'.lang' => self::$_params['lang']
]);
}

Resources