Group by not working - Laravel - laravel

I'm not able to run this simple query in Laravel 5.3
$top_performers = DB::table('pom_votes')
->groupBy('performer_id')
->get();
It gives me:
SQLSTATE[42000]: Syntax error or access violation: 1055 'assessment_system.pom_votes.id' isn't in GROUP BY (SQL: select * from `pom_votes` group by `performer_id`)
However if I copy raw query from the error and fire directly in PhpMyAdmin, it works fine.
I have already checked this:
https://laravel.com/docs/5.3/queries#ordering-grouping-limit-and-offset
Any help would be appricaited.
Thanks,
Parth Vora

Edit your applications's database config file config/database.php
In mysql array, set strict => false to disable MySQL's strict mode

Maybe your issue is due to the fact that you are using a MySQL server vith version 5.7.5+. From this version on the way GROUP BY works is changed since they make it behave in order to be SQL99 compliant (where in previous versions it was not).
Try to do a full group by or change the configuration of your MySQL server.
Link to official MySQL doc where full GROUP BY is explanined

More safe method instead of disabling strict ('strict' => false) what you could do is pass an array to the config, enabling only the modes that you want:
// config/database.php
'connections' => [
//...
'mysql' => [
//...
'strict' => true,
'modes' => [
//'ONLY_FULL_GROUP_BY', // Disable this to allow grouping by one column
'STRICT_TRANS_TABLES',
'NO_ZERO_IN_DATE',
'NO_ZERO_DATE',
'ERROR_FOR_DIVISION_BY_ZERO',
//'NO_AUTO_CREATE_USER', // This has been deprecated and will throw an error in mysql v8
'NO_ENGINE_SUBSTITUTION',
],
],
],
For anybody who is still getting the same error after changing that setting, try clearing the config cache by running php artisan config:cache

Go to config/database.php
Update strict value false.
return [
'connections' => [
'mysql' => [
'strict' => false
]
]
]

There are ways to fix this
#1
Get only the columns we are grouping by, in this case category_id.
NOTE: Columns in select must be present in groupBy, and vice versa.
$posts = Post::query()
->select('category_id')
->groupBy('category_id')
->get();
category_id
1
2
#2
But I want all columns!
Okay, so you want to get all columns. Then the trick is to simply not use groupBy() on a database level. Instead, you can use it with the returned collection instead.
$posts = Post::query()
->get()
->groupBy('category_id');
[
'1' => [
['id' => 1, 'name' => 'Post 1', 'category_id' => 1, 'author_id' => 4 'visits' => 32],
['id' => 2, 'name' => 'Post 2', 'category_id' => 1, 'author_id' => 8 'visits' => 12],
],
'2' => [
['id' => 3, 'name' => 'Post 3', 'category_id' => 2, 'author_id' => 12 'visits' => 201],
['id' => 4, 'name' => 'Post 4', 'category_id' => 2, 'author_id' => 4 'visits' => 0],
],
]
#3
It is possible to simply disable "strict mode" in Laravel, by setting it to false in the database.php config file. While possible I cannot recommend doing so. It is better to spend the time learning how to write proper SQL queries, as the results given by turning "strict mode" off, can be unpredictable and lead to problems down the road.
Reference
https://sinnbeck.dev/posts/laravel-groupby-error

You can define this line before your query, let's suppose you want to use groupBy so for that instead of changing the config strict to false, simply add this line before where you had used groupBy:
\DB::statement("SET SQL_MODE=''");//this is the trick use it just before your query where you have used group by. Note: make sure your query is correct.
//this is just an example code.
$Rspatients = DB::table('reports')
->select(
DB::raw("day(created_at) as day"),
DB::raw("Count(*) as total_patients"))
->orderBy("created_at")
->groupBy(DB::raw("day(created_at)"))
->get();

My company uses raw SQL to run group by without risking changing mysql settings.
here is an working example :
public static function getPositivesDaily($start_date, $end_date, $admin_id)
{
$positives = DB::select(
'select COUNT(inspections.id) as total,DATE_FORMAT(inspections.created_at, :format) as date
from inspections
where inspections.created_at between :start_date and :end_date
and inspection_results = 1
and admin_id = :admin_id
GROUP BY date',
['format'=>'%Y-%m-%d', 'start_date'=>$start_date, 'end_date'=> $end_date, 'admin_id'=>$admin_id]
);
return $positives;
}
Ask me anything about this code if you don't understand and I will reply as soon as I can.
cheers.

