Laravel returns error max prepared statements after getting many count()'s - mysql

I am getting the following error from Laravel.
SQLSTATE[42000]: Syntax error or access violation: 1461 Can't create
more than max_prepared_stmt_count statements (current value: 16382)
(SQL: select count(*) as aggregate from guild)
I have tried removing the guild count statement, but this makes the next SQL query give an error.
$users = User::count();
$crowdfunding = CrowdfundingSettings::find(1);
$guilds = Guild::count();
$data = [
'totalItems' => ItemTemplate::totalCustomItems(),
'totalAbilities' => Ability::totalCustom(),
'totalMobs' => MobTemplate::all()->count(),
'totalQuests' => Quest::all()->count(),
'totalLootTables' => LootTable::all()->count(),
'totalMerchantTables' => MerchantTable::all()->count(),
'totalDialogue' => Dialogue::all()->count(),
'totalCraftingRecipes' => CraftingRecipe::all()->count(),
'totalItemSets' => ItemSetProfile::all()->count(),
'totalSkills' => Skill::totalCustom()
];
return response()->json([
'crowdfunding_settings' => $crowdfunding,
'accounts' => $users,
//'guilds' => $guilds,
'data' => $data,
]);
I am expecting results to from the statement, yet I get an error. I have increased max prepared statements to 32k, but I still get this error displaying it is set to 16k.

As Dimitri mentioned (and also confirmed here) you should not use ->all() when you only need the count and not the data in the model itself.
Replace $data like so:
$data = [
'totalItems' => ItemTemplate::totalCustomItems(),
'totalAbilities' => Ability::totalCustom(),
'totalMobs' => MobTemplate::count(),
'totalQuests' => Quest::count(),
'totalLootTables' => LootTable::count(),
'totalMerchantTables' => MerchantTable::count(),
'totalDialogue' => Dialogue::count(),
'totalCraftingRecipes' => CraftingRecipe::count(),
'totalItemSets' => ItemSetProfile::count(),
'totalSkills' => Skill::totalCustom()
];
Counting the amount of mob templates using MobTemplate::all()->count() will result in the following sql SELECT * FROM mob_template;. The results of that will be loaded into a Eloquent collection, that collection will then count the items it contains in PHP. This is very slow, memory heavy and as it turns out you might also run into issues with prepared statements.
Count the amount of mob templates using MobTemplate::count() will result in the following sql SELECT COUNT(*) FROM mob_template;. This means the database does all the heavy lifting for counting the records and it only returns the result. That way eloquent does not have to load a collection with a bunch of data and it does not have to count all its items in PHP.

Make sure you're disposing PreparedQuery or any other prepared SQL Command after execution. Disposing calls ClosePreparedStatement.

Related

Multiple Fields with a GroupBy Statement in Laravel

Already received a great answer at this post
Laravel Query using GroupBy with distinct traits
But how can I modify it to include more than just one field. The example uses pluck which can only grab one field.
I have tried to do something like this to add multiple fields to the view as such...
$hats = $hatData->groupBy('style')
->map(function ($item){
return ['colors' => $item->color, 'price' => $item->price,'itemNumber'=>$item->itemNumber];
});
In my initial query for "hatData" I can see the fields are all there but yet I get an error saying that 'colors', (etc.) is not available on this collection instance. I can see the collection looks different than what is obtained from pluck, so it looks like when I need more fields and cant use pluck I have to format the map differently but cant see how. Can anyone explain how I can request multiple fields as well as output them on the view rather than just one field as in the original question? Thanks!
When you use groupBy() of Laravel Illuminate\Support\Collection it gives you a deeper nested arrays/objects, so that you need to do more than one map on the result in order to unveil the real models (or arrays).
I will demo this with an example of a nested collection:
$collect = collect([
collect([
'name' => 'abc',
'age' => 1
]),collect([
'name' => 'cde',
'age' => 5
]),collect([
'name' => 'abcde',
'age' => 2
]),collect([
'name' => 'cde',
'age' => 7
]),
]);
$group = $collect->groupBy('name')->values();
$result = $group->map(function($items, $key){
// here we have uncovered the first level of the group
// $key is the group names which is the key to each group
return $items->map(function ($item){
//This second level opens EACH group (or array) in my case:
return $item['age'];
});
});
The summary is that, you need another loop map(), each() over the main grouped collection.

