How to get distinct values for non-key column fields in Laravel? - laravel

This might be quite easy but have no idea how to.
I have a table that can have repeated values for a particular non-key column field. How do I write a SQL query using Query Builder or Eloquent that will fetch rows with distinct values for that column?
Note that I am not fetching that column only, it is in conjunction with other column values, so distinct() might not really work. So that question basically can be how to specify the column that I want to be distinct in a query now that distinct() accepts no parameters?

You should use groupby. In Query Builder you can do it this way:
$users = DB::table('users')
->select('id','name', 'email')
->groupBy('name')
->get();

In Eloquent you can also query like this:
$users = User::select('name')->distinct()->get();

in eloquent you can use this
$users = User::select('name')->groupBy('name')->get()->toArray() ;
groupBy is actually fetching the distinct values, in fact the groupBy will categorize the same values, so that we can use aggregate functions on them. but in this scenario we have no aggregate functions, we are just selecting the value which will cause the result to have distinct values

Though I am late to answer this, a better approach to get distinct records using Eloquent would be
$user_names = User::distinct()->get(['name']);

**
Tested for Laravel 5.8
**
Since you wanna get all columns from the table, you can collect all of the data and then filter it using Collections function called Unique
// Get all users with unique name
User::all()->unique('name')
or
// Get all & latest users with unique name
User::latest()->get()->unique('name')
For more information you can check Laravel Collection Documentations
EDIT: You might have issue with perfomance, by using Unique() you'll get all data first from User table, and then Laravel will filter it.
This way isn't good if you have lots of Users data. You can use query builder and call each fields that you wanna use, example:
User::select('username','email','name')->distinct('name')->get();

Grouping by will not work if the database rules don't allow any of the select fields to be outside of an aggregate function. Instead use the laravel collections.
$users = DB::table('users')
->select('id','name', 'email')
->get();
foreach($users->unique('name') as $user){
//....
}
Someone pointed out that this may not be great on performance for large collections. I would recommend adding a key to the collection. The method to use is called keyBy. This is the simple method.
$users = DB::table('users')
->select('id','name', 'email')
->get()
->keyBy('name');
The keyBy also allows you to add a call back function for more complex things...
$users = DB::table('users')
->select('id','name', 'email')
->get()
->keyBy(function($user){
return $user->name . '-' . $user->id;
});
If you have to iterate over large collections, adding a key to it solve the performance issue.

Note that groupBy as used above won't work for postgres.
Using distinct is probably a better option - e.g.
$users = User::query()->distinct()->get();
If you use query you can select all the columns as requested.

$users = User::select('column1', 'column2', 'column3')->distinct()->get(); retrieves all three coulmns for distinct rows in the table. You can add as many columns as you wish.

I found this method working quite well (for me) to produce a flat array of unique values:
$uniqueNames = User::select('name')->distinct()->pluck('name')->toArray();
If you ran ->toSql() on this query builder, you will see it generates a query like this:
select distinct `name` from `users`
The ->pluck() is handled by illuminate\collection lib (not via sql query).

// Get unique value for table 'add_new_videos' column name 'project_id'
$project_id = DB::table('add_new_videos')->distinct()->get(['project_id']);

I had the same issues when trying to populate a list of all the unique threads a user had with other users. This did the trick for me
Message::where('from_user', $user->id)
->select(['from_user', 'to_user'])
->selectRaw('MAX(created_at) AS last_date')
->groupBy(['from_user', 'to_user'])
->orderBy('last_date', 'DESC')
->get()

Here are 3 ways I have tested that will give same result:
User::distinct()->get(['name'])->pluck('name');
User::select('name')->distinct()->pluck('name')->all();
DB::table('users')->select('name')->groupBy('name')->get()->pluck('name')->all();

