Complex AR query, where clause one column equal to another column's value? - mysql

I've written up the AR query below, but have one problem with a where clause. On the Payout model there is a comp_builder_id and there's also a comp_builder_id on a ContractLevel.
I want to add a where clause to this query to get the data where the Payout model's comp_builder_id is equal to the ContractLevel model's comp_builder_id.
payouts = Payout.includes(:comp_builder => {
:contract_levels => {
:transmittal => [
{:appointment_hierarchies => :child},
:appointment_cases
]
}
})
.includes(:product)
.includes(:payor)
.where(products: { :id => self.product.id })
.where(appointment_cases: { :case_id => self.id })
.where(transmittals: { :id => appointment_case.transmittal_id })
.where(contract_levels: { :transmittal_id => appointment_case.transmittal_id,
:appointment_id => Transmittal.find(appointment_case.transmittal_id).low,
:comp_builder_id => # ? payout's comp builder id ? })
How can this be done in Active Record? In the last few where clauses I'm referencing different variables, those can be ignored as this query is just a snippet.

You can pass a string into the where statement:
User.include(:parent).where('users.parent_id = parents.id')
Basically if you know what you want the where statement to look like in raw SQL then you can just pass it in as a string into the where statement.
That is the only way I know of to make complex where statements in ActiveRecord.
Just make sure to remember that you need to reference the column names, not the model names.

Related

How can I compare 2 columns in the same table but not in the same row with Laravel?

I want to display the values of columns with the same menu_id and the same menu_parentid. However, the array is empty when I execute in Postman. I want the values of columns with common menu_id and menu_parentid displayed in an array.
Controller
public function showMenu()
{
return [
'menus' => Menu::whereColumn('menu_parentid', 'menu_id')
->distinct()
->get()
->map(function ($item) {
return [
'menu_id' => $item->menu_id,
'menu_name' => $item->menu_name,
'menu_icon' => $item->menu_icon,
];
})
];
}
When I test on Postman, I get the following
{
"menus": []
}
Screenshot of database
I think you're going about this the wrong way. Instead of doing a query like that, you can make a relationship of the model with itself to get the $menu->children and then perform the array mapping the way you like. I don't want to write it out and guess how you like it to be displayed, so can you elaborate how you would like the array to be structured? Is it like this?:
"menus": [
[parent_id1, child_id1],
[parent_id1, child_id2],
[parent_id2, child_id3]
];
Or is it a different structure where the parent id's are all mixed with the children? I can only give you hints without you clarifying your structure.
You can you have to select a One to many relation in model.
public function childs()
{
return $this->hasMany(Menu::class, 'menu_parentid', 'menu_id');
}
Then, you can select items with this query,
Menu::with('childs')->distinct()->get();

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.

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.

IQueryable Parent-Child

I need to cast: IQueryable<PARENT> to IQueryable<Child>.
IQueryable<PARENT> query = GetParents();
IQueryable<CHILD> result = query.Select(t => t.Children);
This doesn't work, unable to convert EntitySet to IQueryable. Thanks
Use:
IQueryable<CHILD> result = query.SelectMany(t => t.Children);
The query returns all cild items of the parent, childs referenced by multiple parents will be returned multiple times.
To get the distinct rows use - you will have to implement a class that Implements IComparer though, to do a custom filtering. Distinct() will look at the instances and not the values.
IQueryable<CHILD> result = query.SelectMany(t => t.Children)
.Distict(new Comparer<CHILD>());
Distinct() removes duplicats from the query.
Aternatively you can use grouping to create a grouping for the children:
IEnumerable<CHILD> result = query.SelectMany(t => t.Children)
.GroupBy(x => new CHILD {
id = x.id
// add other properties here
})
.Select(g => g.Key);
Instead of new CHILD { } you could also use new { } to create an anonymous result, in this case you should replace IEnumerable<CHILD> with var.
To perform a type cast in Linq you can use - although I would assume that this will not work in you case:
someQuery.OfType<TypeToCastTo>().Cast<TypeToCastTo>();
OfType<T>() filters the items so that Cast<T>() can perform the type cast.