Now I have no validation. I have Model/Tag and tag table.
Controller/TagController
<?php
App::uses('AppController', 'Controller');
/*App::uses('Article', 'Model');
App::uses('Link', 'Model');
App::uses('User', 'Model');*/
// with some model , sql is extending so I fell it commented out is better.
/**
* Tags Controller
*
* #property Tag $Tag
* #property PaginatorComponent $Paginator
*/
class TagsController extends AppController {
public $uses = array(//'Tag','Article','Link','User'
);
$this->Paginator->settings = array(
'conditions'=> array(
"Link.LFrom = $id"
),
'fields' => array( 'Link.*'),
Now cake run this sql
SELECT Link.* FROM db0tagplus.tag AS Tag WHERE Link.LFrom = 2152 LIMIT 20
Table relation is very complexed so I can not use assosiation.
how to generate this sql?
SELECT `Link`.* FROM `Link` WHERE `Link`.`LFrom` = 2152 LIMIT 20
I guess, if I could unload model and table 'Tag' It will be OK.
I cannot solve it with unbind.(bind is asssosiation matter).
App::import and app::use is loading model order, how to unload specified model?
Solved like this
$this->loadModel('Article');
$this->Paginator->settings = array(
'conditions'=> array(
"Link.LTo = Article.ID"
),
'fields' => array('Link.*','taglink.*','Article.*'
),
'joins'
=> array(
array(
'table' => 'Link',
'type' => 'INNER',
'conditions' => array(
array("$id = Link.LFrom")
)
),
array(
'table' => 'Link',
'alias' => 'taglink',
'type' => 'INNER',
'conditions' => array(
array("Link.ID = taglink.LTo"),
array("$trikeyID = taglink.LFrom")
)
),
)
);
debug($this->Paginator->paginate('Article'));
$this->set('results',$this->Paginator->paginate('Link'));
Using below code to make above query:
App::import('model','Link');
$Link = new Link();
$Link->find('all',array('conditions'=>array('LFrom'=>2152),
'limit'=>20,
'recursive'=>-1
));
You have to use below code in order to remove existing binding
// Let's remove the hasMany...
$this->Leader->unbindModel(
array('hasMany' => array('ModelName'))
);
For reference: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#creating-and-destroying-associations-on-the-fly
Related
I have employees who have one employer. In my employees table I have a field called employer_id. A report using this information is working on my local development setup, but not on my web hosting. I can't figure out what might be causing it not to.
When I try to pull up a list of all the employees I get the following error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'Employer.last_name' in 'where clause'
SQL Query: SELECT Employee.id, Employee.employer_id,
Employee.first_name, Employee.last_name, Employee.email,
Employee.webinar_id, Employee.questions,
Employee.correct_answers, Employee.required_to_pass,
Employee.status, Employee.role, Employee.sport,
Employee.program, Employee.created, Employee.modified FROM
sportrisk_wp.employees AS Employee WHERE
((Employee.last_name NOT LIKE '') AND (Employer.last_name NOT
LIKE '')) AND contain = '1' ORDER BY Employee.created DESC LIMIT
50
On my local copy when I change debug to 2 I see the following in the SQL log (it's joining the Employer):
SELECT Employee.id, Employee.employer_id,
Employee.first_name, Employee.last_name, Employee.email,
Employee.webinar_id, Employee.questions,
Employee.correct_answers, Employee.required_to_pass,
Employee.status, Employee.role, Employee.sport,
Employee.program, Employee.created, Employee.modified,
Employer.id, Employer.employer_id, Employer.company,
Employer.first_name, Employer.last_name, Employer.phone,
Employer.email, Employer.username, Employer.password,
Employer.usercode, Employer.subscriber, Employer.active,
Employer.admin, Employer.created, Employer.modified,
Employer.status FROM local.employees AS Employee LEFT JOIN
local.users AS Employer ON (Employee.employer_id =
Employer.id) WHERE ((Employee.last_name NOT LIKE '') AND
(Employer.last_name NOT LIKE '')) ORDER BY Employee.created
DESC LIMIT 50
I believe this is all the relevant code.
UsersController
// inside a larger function
// Load Employee Model
$this->loadModel('Employee');
// Base conditions - to hide some old records which do not contain name information.
$conditions = array(
"Employee.last_name NOT LIKE" => '',
"Employer.last_name NOT LIKE" => ''
);
// Find records & paginate
$this->paginate = array(
'limit' => 50,
'conditions' => array(
"AND" => $conditions
),
'order' => array('Employee.created DESC')
);
$employees = $this->paginate('Employee');
?>
Employee Model
<?php
class Employee extends AppModel
{
var $name = 'Employee';
var $recursive = 1;
var $belongsTo = array (
'Employer' => array (
'className' => 'Employer',
'foreignKey' => 'employer_id',
'order' => 'Employee.last_name ASC'
)
);
public $actsAs = array('Containable');
var $validate = array(
'email' => array(
'rule' => 'notBlank'
),
'first_name' => array(
'rule' => 'notBlank'
),
'last_name' => array(
'rule' => 'notBlank'
),
'usercode' => array(
'rule' => 'notBlank'
)
);
}
?>
Employer Model
<?php
class Employer extends AppModel
{
var $name = 'Employer';
var $recursive = -1;
var $hasOne = 'Supervisor';
var $hasMany = 'Employee';
public $useTable = 'users';
public $actsAs = array('Containable');
var $validate = array(
'email' => array(
'rule' => 'notBlank'
),
'password' => array(
'rule' => 'notBlank'
)
);
}
?>
I can't figure out why it isn't linking the Employer model in one environment but is on the other. I've done a straight copy of all the files.
SQL Queries
This is the query I get on the hosted site
SELECT `Employee`.`id`, `Employee`.`employer_id`, `Employee`.`first_name`, `Employee`.`last_name`, `Employee`.`email`, `Employee`.`webinar_id`, `Employee`.`questions`, `Employee`.`correct_answers`, `Employee`.`required_to_pass`, `Employee`.`status`, `Employee`.`role`, `Employee`.`sport`, `Employee`.`program`, `Employee`.`created`, `Employee`.`modified` FROM `sportrisk_wp`.`employees` AS `Employee` WHERE ((`Employee`.`last_name` NOT LIKE '') AND (`Employer`.`last_name` NOT LIKE '')) ORDER BY `Employee`.`created` DESC LIMIT 50
This is what I get on my local site (and what I'm looking for)
SELECT `Employee`.`id`, `Employee`.`employer_id`, `Employee`.`first_name`, `Employee`.`last_name`, `Employee`.`email`, `Employee`.`webinar_id`, `Employee`.`questions`, `Employee`.`correct_answers`, `Employee`.`required_to_pass`, `Employee`.`status`, `Employee`.`role`, `Employee`.`sport`, `Employee`.`program`, `Employee`.`created`, `Employee`.`modified`, `Employer`.`id`, `Employer`.`employer_id`, `Employer`.`company`, `Employer`.`first_name`, `Employer`.`last_name`, `Employer`.`phone`, `Employer`.`email`, `Employer`.`username`, `Employer`.`password`, `Employer`.`usercode`, `Employer`.`subscriber`, `Employer`.`active`, `Employer`.`admin`, `Employer`.`created`, `Employer`.`modified`, `Employer`.`status` FROM `local`.`employees` AS `Employee` LEFT JOIN `local`.`users` AS `Employer` ON (`Employee`.`employer_id` = `Employer`.`id`) WHERE ((`Employee`.`last_name` NOT LIKE '') AND (`Employer`.`last_name` NOT LIKE '')) ORDER BY `Employee`.`created` DESC LIMIT 50
END RESULT
In the end (after a chat with Ved) my paginate array ended up being this, and my report loads.
$this->paginate = array(
'contain' => array('Employer'),
'limit' => 50,
'conditions' => array(
"AND" => $conditions
),
'joins' => array( array( 'alias' => 'Employer', 'table' => 'users', 'type' => 'LEFT', 'conditions' => 'Employer.id = Employee.employer_id' ) ),
'fields' => array('Employee.first_name', 'Employee.last_name', 'Employee.email', 'Employee.webinar_id', 'Employee.questions', 'Employee.correct_answers', 'Employee.required_to_pass', 'Employee.status', 'Employee.role', 'Employee.sport', 'Employee.program', 'Employee.created', 'Employer.id', 'Employer.employer_id', 'Employer.company', 'Employer.first_name', 'Employer.last_name', 'Employer.email', 'Employer.active'),
'order' => array('Employee.created DESC')
);
I think you have forget to load associated data, use contain
// Find records & paginate
$this->paginate = array(
'contain' => array('Employer'),
'fields' => array('Employer.id'), // add more fields
'limit' => 50,
'conditions' => array(
"AND" => $conditions
),
'joins' => array( array( 'alias' => 'Employer', 'table' => 'users', 'type'
=> 'LEFT', 'conditions' => 'Employer.id = Employee.employer_id' ) ),
'order' => array('Employee.created DESC')
);
I would like to bind models, mostly because the datasources change from one model to the other. Let's say I have the following query :
SELECT ext.col1, ext.col2, ext.col3, COUNT(*) Link Count
FROM `external.`Media` ext
INNER JOIN `internal`.`links` l ON ext.someId = links.id
INNER JOIN `internal`.tags t ON l.tag_id = t.tag_id
WHERE t.parent_tag_id = 1098
AND t.metadata = 'someValue'
GROUP BY ext.col1, ext.col2, ext.col3
So I have 3 models from which data is coming from. Currently I have the following in the Media model..
public $belongsTo = array(
'Links' => array(
'className' => 'Links',
'foreignKey' => 'someId',
),
'Tag' => array(
'className' => 'Tag'
)
);
Then in the other models :
Tag Model
public $useTable = "tags";
public $name = 'Tag';
public $hasAndBelongsToMany = array(
'TagLink' => array(
'className' => 'TagLink',
'foreign_key' => 'tag_id',
'conditions' => array(
'Tag.parent_tag_id' => 1098
)
)
);
Link Model
public $useTable = "tag_links";
public $name = 'TagLink';
public $hasAndBelongsToMany = array(
'Tag' => array(
'className' => 'Tag',
'foreign_key' => 'tag_id'
)
);
Then back in the Media model , I use the find expecting to have the model binding set up :
return $this->find("all", array(
'conditions' => array(
'Tag.metadata' => $username
)
));
This generates an error :
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Tag.metadata' in 'where clause'
I am assuming that this is happening because the table is not found
[SQL Generated]
SELECT `Media`.`media_id`, `Media`.`mediatype`, `Media`.`created`, `Media`.`active_flag`,
`Media`.`mediafile`, `Media`.`fallbackfile`, `Media`.`setupId`, `Media`.`externalId`,
`Media`.`width`, `Media`.`height`, `Media`.`brands`, `Media`.`languages`, `Media`.`products`,
`Media`.`projects`, `Media`.`affiliates`, `Media`.`description`, `Media`.`linking_code` FROM
`external`.`ext_media_gallery` AS `Media` WHERE `Tag`.`metadata` = 'testuser' AND
`MediaGallery`.`active_flag` = 'active' AND ((FIND_IN_SET('testuser', `Media`.`affiliates`)) OR
(`Media`.`affiliates` = ''))
This basically shows that the binding is incorrect/not happening
Basically I want to replace the following to avoid using joins on the fly since these would need to apply for every query.. hence the model binding..
This works for me, but I want to convert it to do it using model binding :
return $this->find("all", array(
"joins" => array(
array(
"table" => "internal.links",
"alias" => "Link",
"type" => "INNER",
"conditions" => array(
"Link.id = Media.someId"
)
),
array(
"table" => "internal.tags",
"alias" => "Tag",
"type" => "INNER",
"conditions" => array(
"Tag.tag_id = Link.tag_id",
"Tag.parent_tag_id = 3214",
"Tag.metadata" => $username
)
),
)
));
I have the following Code to get a special model related to another model related to actual object: (I want the Entry-model - in the Rubric-model-view)
$this->paginate = array(
'Entrieslocation' => array(
'conditions' => array('Entrieslocation.rubric_id' => $this->Rubric->id, 'Entrieslocation.subrubric_id' => '0'),
'joins' => array(
array(
'alias' => 'Entry',
'table' => 'entries',
'type' => 'INNER',
'conditions' => 'Entry.id = Entrieslocation.entry_id'
)
)
)
);
Then I thought I could get it by something like this:
$this->set('entries', $this->paginate('Entry'));
But the variable is empty, how can I access the Entries inside the Pagination in the view?
The Query generated by the code above is correct, I checked it by entering the query into phpmyadmin. So how can I access the result (only the Entries)?
Thanks for your help :)
First of all you must define $paginate variable below controller class. For example
class UsersController extends AppController {
public $paginate = array();
}
Then in your action follow following patter to get the paginated result.
$this->paginate = array(
'recursive' => -1,
'limit' => 10,
'conditions' => array('Your conditions'),
'fields' => array('Entry.*, Entrieslocations.*'),
'joins' => array(
array(
'table' => 'Entrieslocations',
'alias' => 'Entrieslocation',
'type' => 'inner',
'conditions' => 'Entry.id = Entrieslocation.entry_id'
),
),
'order' => array(
'Entry.id' => 'desc'
)
);
$entries = $this->paginate('Entry');
To pass result set to the view use following:
$this->set('entries', $entries);
I am in the FilesController and I'm trying to get a file based on the condition that its order belongs to the current user.
FilesController
// Check the file exists and that it belongs to the user
$this->File->find('first', array(
'conditions' => array(
'File.id' => $id,
'Order.Customer.id' => $this->Auth->user('id')
),
'recursive' => 2
));
Cake SQL Error
Unknown column 'Order.Customer.id' in 'where clause'
I'm trying to get the SQL to left join orders onto files and then left join customers onto orders, but I can't figure out how to join the customers table, although I'm sure I've done this before. I've tried everything I can think of with the conditions, and using contains.
Here's my model relationships:
Customer Model
class Customer extends AppModel {
public $hasMany = array('Order');
}
Order Model
class Order extends AppModel {
public $belongsTo = array('Customer');
public $hasMany = array('File');
}
File Model
class File extends AppModel {
public $belongsTo = array('Order');
}
Try joining the tables using the 'joins' parameter. Sometimes 'contains' doesn't work and you need to fall back on this.
$this->File->find('first', array(
'conditions' => array(
'File.id' => $id,
'Order.Customer_id' => $this->Auth->user('id')
),
'joins' => array(
array(
'table' => 'orders',
'alias' => 'Order',
'type' => 'LEFT',
'conditions' => array('File.orders_id = Order.id')
),
array(
'table' => 'customers',
'alias' => 'Customer',
'type' => 'LEFT',
'conditions' => array('Customer.orders_id = Order.id')
)
)
));
You may want to use Containable (http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html) as it is the easiest solution. You can not use Order.Customer.id as Cake does not nest conditions like that. Manual joins would also work.
$this->loadModel('Customer');
$customer = $this->Customer->findById($this->Auth->user('id'), array(
'conditions' => array(
'File.id' => $id
),
'recursive' => 2
));
Orders will be accessible as:
pr($customer['Order']);
File will be accessible as:
pr($customer['File']);
I realised there is no need to actually join the customers table in this instance, because the orders table already has the customer_id.
$this->File->find('first', array(
'conditions' => array(
'File.id' => $id,
'Order.customer_id' => $this->Auth->user('id')
)
));
I'm trying to use on the fly associations to trim down the data I retrieve, but the model I'm using is associated to other models with a re-named field because I have 2 of the same models associated with it.
So, here's the model, say 'test', that has two 'user' fields, both related to the User model.
In the model:
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
),
'User_Watched' => array(
'className' => 'User',
'foreignKey' => 'user_id_watched'
)
);
When I retrieve data related to 'test', I want to only retrieve particular data linked to the 'User' and 'User_Watched' fields without any other nested information.
But when I do:
$this->User->unbindModel(array('hasMany' => array('something1', 'something2')), false);
then something1 and something2 data does not show up for the 'User' field of model 'test', but is still retrieved for the 'User_watched' field.
Can I not retrieve unwanted data for the 'User_watched' field?
Hope this makes sense... :)
KcYxA,
Containable behavior might help a lot in this case, as benjamin mentioned, your "find" queries would look like:
$this->User->find('first', array(
'conditions' => array('User.id' => $id),
'contain' => array('UserWatched')
));
In this case, you won't have to use unbindModel method. In this example, you'll get User and UserWatched data.
If you need only User data from "find", then tell Cake to "$this->User->contain();" so it won't go further then User model.
to use on the fly associations to trim
down the data I retrieve
Good idea.
'foreignKey' => 'user_id_watched'
should possibly be:
'foreignKey' => 'user_watched_id'.
Edit 1: At least this would make sense according to my current understanding. If user_id is a correct foreign key(FK), which cakephp uses to unbind the relations, but user_id_watched isn't, than your described behavior is explained.
Edit 2: The Containable behavior gives you another tool for controlling associated models.
Change $primaryKey in fly, run controller
Sample:
// Models
//....
class PreProductoDescripcion extends AppModel {
/**
* Primary key field
*
* #var string
*/
public $primaryKey = 'id_producto_descripcion';
//....
//....
}
class SenasaPedidosDetalles extends AppModel {
/**
* Display field
*
* #var string
*/
public $displayField = 'cod_tango';
public $belongsTo = array(
'SenasaPedidos' => array(
'className' => 'SenasaPedidos',
'foreignKey' => 'senasa_pedidos_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'PreProductoDescripcion' => array(
'className' => 'PreProductoDescripcion',
'foreignKey' => 'cod_tango',
//'conditions' => array('SenasaPedidosDetalles.cod_tango' => 'PreProductoDescripcion.codigo'),
'fields' => '',
'order' => ''
)
);
//....
#
// Controller Fly
//...
$this->SenasaPedidos->Behaviors->load('Containable');
$this->SenasaPedidos->SenasaPedidosDetalles->PreProductoDescripcion->primaryKey = 'codigo';
$datos = $this->SenasaPedidos->find(
'first', array(
'fields' => array( 'SenasaPedidos.*' ),
'conditions' => array( 'SenasaPedidos.id' => $id ),
'contain' => array(
'Usuarios' => array(
'fields' => array( 'Usuarios.apellido_nombre' )
),
'Clientes' => array(
'fields' => array( 'Clientes.razon_social' )
),
'Provincias' => array(
'fields' => array( 'Provincias.nombre' )
),
'Transportes' => array(
'fields' => array( 'Transportes.razon_social' )
),
'SenasaPedidosDetalles' => array(
'fields' => array( 'SenasaPedidosDetalles.*' ),
'PreProductoDescripcion' => array(
'fields' => array(
'PreProductoDescripcion.id_producto_descripcion',
'PreProductoDescripcion.descripcion'
)
)
),
)
));
//...