If you false strict mode then you can't use other strict functionality to fix this error Go to the Illuminate\Database\Connectors\MySqlConnector.php and change function like below:
protected function strictMode() {
return "set session
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY
_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'";
}
replace function with this.

Related

Query parameter binding issue with illuminate/database and illuminate/queue

I'm using Illuminate\Queue outside of a Laravel app inside an add-on for a CMS. So the only instances of Laravel or Illuminate are these packages that I've required:
"illuminate/queue": "^8.83",
"illuminate/bus": "^8.83",
"illuminate/contracts": "^8.83"
I'm first trying to use the Database for the queue as the default driver since the CMS is database driven, then provide options to SQS etc. I've setup everything so the migrations create my queue tables and everything seems to be wired together when I make the following call to push something to the queue.
/** #var \Illuminate\Queue\QueueManager $queue */
$queue->push('test', ['foo' => 'bar']);
Then it ends in the following error. The parameter bindings are not working or something. It's leaving the ? in the values list.
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"exp_dgq_jobs" ("queue", "attempts", "reserved_at", "available_at", "created_at"' at line 1 (SQL: insert into "exp_dgq_jobs" ("queue", "attempts", "reserved_at", "available_at", "created_at", "payload") values (default, 0, ?, 1674567590, 1674567590, {"uuid":"6bf7a17e-dda3-4fed-903a-8714e5a2d146","displayName":"test","job":"test","maxTries":null,"maxExceptions":null,"failOnTimeout":false,"backoff":null,"timeout":null,"data":{"foo":"bar"}}))
I've step debugged the whole request and it feels like a bug, but then again this is really the first time I've used Laravel or one of it's packages, so maybe I'm missing something? This function explicitly sets reserved_at to null, and the Connection->prepareBindings() method doesn't do anything with the ?, it just leaves it as that value, so the query fails.
protected function buildDatabaseRecord($queue, $payload, $availableAt, $attempts = 0)
{
return ['queue' => $queue, 'attempts' => $attempts, 'reserved_at' => null, 'available_at' => $availableAt, 'created_at' => $this->currentTime(), 'payload' => $payload];
}
What am I missing? Everything just looks right to me an I'm kind of at a loss. I'm making this with PHP 7.4 in mind (for the time being). Maybe I'll try 8.1 to see if that changes anything with the Illuminate packages. Using MySQL 8 too.
Update: potentially relevant screenshot just before the error.
Update 2: I tried PHP 8.1 and latest Laravel 9 packages, didn't make a difference.
For more clarity on how I"m creating my QueueManager:
<?php $queue = new Queue;
$queue->addConnection([
'driver' => 'database',
'table' => ee('db')->dbprefix . 'dgq_jobs',
'queue' => 'default',
'retry_after' => 90,
'after_commit' => false,
]);
$databaseConfig = $provider->make('DatabaseConfig');
$queue->addConnector('database', function () use ($databaseConfig) {
$pdo = new PDO(
sprintf('mysql:host=%s; dbname=%s', $databaseConfig['host'], $databaseConfig['database']),
$databaseConfig['username'],
$databaseConfig['password']
);
$connection = new Connection($pdo);
$connectionResolver = new ConnectionResolver(['default' => $connection]);
$connectionResolver->setDefaultConnection('default');
return new DatabaseConnector($connectionResolver);
});
return $queue->getQueueManager();
I was able to reproduce the error you were seeing. I haven't looked too deeply but I think it may be due to the PDO object not setting up the connection exactly as the Illuminate Queue library expects.
This modification to using the Illuminate\Database library to create the connection solved the issue in my test environment:
$database = new \Illuminate\Database\Capsule\Manager;
$queue = new \Illuminate\Queue\Capsule\Manager;
$database->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'db_name',
'username' => 'username',
'password' => '',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]);
$queue->addConnector('database', function () use ($database) {
$connection = $database->getConnection();
$connectionResolver = new \Illuminate\Database\ConnectionResolver(['default' => $connection]);
$connectionResolver->setDefaultConnection('default');
return new \Illuminate\Queue\Connectors\DatabaseConnector($connectionResolver);
});
$queue->addConnection([
'driver' => 'database',
'table' => 'jobs_table',
'queue' => 'default',
'retry_after' => 90,
'after_commit' => false,
]);
$queue->getQueueManager()->push('SendEmail', ['message' => 'test']);

