Append array to JSON column to multiple rows at once - php

I have a column type JSON in database, and I'm performing an update on multiple rows.
For example, this query
Model::whereIn('id',$ids)->update([
'status' => 'canceled'
]);
And this table has another column called history (JSON type), each row already has its own history in JSON.
How do I append to each one of them? This array, for example
[
'user_id' => '144',
'action' => 'cancel',
'at' => '2021 - 08 - 30'
]
My idea and question, is there something like
Model::whereIn( 'id', $ids )->appendJson('field_name',$array);

The easiest approach could be like this:
$status = 'canceled';
$extraHistoryData = [...];
Model::whereKey($ids)->get(function ($model) use ($status, $extraHistoryData) {
$history = $model->history;
$history = array_merge($history, $extraHistoryData);
$model->update(compact('status', 'history'));
});
If you want to update all rows in a single query, the closest I can get is this thread. But It's not very likely to solve your problem.

Related

Dynamically add columns to query results via CakePHP 3 ORM queries

I'm trying to write a query using CakePHP 3.7 ORM where it needs to add a column to the result set. I know in MySQL this sort of thing is possible: MySQL: Dynamically add columns to query results
So far I've implemented 2 custom finders. The first is as follows:
// src/Model/Table/SubstancesTable.php
public function findDistinctSubstancesByOrganisation(Query $query, array $options)
{
$o_id = $options['o_id'];
$query = $this
->find()
->select('id')
->distinct('id')
->contain('TblOrganisationSubstances')
->where([
'TblOrganisationSubstances.o_id' => $o_id,
'TblOrganisationSubstances.app_id IS NOT' => null
])
->orderAsc('Substances.app_id')
->enableHydration(false);
return $query;
}
The second custom finder:
// src/Model/Table/RevisionSubstancesTable.php
public function findProductNotifications(Query $query, array $options)
{
$date_start = $options['date_start'];
$date_end = $options['date_end'];
$query = $this
->find()
->where([
'RevisionSubstances.date >= ' => $date_start,
'RevisionSubstances.date <= ' => $date_end
])
->contain('Substances')
->enableHydration(false);
return $query;
}
I'm using the finders inside a Controller to test it out:
$Substances = TableRegistry::getTableLocator()->get('Substances');
$RevisionSubstances = TableRegistry::getTableLocator()->get('RevisionSubstances');
$dates = // method to get an array which has keys 'date_start' and 'date_end' used later.
$org_substances = $Substances->find('distinctSubstancesByOrganisation', ['o_id' => 123);
if (!$org_substances->isEmpty()) {
$data = $RevisionSubstances
->find('productNotifications', [
'date_start' => $dates['date_start'],
'date_end' => $dates['date_end']
])
->where([
'RevisionSubstances.substance_id IN' => $org_substances
])
->orderDesc('RevisionSubstances.date');
debug($data->toArray());
}
The logic behind this is that I'm using the first custom finder to produce a Query Object which contains unique (DISTINCT in SQL) id fields from the substances table, based on a particular company (denoted by the o_id field). These are then fed into the second custom finder by implementing where(['RevisionSubstances.substance_id IN' ....
This works and gives me all the correct data. An example of the output from the debug() statement is as follows:
(int) 0 => [
'id' => (int) 281369,
'substance_id' => (int) 1,
'date' => object(Cake\I18n\FrozenDate) {
'time' => '2019-09-02T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'comment' => 'foo',
'substance' => [
'id' => (int) 1,
'app_id' => 'ID000001',
'name' => 'bar',
'date' => object(Cake\I18n\FrozenDate) {
'time' => '2019-07-19T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
]
],
The problem I'm having is as follows: Each of the results returned contains a app_id field (['substance']['app_id'] in the array above). What I need to do is perform a count (COUNT() in MySQL) on another table based on this, and then add that to the result set.
I'm unsure how to do this for a couple of reasons. Firstly, my understanding is that custom finders return Query Objects, but the query is not executed at this point. Because I haven't executed the query - until calling $data->toArray() - I'm unsure how I would refer to the app_id in a way where it could be referenced per row?
The equivalent SQL that would give me the required results is this:
SELECT COUNT (myalias.app_id) FROM (
SELECT
DISTINCT (tbl_item.i_id),
tbl_item.i_name,
tbl_item.i_code,
tbl_organisation_substances.o_id,
tbl_organisation_substances.o_sub_id,
tbl_organisation_substances.app_id,
tbl_organisation_substances.os_name
FROM
tbl_organisation_substances
JOIN tbl_item_substances
ON tbl_organisation_substances.o_sub_id = tbl_item_substances.o_sub_id
JOIN tbl_item
ON tbl_item.i_id = tbl_item_substances.i_id
WHERE
tbl_item.o_id = 1
AND
tbl_item.date_valid_to IS NULL
AND
tbl_organisation_substances.app_id IS NOT NULL
ORDER BY
tbl_organisation_substances.app_id ASC
) AS myalias
WHERE myalias.app_id = 'ID000001'
This does a COUNT() where the app_id is ID000001.
So in the array I've given previously I need to add something to the array to hold this, e.g.
'substance' => [
// ...
],
'count_app_ids' => 5
(Assuming there were 5 rows returned by the query above).
I have Table classes for all of the tables referred to in the above query.
So my question is, how do you write this using the ORM, and add the result back to the result set before the query is executed?
Is this even possible? The only other solution I can think of is to write the data (from the query I have that works) to a temporary table and then perform successive queries which UPDATE with the count figure based on the app_id. But I'm really not keen on that solution because there are potentially huge performance problems of doing this. Furthermore I'd like to be able to paginate my query so ideally need everything confined to 1 SQL statement, even if it's done across multiple finders.
I've tagged this with MySQL as well as CakePHP because I'm not even sure if this is achievable from a MySQL perspective although it does look on the linked SO post like it can be done? This has the added complexity of having to write the equivalent query using Cake's ORM.

Distinct Values in Laravel

Hello Friends I am using the following query :
$cur_date = date('Y-m-d');
$clientTemp = DB::table('clients')->where('quotations.exp_date','<',$cur_date)
->join('quotations','quotations.customer_id','=','clients.id')
->get()
->map(function ($clientTemp) {
return [
'id' => $clientTemp->id,
'hash' => $clientTemp->hash,
'name' => $clientTemp->first_name.' '.$clientTemp->last_name,
'email' => $clientTemp->email,
'mobile' => $clientTemp->mobile
];
});
I am getting this data from two tables :
1. Qutoations and 2. Clients.
In quotations table if the exp_date is less than current date then the details will be fetched from client table.
But there is possibility then there are more than 1 rows in quotation table but I want to fetch only one table from that for which customer_id is unique. How can I fetch unique row with same customer_id from quotations table
You'd need to use a GROUP BY clause but due to MySQL's default ONLY_FULL_GROUP_BY mode, you must aggregate any column that has more than one value.
You don't seem to be actually using any values from quotations, so you could just add:
DB::table('clients')->select('clients.*')->groupBy('clients.id')...
Otherwise, you'd need to tell MySQL how to aggregate any rows that have multiple values, like:
DB::table('clients')->selectRaw('clients.*, MIN(quotations.id)')->groupBy('clients.id')...
You should use groupby
$cur_date = date('Y-m-d'); $clientTemp = DB::table('clients')->where('quotations.exp_date','<',$cur_date)
->join('quotations','quotations.customer_id','=','clients.id')
->->groupBy('quotations.customer_id')
->get()
->map(function ($clientTemp) {
return [
'id' => $clientTemp->id,
'hash' => $clientTemp->hash,
'name' => $clientTemp->first_name.' '.$clientTemp->last_name,
'email' => $clientTemp->email,
'mobile' => $clientTemp->mobile
];
});

Save all record records using request->all in Laravel

Laravel provides a great help for developers to save all input fields of a form which is one record with one line of code.
like if I want to save a form which has multiple input fields and one record to database like:
then I can save it with below code and it works great:
SaveOrder:: create($request->all());
Now I have a question. If I have multiple records (multiple rows) in a form and I can add new rows with a button pressed. Then how can I save all records with above code?
Like:
It's easy to do that using Eloquent :
$data = array(
array('field1'=>'value1', 'field2'=> value2),
array('field1'=>'value1', 'field2'=> value1),
//...
);
Model::insert($data);
Assuming your input names look something like name[], since you can add rows on the fly, you can retrieve the input as an array, and insert them using something like this:
$data = [];
$names = request('name');
$product_names = request('product_name');
$product_colour = request('product_colour');
$product_size = request('product_size');
for ($i = 0; $i < count($names); $i++) {
// Add checks to make sure indices actually exist, probably using preprocessing in JS
$data[] = [
'name' => $names[$i],
'product_name' => $product_names[$i],
'product_colour' => $product_colour[$i],
'product_size' => $product_size[$i],
];
}
Model::insert($data);
The best answer for this question is using foreach statement. Like:
$CustomerName= $request -> input('CustomerName');
$ProductId= $request -> input('ProductId');
$ProductName= $request -> input('ProductName');
$ProductColor= $request -> input('ProductColor');
foreach( $ProductId as $key => $n ) {
SaveOrder::insert(
array(
'CustomerName' => $CustomerName[$key],
'ProductId' => $ProductId[$key],
'ProductName' => $ProductPrice[$key],
'ProductColor' => $ProductQuantity[$key],
)
);}
Use upsert
If you use Laravel 8 or above, you can make use of upsert. Such an useful function to insert or update matching records at the same time.
SaveOrder::upsert($request->all(), ['id'], ['CustomerName', 'ProductName', 'ProductColor', 'ProductID']);
The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of the columns that should be updated if a matching record already exists in the database. The upsert method will automatically set the created_at and updated_at timestamps if timestamps are enabled on the model:
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);
Read the documentation on Laravel Upsert

cant get the group of an int field from a query in cakephp3

I have a table with a field called 'year'. It has many repetitions in the column so I want to find the distinct group. I have 4 different 'years' in about 20 rows and I dont get these values from the query. Instead what is returned are 4 numbers which are not the years (5,14,4,70). The same code worked fine when I used this with suburb field in another table where there were multiple values of this field. I dont get why this isnt working.
//in view
echo $this->Form->input('year', ['label' => 'Year','options' => $allyears]);
//controller
$allyears = $this->TimesheetDates->find('list')
->select(['TimesheetDates.id', 'TimesheetDates.year'])
->group(['TimesheetDates.year'])->autoFields(true)
->order(['TimesheetDates.year'=> 'ASC'])
->hydrate(false);
$this->set('allyears',$allyears);
//another controller and this code worked fine
$suburb = $this->Students->find('list')->where(['Students.address_suburb !=' => '','Students.student_inactive' => 0])
->select(['Students.id','Students.address_suburb'])
->group(['Students.address_suburb'])->autoFields(true)
->order(['Students.address_suburb' => 'ASC'])
->hydrate(false);
take a look at the documentation about how find('list') works
$allyears = $this->TimesheetDates->find('list', [
'keyField' => 'id',
'valueField' => 'year']
)
->group(['year'])
->order(['year'=> 'ASC']);
Note that it has no meaning selecting the id of the TimesheetDates Table as you are grouping by year and the id is choosen randomly between all the records that share the same year

How to increment and update column in one eloquent query

Is it possible to update a timestamp (besides updated_at) and increment a column in one query? I obviously can
->increment('count')
and separately
->update(['last_count_increased_at' => Carbon::now()])
but is there an easy way to do both together.
Product::where('product_id', $product->id)
->update(['count'=> $count + 1, 'last_count_increased_at' => Carbon::now()];
Without having to query and get the count first?
You can specify additional columns to update during the increment or decrement operation:
Product::where('id',$id)
->increment('count', 1, ['increased_at' => Carbon::now()]);
It is more eloquent solution.
You can use the DB::raw method:
Product::where('product_id', $product->id)
->update([
'count'=> DB::raw('count+1'),
'last_count_increased_at' => Carbon::now()
]);
With Laravel 8 you can now achieve this in a single query to create or update on duplicate key.
$values = [
'name' => 'Something',
'count' => 1,
];
$uniqueBy = ['name'];
$update = ['count' => DB::raw('count+1')];
Model::upsert($values, $uniqueBy, $update);
If the model exists count will be incremented, if it is inserted count will equal 1. This is done on the DB level, so only one query involved.
Read more about upserts: https://laravel.com/docs/8.x/eloquent#upserts

Categories