For those who like me doing same mistake. Here is the elaborated answer
Tested in Laravel 5.7
A. Records in DB
UserFile::orderBy('created_at','desc')->get()->toArray();
Array
(
[0] => Array
(
[id] => 2073
[type] => 'DL'
[url] => 'https://i.picsum.photos/12/884/200/300.jpg'
[created_at] => 2020-08-05 17:16:48
[updated_at] => 2020-08-06 18:08:38
)
[1] => Array
(
[id] => 2074
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[created_at] => 2020-08-05 17:20:06
[updated_at] => 2020-08-06 18:08:38
)
[2] => Array
(
[id] => 2076
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[created_at] => 2020-08-05 17:22:01
[updated_at] => 2020-08-06 18:08:38
)
[3] => Array
(
[id] => 2086
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[created_at] => 2020-08-05 19:22:41
[updated_at] => 2020-08-06 18:08:38
)
)
B. Desired Grouped result
UserFile::select('type','url','updated_at)->distinct('type')->get()->toArray();
Array
(
[0] => Array
(
[type] => 'DL'
[url] => 'https://i.picsum.photos/12/884/200/300.jpg'
[updated_at] => 2020-08-06 18:08:38
)
[1] => Array
(
[type] => 'PROFILE'
[url] => 'https://i.picsum.photos/13/884/200/300.jpg'
[updated_at] => 2020-08-06 18:08:38
)
)
So Pass only those columns in "select()", values of which are same.
For example: 'type','url'. You can add more columns provided they have same value like 'updated_at'.
If you try to pass "created_at" or "id" in "select()", then you will get the records same as A.
Because they are different for each row in DB.

In my case (with Laravel 9), I need:-
to use where clause.
to get complete row.
to get unique entries for specific column.
As more complex query, but still, I have an easy approach. May be that help you also.
User::where('id',auth()->user()->id)->distinct('name')->get();

Related

How to Sort Laravel collection groupBy results in asc order

I am just running a query in Laravel using a query builder as follows.
$query = \DB::table('units_amenities_values')
->join('units', 'units.id', 'units_amenities_values.unit_id')
->join('floors', 'floors.id', 'units.floor_id')
->join('buildings', 'buildings.id', 'floors.building_id')
->join('properties', 'properties.id', 'buildings.property_id')
->leftJoin('amenity_values', 'amenity_values.id', 'units_amenities_values.amenity_value_id')
->leftJoin('amenities', 'amenities.id', 'amenity_values.amenity_id')
->select('units_amenities_values.id as uav_id', 'amenities.id as amenity_id', 'amenities.amenity_name', 'amenity_values.amenity_value', 'floors.floor','units.id as unit_id', 'units.unit_number','units.unit_rent', 'units.stack', 'amenities.category_id', 'buildings.id as building_id')
->where('buildings.property_id',2)
->orderBy('units.unit_number','asc');
$data = $query->get();
$grouped = $data->groupBy('building_id');
This works without error, however, the result is not in the way as I intended. This is grouping in descending order.
I tried by adding ->orderBy('buildings.id','asc') before as well as after orderBy('units.unit_number','asc'). This makes does not make any difference. Also, tried by adding orderBy('building_id','asc') with groupBy as well, but it throws error as orderBy does not exist.
Is there anything that I could do, to get the results as my preference.
By the way, the $grouped looks like:
<pre>Illuminate\Support\Collection Object
(
[items:protected] => Array
(
[7] => Illuminate\Support\Collection Object
(
[items:protected] => Array
(
[0] => stdClass Object
(
[uav_id] => 5557
...
...
[building_id] => 7
)
[1] => ...
[2] => ...
)
)
[6] => ...
[5] => ...
[4] => ...
...
)
)
</pre>
As you have given several examples I'm not sure what order you want the results in. Do you want the results ordered by buildings_id, or unit_number or both?
The query you have shown will order the results by unit_number (although you have failed to show this value in you output).
The ->groupBy() method takes the building_id from your results and creates a new key - but doesn't reorder your collection, so it's still ordered by unit_number.
If you want to return a collection ordered by building_id, use ->orderBy('building_id', 'asc') which will return an ordered collection. If you iterate through it, it will be in the correct order.
If you prefer a plain array, use
$array = $data->toArray()
If you want to keep the grouping (with the extra key), then use
$grouped = $data->sortBy('building_id')->groupBy('building_id')

Yii2: query across two databases

I have two databases (one in Oracle and one mySQL) and I somehow need to join the data.
The following query works:
$qry = oracleTableName::find()
->with('mysqlTableName')
->asArray()
->all();
and returns the following layout:
[0]=> array(
[id] => 1
[name] => test
[mysqlID] => 7
[mysqlTableName] => array(
[id]=>7
[score]=>1
)
)
However, if I use the select statement, it fails, (saying that the column mysqlTableName.id is an invalid identifier):
$qry = oracleTableName::find()
->with('mysqlTableName')
->select([
'oracleTableName.id as OracleID',
'mysqlTableName.id as MysqlID',
'mysqlTableName.score as Score'
])
->asArray()
->all();
How can I select from both databases (or "access" the mysql result) so that I have one output i.e.:
[0]=>array(
[OracleID]=>1
[MysqlID]=>7
[Score]=>3
Thank you
UPDATE
Here is the actual query and outputs:
NOTE: in this example, the table "MapInvestorToOpportunity" is mysql and the table "INVESTOR" is Oracle
This works fine:
$performance= MapInvestorToOpportunity::find()
->with('investor')
->andWhere(['fk_opportunityID' => $this->fk_opportunityID])
->limit(5)
->asArray()
->all();
And yields the following output:
Array
(
[0] => Array
(
[id] => 43797
[uid] => 0451/0258_DD45834-99207
[fk_opportunityID] => 3
[status] => 1
[fk_investorID] => 99207
[investor] => Array
(
[INVESTOR_ID] => 99207
[COUNTRY_ID] => US
[PRIMARY_INSTITUTION] => DD71233
I can clearly see the country ID. However, as soon as I select the country ID it fails:
$performance= MapInvestorToOpportunity::find()
->with('investor') // or ('investor INVESTOR')
->andWhere(['fk_opportunityID' => $this->fk_opportunityID])
->select([
'fk_opportunityID',
'fk_investorID',
'map_investor_to_opportunity.INVESTOR_ID',
'COUNTRY_ID', // or 'INVESTOR.COUNTRY_ID'
])
->limit(5)
->asArray()
->all();
Column not found: 1054 Unknown column 'INVESTOR.COUNTRY_ID' in 'field list'
The SQL being executed was: SELECT `fk_opportunityID`, `fk_investorID`, `INVESTOR`.`COUNTRY_ID` FROM `map_investor_to_opportunity` WHERE `fk_opportunityID`='3' LIMIT 5
My understanding is that it is not possible to join the data in a query because it is two different databases. However, I just wanted to make absolutely sure... it seems a little crazy considering the array output clearly shows the data from the Oracle database
Many thanks
I don't think it is possible to create JOIN between two completely separate DBMS. with() will register eager loading rules, but it will not create actual join between two tables - it will perform two separate queries to obtain necessary models.
To create actual join you should use joinWith() instead of with().
$qry = oracleTableName::find()
->joinWith('mysqlTableName')
->select([
'oracleTableName.id as OracleID',
'mysqlTableName.id as MysqlID',
'mysqlTableName.score as Score'
])
->asArray()
->all();
But this will most likely fail, since there is no support for cross-DB joins.
Probably the best what you could get is to query results separately and combine them at PHP level.
You should just add a table identifier after the table name.
$qry = oracleTableName::find()
->with('mysqlTableName msql')
->select([
'oracleTableName.id as OracleID',
'msql.id as MysqlID',
'msql.score as Score'
])
->asArray()
->all();
The reason for this is, the with clause accepts relation name, not Table name, which might be slightly, but enough different. If this doesn't work, please comment with the result of this query and I will edit the answer accordingly
$qry = oracleTableName::find()
->with('mysqlTableName msql')
->select([
'oracleTableName.id as OracleID',
'msql.id as MysqlID',
'msql.score as Score'
])
->createCommand()->rawSql;

Any way to except empty field from $request->all() in laravel?

I want to except empty value field from $request->all();
Array ( [first_name] => Dev 1 [password] => [last_name] => [phone] => 123456 [password_confirmation] => )
I got the array like this but I want to except field from above array like last_name, password which has no value.
Any way to do this without for loop.
I mean laravel provide any default method for that ?
Thanks
array_filter will remove empty elements:
$filtered = array_filter($request->all());

Inserting multipe rows from dynamic form fields using laravel 5

How can I store multiple form array data in laravel, as I need to create a new record against each array index.
[0] => Array
(
[make] => test
[model] => XYZ
)
[1] => Array
(
[make] => s4
[model] => BB
)
[2] => Array
(
[make] => 99
[model] => AX
)
This is what I am trying to, but here loop get executed 6 times instead of three
$results = $request->all();
foreach ($results as $key => $value) {
echo $key;
// insert operation
}
I believe you should specify the control/field because the Request contains other (non-data) information. Something like:
$results = $request['array_name'];
https://laravel.com/docs/5.4/queries#inserts
Model::insert($request->all())
This will mass insert each array into your database. insert do not automatically set datetime values for created_at and updated_at, note that the array keys should match your table columns, and make sure your model has these fields as $fillables.

CodeIgniter 3.1.7 unbuffered_row() returning NULL but row() works

I am attempting to output a CSV from a query result. dbutil->csv_from_result() should work, but it only returns the column headers. No data is included.
I tracked it down to system/database/DB_utility.php which uses unbuffered_row().
Any calls to unbuffered_row() return NULL. If I change it to row(), I get a result. It makes no difference if I use unbuffered_row('array') or unbuffered_row('object')
Why does row() work but unbuffered_row() does not?
Is this a bug in CI or am I missing something?
Edit: row('array') doesn't seem to work either.
Edit: It seems that calling $query->result() spoils dbutil->csv_from_result($query). You apparently cannot iterate through query results AND then save the results in a CSV file. This was possible in CI 2.
Is there any way to show query results AND save the CSV without running the query twice?
I have faced similar problem and found this:
I have this query:
$query = $this->db->query("SELECT * FROM tablename");
print_r($query);
Then I get this and unbuffered_row works fine:
CI_DB_oci8_result Object
(
[stmt_id] => Resource id #106
[curs_id] =>
[limit_used] =>
[commit_mode] => 32
[conn_id] => Resource id #91
[result_id] => 1
[result_array] => Array
(
)
[result_object] => Array
(
)
[custom_result_object] => Array
(
)
[current_row] => 0
[num_rows] =>
[row_data] =>
)
BUT if I call $query->num_rows() before I get a different CI_DB_oci8_result object and unbuffered_row didn't work returning null
$query = $this->db->query("SELECT * FROM tablename");
echo $query->num_rows();
print_r($query);
Hope this help somebody.

Resources