jenssegers laravel-mongodb duplicating records on pagination using skip, take and limit

I have been using https://github.com/jenssegers/laravel-mongodb package for interacting with MongoDB. Whenever I fetch a document without skip(), take() and orderBy(), I do not get any duplicate entries. However when I apply skip(), take() and orderBy() to the document, there are duplicate entries on different pages e.g. page 1 have an object with ID 123 and page 2 also have the same object with ID 123.
Please note that there are no duplicates in Mongo DB.
Here is how I am trying to do this:
$record = Model::where('filter1', $algorithm) ->where('filter2', '>=', $asofdate) ->where('filter2', '<', $nextDay);
$record->project(['_id' => 0])->skip($skip)->take($pageSize)->orderBy('column1', 'desc')->get(['column1','column2']);
I also tried using the raw query provided by the package but the issue is still same.
$record = Tree::raw(function($collection) use ($asofdate, $nextDay) {
return $collection->find( [
"$and" => [
["filter1" => "blind_popularity"],
["filter2" => ["$gte" => $asofdate]],
["filter2" => ["$lt" => $nextDay]],
["filter1" => ["$in" => [22]]] ]
],
[ "column1" => 1, "column2"=>1, "column3"=>1, "column4"=>1,
"column5"=>1, "_id"=>0 ],
[ "skip" => 0, "limit" => 15, "sort" => ["column1"=>1]
]);
});
Anybody have interaction with problem like this ?, share your thoughts to solve it.

Laravel and Spatie Permissions plugin update not working