cakephp 3 (My)SQL aggregate function max() syntax error near 'AS MAX(`users`__`date_time`) AS `date`

I'm developing a social application with cakephp 3.
I'm working on the messanging function right now. It works this way, that on the left side one gets displayed the users, who one has conversations with and in the middle is the conversation.
I want the users to be sorted by last messaging date. Meaning, if Peter was the last one I wrote to, then Peter should appear on top and so on.
I have the following query:
$query = $this->Messages
->find('all', [
'fields' => [
'MAX(`messages`.`max_date`)',
'id',
'sender_id',
'reciever_id',
]
])
->where([
'sender_id = ' => $active_user_id
])
->orWhere([
'reciever_id = ' => $active_user_id
])
->group([
'sender_id',
'reciever_id'
])
->order([
'id' => 'DESC'
]);
For performance reasons I added a grouping by sender_id and reciever_id, since I don't want to recieve all messages, but only find the users, that I wrote with so far.
The problem is that the group-statement destroys the sorting. So I thought about a max-aggregate-function to preserve the order.
However I get the following error-message by the cakephp framework:
Error: 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 'MAX(`messages`__`max_date`), Messages.id AS `Messages__id`, Messages.sender_id A' at line 1
So does anyone know, what is wrong with my query? I gave my best to follow the example in the documentation.
Why does it say messages__max_date and not messages.max_date?
That's simply not how you define computed fields and correspondig alias, this needs to be done in a key => value fashion, ie
'fields' => [
'max_date' => 'MAX(`Messages`.`date_time`)',
// ...
]
which would result in an SQL fragment like
MAX(`Messages`.`date_time`) AS `max_date`
See Cookbook > Database Access & ORM > Query Builder > Selecting Data
Also as a tip, you might want to use expressions instead of simple strings, so that CakePHP can properly create platform specific SQL in case required. MAX isn't affected AFAIR, so this is just a general hint.
$query = $this->Messages->find();
$query
->select([
'max_date' => $query->func()->max($this->Messages->aliasField('date_time'))
// ...
])
// ...
See Cookbook > Database Access & ORM > Query Builder > Using SQL Functions

Trying to combine and/or arguments for MySQL query

I am attempting to query a MYSQL table with three 'scenarios' for finding objects. While I have successfully broken these into three separate queries I feel there has to be a 'better and faster' way to sift through the data. However, when I combine like below, I do not find any objects matching the query. This is using xPDO within MODx. The failed attempt is immediately below:
$orders=$modx->newQuery('Orders');
$orders->where(array(
array( //scenario #1
'Orders.start_date:<=' => $rentalDate->end_date,
'AND:Orders.start_date:>=' => $rentalDate->start_date
),
array( //scenario #2
'OR:Orders.end_date:<=' => $rentalDate->end_date,
'AND:Order.start_date:>=' => $rentalDate->start_date
),
array( //scenario #3
'OR:Orders.end_date:>=' => $rentalDate->start_date,
'AND:Orders.end_date:<=' => $rentalDate->end_date
)
));
$conflictingOrders = $modx->getCollection('Orders',$orders);
However, if I run each scenario separately, it does pick up the objects correctly. Example:
$s1Query=$modx->newQuery('Orders');
$s1Query->where(array(array('Orders.start_date:<=' => $rentalDate->end_date,'AND:Orders.start_date:>=' => $rentalDate->start_date)));
$s1Results=$modx->getCollection('Orders',$s1Query);
Any ideas where I am going wrong in the first code? Please let me know if any further information is needed. Cheers!
Helpful doc:http://rtfm.modx.com/xpdo/2.x/class-reference/xpdoquery/xpdoquery.where
The array scenarios in your code are being treated as AND conditions when listed in the $orders->where() method.
Try this:
$orders = $modx->newQuery('Orders');
$orders->where(array(
'Orders.start_date:<=' => $rentalDate->end_date,
'AND:Orders.start_date:>=' => $rentalDate->start_date
));
$orders->orCondition(array( //scenario #2
'Orders.end_date:<=' => $rentalDate->end_date,
'AND:Order.start_date:>=' => $rentalDate->start_date
));
$orders->orCondition(array( //scenario #3
'Orders.end_date:>=' => $rentalDate->start_date,
'AND:Orders.end_date:<=' => $rentalDate->end_date
));
// uncomment the following lines to see the raw query generated
// $orders->prepare();
// print_r($orders->toSql());
$conflictingOrders = $modx->getCollection('Orders',$orders);

