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

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)

Related

Eloquent where clause to use related model's column when using with() -laravel 4

I have 2 models
Truck
class Truck extends \Eloquent {
// Add your validation rules here
public static $rules = [
'trucktype_id' => 'required',
'weight'=> 'required',
'truck_no'=> 'required'
];
// Don't forget to fill this array
protected $fillable = ['trucktype_id','weight','picture_path','remarks','truck_no'];
public function TruckType(){
return $this->belongsTo('TruckType','trucktype_id');
}
}
TruckType
class Trucktype extends \Eloquent {
// Add your validation rules here
public static $rules = array(
'type' => 'required|unique:trucktypes,type',
'max_weight' => 'required'
);
// Don't forget to fill this array
protected $fillable = ['type','max_weight'];
}
I need to lookup related table records i.e TruckType
$trucksobj = Truck::with('TruckType');
if($truck_no!="")
$trucksobj->where("truck_no",'=',$truck_no);
if($start_date!="" && $end_date!="")
$trucksobj->whereBetween('created_at', array($start_date, $end_date));
if($truck_type!="")
$trucksobj->where("trucktype_id",'=',$truck_type);
if($overweight=="on")
$trucksobj->where('TruckType.max_weight', '>=', 0);
But the above query didnt resolve TruckType.max_weight and throws following error
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'TruckType.max_weight' in 'where clause' (SQL: select count(*) as aggregate from trucks where TruckType.max_weight >= 0)
I think you misunderstand how with() actually works. It is only used to alleviate the N+1 query problem, and does not make the contents of the table available for querying. After your first query has ran to select all of the trucks, the with() simply causes the following query to be automatically ran:
select * from TruckType where TruckType.id in (...)
Here the list at the end will contain all of the different truck.trucktype_id values that were found in your first query, and then they'll automatically be available for you to use via $truck->TruckType->{property} etc.
Now, if you actually have a look at the query that's being generated for you, you can clearly see that there is no TruckType table referenced anywhere:
select count(*) as aggregate from trucks where TruckType.max_weight >= 0
This is why the error is being thrown.
You have two options:
(1) Use a join
$trucksobj = Truck::with('TruckType')->join('TruckType', 'truck.trucktype_id', '=', 'TruckType.id')->where('TruckType.max_weight', '>=', 0);
(2) Use whereHas() to place a constraint on your relationship
$trucksobj = Truck::with('TruckType')->whereHas('TruckType', function($q) {
$q->where('max_weight', '>=', 0);
});
If you don't actually need to know anything about the truck type, and you only want to use it to sieve through the trucks, then you can get rid of with('TruckType') and just keep the rest of the query.

Query to find where associated model (has many) is empty in cakephp

I have a model named Application. And Application is associated to has_many model named Location.
Application has many Location
In my Application query:
$this->Application->find('all', array('conditions' => 'Application.status' => 'accepted'));
I'm finding applications where status is accepted.
Next thing that I would like to achieve is to find Application records where associated Location is empty/null or in other words where count of Location records is 0.
I tried to make join query like this:
$join_query = array(
'table' => 'locations',
'alias' => 'Location',
'type' => 'INNER',
'conditions' => array(
'Location.application_id = Application.id',
'OR' => array(
array('Location.id' => NULL)
)
)
);
But seems like it's just querying Application records that do have associated Location records.
Thanks in advanced if you guys have any idea(s).
You need to use a left join, not an inner join. Inner join will get only those results that have a row in both of the tables you are joining, where you want only results where there is only a row in the left table. Left joins will get all the results in the left table, regardless if there's a row associated with it in the right table. Then add a condition after the join is complete, to only select those joined results where Location.id is null.
$this->Application->find('all',
array(
'conditions' => array('Location.id' => null),
'joins' => array(
array(
'table' => 'locations',
'alias' => 'Location',
'type' => 'LEFT',
'conditions' => array('Location.application_id = Application.id')
),
),
)
);
Your query says "find any application and its location with application_id = id, AND (1 OR where location.id = null)", so that will match any application that has location.
What I'd do is to leave joins and just use containable and counts. With plain sql I'd use a left join and count the Locations, like in this example. But cake doesn't behave well with not named columns, like "COUNT(*) AS num_locations", so I tend to avoid that.
I'd transform your query to a containtable one
$apps = this->Application->find('all', array('contains'=>'Location'));
foreach($apps as $app) {
if (count($app['Location']) <= 0)
//delete record
}
You could also implement a counterCache, and keep in a BD column the number of locations per application, so the query can be a simple find like
$this->Application->find('all', array('conditions'=>array('location_count'=>0)));
Ooooor, you could add a virtual field with "SUM(*) as num_locations" and then use your join with "left outter join" and compare "num_locations = 0" on the conditions.
Those are the options that comes to mind. Personally I'd use the first one if the query will be a one time/not very used one. Probably put it in the Application model like
public function findAppsWithNoLocations() {
$apps = this->Application->find('all', array('contains'=>'Location'));
foreach($apps as $app) {
if (count($app['Location']) <= 0)
//delete record
}
}
But the other two options would be better if the sum of locations per app is going to be a recurrent query you'll search for.
EDIT
And of course Kai's answer options that does what you want xD. This tendency to complicate things will be the end of me... Well, will leave the answer here to show a reference to other convoluted options (specifically counterCache if you'll need to count the relations a lot of times).
i know this is already some time ago.
i could manage it this way:
public function getEmpty($assoc) {
foreach($this->find('all') as $c){
if(empty($c[$assoc])) $return[] = $c;
}
return $return;
}
now i got all entries that have an empty associated data.
in my controller i call the function like this:
$ce = $this->Company->getEmpty('CompaniesUsers');
companies Users is the Empty Associated model i want to check.