I've performed a long-overdue update on a Laravel project from v5.7 (with Spatie Permissions 2.21) to v9 with Spatie 5.5.0. I'm not getting any error but the hasRole() function no longer ever returns true for users who definitely have the role. Echoing Auth::user()->getRoleNames() for the user just returns an empty array. Any guidance would be greatly appreciated.
Looking at my old commits, it seems that aside from the composer.json additions and database migrations, that only things I needed to do were a User model edit:
...
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
...
And this config/permission.php (comments stripped):
<?php
return [
'models' => [
'permission' => Spatie\Permission\Models\Permission::class,
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
'roles' => 'roles',
'permissions' => 'permissions',
'model_has_permissions' => 'model_has_permissions',
'model_has_roles' => 'model_has_roles',
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
'role_pivot_key' => null, //default 'role_id',
'permission_pivot_key' => null, //default 'permission_id',
'model_morph_key' => 'model_id',
'team_foreign_key' => 'team_id',
],
'register_permission_check_method' => true,
'teams' => false,
'display_permission_in_exception' => false,
'display_role_in_exception' => false,
'enable_wildcard_permission' => false,
'cache' => [
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
'key' => 'spatie.permission.cache',
'store' => 'default',
],
];
Below few things i would like to try
$user->assignRole($this->roles)
Try echo below line just after assign
echo $user()->getRoleNames()
Also try fetch with relations
Dump($user->with('roles')->get()
It will tell you atleast roles assignment is working.
So it turns out this was connected to another problem I was having that fortunately I was trying to fix at the same time:
Laravel upgrade broke model paths
The cause of this permissions issue was in the database but not table / field names, but actually the field contents.
In the model_has_roles table, the old App\User namespace was used (hopefully I'm using "namespace" correctly there!) and should have been App\Models\User in line with the new namespace. Then it all worked fine.

Laravel 5.6. How to test JSON/JSONb columns

$this->assertDatabaseHas() not working with JSON/JSONb columns.
So how can I tests these types of columns in Laravel?
Currently, I have a store action. How can I perform an assertion, that a specific column with pre-defined values was saved.
Something like
['options->language', 'en']
is NOT an option, cause I have an extensive JSON with meta stuff.
How can I check the JSON in DB at once?
UPD
Now can be done like that.
I have solved it with this one-liner (adjust it to your models/fields)
$this->assertEquals($store->settings, Store::find($store->id)->settings);
Laravel 7+
Not sure how far back this solution works.
I found out the solution. Ignore some of the data label, Everything is accessible, i was just play around with my tests to figure it out.
/**
* #test
*/
public function canUpdate()
{
$authUser = UserFactory::createDefault();
$this->actingAs($authUser);
$generator = GeneratorFactory::createDefault();
$request = [
'json_field_one' => [
'array-data',
['more-data' => 'cool'],
'data' => 'some-data',
'collection' => [
['key' => 'value'],
'data' => 'some-more-data'
],
],
'json_field_two' => [],
];
$response = $this->putJson("/api/generators/{$generator->id}", $request);
$response->assertOk();
$this->assertDatabaseHas('generators', [
'id' => $generator->id,
'generator_set_id' => $generator->generatorSet->id,
// Testing for json requires arrows for accessing the data
// For Collection data, you should use numbers to access the indexes
// Note: Mysql dose not guarantee array order if i recall. Dont quote me on that but i'm pretty sure i read that somewhere. But for testing this works
'json_field_one->0' => 'array-data',
'json_field_one->1->more-data' => 'cool',
// to access properties just arrow over to the property name
'json_field_one->data' => 'some-data',
'json_field_one->collection->data' => 'some-more-data',
// Nested Collection
'json_field_one->collection->0->key' => 'value',
// Janky way to test for empty array
// Not really testing for empty
// only that the 0 index is not set
'json_field_two->0' => null,
]);
}
Note: The below solution is tested on Laravel Version: 9.x and Postgres version: 12.x
and the solution might not work on lower version of laravel
There would be two condition to assert json column into database.
1. Object
Consider Object is in json column in database as shown below:
"properties" => "{"attributes":{"id":1}}"
It can assert as
$this->assertDatabaseHas("table_name",[
"properties->attributes->id"=>1
]);
2. Array
Consider array is in json column as shown below:
"properties" => "[{"id":1},{"id":2}]"
It can assert as
$this->assertDatabaseHas("table_name",[
"properties->0->id"=>1,
"properties->1->id"=>2,
]);
Using json_encode on the value worked for me:
$this->assertDatabaseHas('users', [
'name' => 'Gaurav',
'attributes' => json_encode([
'gender' => 'Male',
'nationality' => 'Indian',
]),
]);

laravel seeder array to string conversion

I have a model factory like this
$factory->define(App\Sale::class, function (Faker\Generator $faker) {
return [
'unit' => $faker->randomDigit,
'street_no' => $faker->randomDigit,
'street_name' => $faker->streetName,
'street_type' => $faker->streetSuffix,
'suburb' => $faker->randomElements(['Melton South','Melton West','Rye']),
'postcode' => $faker->numberBetween($min=1000, $max=4000),
'sale_date' => $faker->dateTimeThisYear,
];
});
Database seeder runs it
factory(App\Sale::class, 5)->create();
The problem is when i run it php artisan db:seed I'm getting the error
[Illuminate\Database\QueryException]
Array to string conversion (SQL: insert into `sales` (`unit`, `street_no`,
`street_name`, `street_type`, `suburb`, `postcode`, `sale_date`,
`updated_at`, `created_at`)
values (7, 1, Labadie Centers, Bridge, Rye, 3758, 2016-08-12 1
5:02:07, 2017-05-23 13:16:56, 2017-05-23 13:16:56))
The error sql doesnt show any arrays that i can see.
When i pasted that sql into my db app and ran it, in order to make it work i had to quote all the strings, but laravel docs dont say anything about that when using faker ?
Am I missing something in the model factory ?
exi
Try to change
'suburb' => $faker->randomElements(['Melton South','Melton West','Rye']),
to
'suburb' => $faker->randomElement(['Melton South','Melton West','Rye']),
Note 's' on Elements

Resources