How to generate a MySQL IS NOT NULL condition in CakePHP?

I'm trying to get a subset of results as a virtualField for use in my view. I may even be way off on how I'm approaching this, but here's what I've done so far:
I started with this question here: CakePHP virtualField find all not null which lead to this little beauty.
Now I have an issue where the find statement passing (Array) into the MySQL.
My code looks like:
class Transaction extends AppModel {
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->virtualFields['Accounts'] = $this->find("all", array("conditions" => array("account !=" => null)));
}
And I'm seeing:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Array' in 'field list'
SQL Query: SELECT `Transaction`.`id`, `Transaction`.`name`,
`Transaction`.`person_id`, `Transaction`.`account`, (Array)
AS `Transaction__Accounts` FROM `my_database`.`transactions`
AS `Transaction` WHERE `Transaction`.`person_id` = (2)
I've also tried $this->Transaction->find and "Transaction.account !=", to no avail. I've found some other issues with the (Array) but none that help my situation. Any pointers in the right direction would be great.
Problem: your query results are an array, and you're telling SQL to assign a field name to each query result containing that array - virtual fields are only made to contain single level variables like strings.
Solution: use a join structure onto itself with those conditions which will return a nested result set along with each of your results. Use CakePHP's model relationships to do this:
<?php
class Transaction extends AppModel {
var $hasMany = array(
'Accounts' => array(
'className' => 'Transaction',
'foreignKey' => false,
'conditions' => array('Accounts.account IS NOT NULL')
)
);
}
?>
Example output:
Array(
'Transaction' => array( // transaction data),
'Accounts' => array( // associated transaction data with account set to null
)
Now, as you can probably gather from that result, if you return 1000 rows from Transaction, you'll get all results from Accounts nested into each Transaction result. This is far from ideal. From here, you can either make the join conditions more specific to target relevant Accounts records, or this is not the right approach for you.
Other approaches could be:
Accounts model, uses Transaction database table, implicit find conditions are that account is null
Manual query to retrieve these results in the afterFind() method of your Transaction model, which will retrieve these results once, and you'll then return array_merge($accounts, $transactions)

Codeigniter/Mysql: Column count doesn't match value count with insert_batch()?

Alright, so i have a huge list (like 500+) of entries in an array that i need to insert into a MySQL database.
I have a loop that populates an array, like this:
$sms_to_insert[] = array(
'text' => $text,
'contact_id' => $contact_id,
'pending' => $status,
'date' => $date,
'user_id' => $this->userId,
'sent' => "1"
);
And then i send it to the database using the built insert_batch() function:
public function add_sms_for_user($id, $sms) {
//$this->db->delete('sms', array("user_id" => $id)); Irrelevant
$this->db->insert_batch('sms', $sms); // <- This!
}
The error message i get is as follows:
Column count doesn't match value count at row 1.
Now, that doesn't make sense at all. The columns are the same as the keys in the array, and the values are the keys value. So, why is it not working?
Any ideas?
user_id turned out to be null in some situations, that's what caused the error.
EDIT: If you replace insert_batch() with a loop that runs insert() on the array keys you will get more clear error messages.