CakePHP2.3: Building complex conditional Model->find() options

I'm not savvy with MySQL or databases generally, so here's a model of my data (table{cols}]) in order to make my question coherent:
Domains{id, name} Note: 'domains' here does not refer to web domains
Subdomains{id, domain_id, name}
Items{id, subdomain_id, name}
SubdomainsItems{id, subdomain_id, item_id} no domain_id column!
My Items Controller has a function, fetchWithin($domains, $subdomains) which, ultimately, should just execute one of two complexish find(). It's the complexish I can't get past.
Programmatically I can achieve this, but I'm quite certain the better way is by clever joins and the like. Alas, currently this is approach:
If $domainsis empty, do only steps 2&3, otherwise:
foreach($domains as $d): get all the rows of Subdomains where Subdomain.domain_id = Domains.id as $subdomains
foreach($subdomains as $s) : go get all the rows of SubdomainsItems where SubdomainsItems.subdomain_id = Subdomains.id as $item_ids
foreach($items_ids as $i): get all the rows of Items where Items.id = SubdomainsItems.items_id
This works, but I think this is obviating the power of a relational database and I'd like to understand how this should be done (ie. according to either Cakephp convention or simply by whatever MySQL statement would achieve this).
Help would be hugely appreciated, I try to learn the more complex aspects of SQL but it just goes right over my head. :S
Understanding the necessary query
With the structure described in the question the kind of query necessary is of the form:
SELECT
*
FROM
items
LEFT JOIN
subdomains ON (
items.subdomain_id = subdomains.id
)
LEFT JOIN
domains ON (
subdomains.domain_id = domains.id
)
WHERE
domains.name = "foo"
AND
subdomains.name IN ('some', 'list', 'of', 'subdomains');
Compared to the logic in the question this joins all three tables together and permits finding all items by domain name, or subdomain name (or any other criteria involving any or all three tables); Generally speaking if you want to find data in a db and use more than one query to get it - there's a more efficient way to do it.
Implementing the find call
There are a number of ways of creating such a query with Cake. The simplest, probably, is to use the join key and just specify the joins explicitly:
function fetchWithin($domains = null, $subdomains = null) {
$params = array(
'joins' => array(
array('table' => 'subdomains',
'alias' => 'Subdomain',
'type' => 'LEFT',
'conditions' => array(
'Subdomain.id = Item.subdomain_id',
)
),
array('table' => 'domains',
'alias' => 'Domain',
'type' => 'LEFT',
'conditions' => array(
'Domain.id = Subdomain.domain_id',
)
)
)
);
if ($domains) { // single value or an array
$params['conditions']['Domain.name'] = $domains;
}
if ($subdomains) { // single value or an array
$params['conditions']['Subdomain.name'] = $subdomains;
}
return $this->find('all', $params);
}

CakePhp mysql raw query error

I'm new to cakephp. I'm trying to search through mysql tables. I want to use nested query.
class TableController extends AppController{
.
.
public function show(){
$this->set('discouns', $this->DiscounsController->query("SELECT * FROM discoun as Discoun WHERE gcil_id = 1"));//(SELECT id FROM gcils WHERE genre = 'Shoes' AND company_name = 'Adidas')"));
}
}
Error:
Error: Call to a member function query() on a non-object
I've also tried
public function show(){
$this->DiscounsController->query("SELECT * FROM count as Count WHERE ctr_id = (SELECT id FROM ctrs WHERE genre = 'Shoes' AND company_name = 'Adidas')");
}
Error:
Error: Call to a member function query() on a non-object
File: C:\xampp\htdocs\cakephppro\myapp\Controller\CountsController.php
Please help. I've been trying this for last few hours. :/
As mentioned in the comments there are a few problems with your code.
Firstly, you are trying to call the query() method on a Controller, whereas you should be executing it on a Model, as it is models that handle database queries and the controller should simply be used to call these methods to get the data and pass them to the view.
The second thing is that you are executing a very simple SQL query raw instead of using CakePHPs built in functions <- Be sure to read this page in full.
Now for your problem, as long as you have setup your model relationships correctly and followed the correct naming conventions, this should be your code to run your SQL query from that controller:
public function show(){
$this->set('discouns', $this->Discouns->find('all', array(
'conditions' => array(
'gcil_id' => 1,
'genre' => 'shoes',
'company_name' => 'Adidas'
)
));
}
query() is not a Controller, but a Model method. That's what the error (Call to a member function on a non-object) is trying to tell you.
So the correct call would be:
$this->Discount->query()
But you are calling this in a TableController, so unless Table and Discount have some type of relationship, you won't be able to call query().
If the Table does have a relationship defined you will be able to call:
$this->Table->Discount->query()
Please not that query() is only used when performing complex SQL queries in scenarios where the standard methods (find, save, delete, etc.) are less practical.
$this->Counts->find('all',array(
'conditions' => array(
'ctrs.genre' => 'Shoes',
'ctrs.company_name' => 'Adidas'
), 'recursive' => 1
));
The above is with tables named counts and ctrs.
This is assuming you have the model set up to have some sort of relationship between the counts table and the ctrs table. It's kind of hard to tell in your code exactly what you tables are.
The CakePHP book should have all the answers you need. One of the reasons to run CakePHP over regular PHP is the FIND statement. Once you have your models set up correctly, using the find statement should be really easy.
http://book.cakephp.org/2.0/en/models.html

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.