I'm in the process of migrating a database from MySQL to PostgreSQL and am using CakePHP to access the data. The normal model methods (find, delete, create, etc), seem to work as expected, but when I run custom queries using the query method, it doesn't seem to populate the resulting array how I'd expect.
For example, this code when run with CakePHP with MySQL as the database:
$results = $this->Table1->query('SELECT Table1.*, Table2.* FROM Table1 LEFT JOIN Table2 USING (Field)');
Produces the following array
array(
array('Table1' => array(<Table1Result1>), 'Table2' => array(<Table2Result1>))
array('Table1' => array(<Table1Result2>), 'Table2' => array(<Table2Result2>))
...
array('Table1' => array(<Table1ResultN>), 'Table2' => array(<Table2ResultN>))
)
When I run a similar query using PostgreSQL as the database, I get the following array:
array(
array(0 => array(<Table1and2Result1Combined>))
array(0 => array(<Table1and2Result2Combined>))
...
array(0 => array(<Table1and2ResultNCombined>))
)
Is there a way to get the PostgreSQL result to be returned in the same way as the MySQL one?
You have to build your query like this:
SELECT
table1.field1 AS "Table1__field1",
table1.field2 AS "Table1__field2"
FROM
table1
;
Related
I use a union to join two datasets and then the following query to setup for pagination correctly
$paginationQuery = $this->find('all')
->contain(['EmailAddresses' => [
'foreignKey' => false,
'queryBuilder' => function($q) {
return $q->where(['Members__id' => 'EmailAddresses.member_id']);
}
]])
->select( $selectMainUnion )
->from([$this->getAlias() => $query])
->order(['Members__last_name' => 'ASC', 'Members__first_name' => 'ASC']);
I have also tried
$paginationQuery = $this->find('all')
->contain(['EmailAddresses'])
->select( $selectMainUnion )
->from([$this->getAlias() => $query])
->order(['Members__last_name' => 'ASC', 'Members__first_name' => 'ASC']);
and tried
$query->loadInto($query, ['EmailAddresses']); where $query is the result of the union.
Neither of these result in email addresses added to $paginationQuery.
Is there a way to do this?
Adding to clarify the code
$selectMain =['Members.id',
'Members.member_type',
'Members.first_name',
'Members.middle_name',
'Members.last_name',
'Members.suffix',
'Members.date_joined'];
foreach($selectMain as $select) {
$selectMainUnion[] = str_replace('.', '__', $select);
}
$this->hasMany('EmailAddresses', [
'foreignKey' => 'member_id',
'dependent' => true,
]);
Looking at the SQL in DebugKit SQL Log, there is no reference to the EmailAddresses table.
Generally containments do work fine irrespective of the queries FROM clause, whether that's a table or a subquery should be irrelevant. The requirement for this to work however is that the required primary and/or foreign key fields are being selected, and that they are in the correct format.
By default CakePHP's ORM queries automatically alias selected fields, ie they are being selected like Alias.field AS Alias__field. So when Alias is a subquery, then Alias.field doesn't exist, you'd have to select Alias.Alias__field instead. So with the automatic aliases, your select of Members__id would be transformed to Members.Members__id AS Members__Members__id, and Members__Members__id is not something the ORM understands, it would end up as Members__id in your entities, where the eager loader would expect id instead, ie the name of the primary key which is used to inject the results of the queried hasMany associated records (this happens in a separate query), your custom queryBuilder won't help with that, as the injecting happens afterwards on PHP level.
Long story short, to fix the problem, you can either change how the fields of the union queries are selected, ie ensure that they are not selected with aliases, that way the pagination query fields do not need to be changed at all:
$fields = $table->getSchema()->columns();
$fields = array_combine($fields, $fields);
$query->select($fields);
This will create a list of fields in the format of ['id' => 'id', ...], looks a bit whacky, but it works (as long as there's no ambiguity because of joined tables for example), the SQL would be like id AS id, so your pagination query can then simply reference the fields like Members.id.
Another way would be to select the aliases of the subquery, ie not just select Member__id, which the ORM turns into Member__Member__id when it applies automatic aliasing, but use Members.Member__id, like:
[
'Member__id' => 'Members.Member__id',
// ...
]
That way no automatic aliasing takes place, on SQL level it would select the field like Members.Member__id AS Member__id, and the field would end up as id in your entities, which the eager loader would find and could use for injecting the associated records.
I am attempting to do a GroupBy on an associated table via contains -> conditions, however I am getting the following error...
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 'group = 'BrandUsers.user_id' AND BrandUsers.brand_id in (1,2,3,4,5,6))' at line 1
with the following query
SELECT
BrandUsers.id AS `BrandUsers__id`,
BrandUsers.user_id AS `BrandUsers__user_id`,
BrandUsers.brand_id AS `BrandUsers__brand_id`,
Users.id AS `Users__id`,
Users.username AS `Users__username`,
Users.email AS `Users__email`,
Users.password AS `Users__password`,
Users.first_name AS `Users__first_name`,
Users.last_name AS `Users__last_name`,
Users.token AS `Users__token`,
Users.token_expires AS `Users__token_expires`,
Users.api_token AS `Users__api_token`,
Users.activation_date AS `Users__activation_date`,
Users.secret AS `Users__secret`,
Users.secret_verified AS `Users__secret_verified`,
Users.tos_date AS `Users__tos_date`,
Users.active AS `Users__active`,
Users.is_superuser AS `Users__is_superuser`,
Users.role AS `Users__role`,
Users.created AS `Users__created`,
Users.modified AS `Users__modified`
FROM
brand_users BrandUsers
INNER JOIN
users Users
ON Users.id =
(
BrandUsers.user_id
)
WHERE
(
group = :c0
AND BrandUsers.brand_id in
(
:c1,
:c2,
:c3,
:c4,
:c5,
:c6
)
)
I have taken a look at the following links, but the above error persists
Group By within contain cakephp
cakephp GROUP BY within containable
Here is my code
$this->paginate = [
'contain' => [
'BrandUsers' => [
'conditions' => [
'group' => 'BrandUsers.user_id'
]
],
'BrandUsers.Users'
]
];
$brands = $this->paginate(
$this->Brands
->find('all')
->where(['Brands.user_id' => $this->Auth->user('id')])
);
As mentioned in the answers/comments to the questions that you've linked, there is no group option for containments, that's true for CakePHP 2.x as well as 3.x, and if there was such an option you would have placed it wrong, as you've nested it inside the conditions option, hence it is being compiled into the queries WHERE clause.
If you need to modify the query used for obtaining containments on the fly, then you can for example pass a callable as known from other query builder methods:
'BrandUsers' => function (\Cake\ORM\Query $query) {
return $query->group('BrandUsers.user_id');
}
or use the finder option to point to a finder that modifies the passed query accordingly:
'BrandUsers' => [
'finder' => 'groupedByUser'
]
It should be noted that grouping only works for HasMany and BelongsToMany associations, as they are not being joined into the main/parent query.
See also
Cookbook > Database Access & ORM > Query Builder > Passing Conditions to Contain
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods
I an looking to use a JOIN to select data from a table and a view in CakePHP like so :
$this->Annonces->find('all')
->where($arrFiltres)
->order($arrOrder)
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [...],
]);
And would like to be able to select all the fields from the first table and som of the jointed table like so :
->select(['Annonces.*', 'AnnoncesSuivis.id']);
But this creates a faulty SQL query.
.* isn't supported by the ORM Query, it will convert this to
Annonces.* AS Annonces__*
which is invalid SQL. It would work with the lower level Database Query (Connection::newQuery()), which doesn't add aliases, however it won't return entities, so that's probably not what you want.
See Cookbook > Database Access & ORM > Database Basics > \Cake\Database\Connection::newQuery()
Pass a table object
As of CakePHP 3.1 you can pass table objects to Query::select(), which will cause all the fields of the table to be selected.
$this->Annonces
->find('all')
->select(['AnnoncesSuivis.id'])
->select($this->Annonces)
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [ /* ... */ ],
])
->where($arrFiltres)
->order($arrOrder);
That way the AnnoncesSuivis.id field, and all fields of Annonces will be selected.
See Cookbook > Database Access & ORM > Query Builder > Selecting All Fields From a Table
Build the fields from the schema
That's what passing a table object will cause internally too, and it's also supported in CakePHP < 3.1.
$query = $this->Annonces->find('all');
$fields = $query->aliasFields(
$this->Annonces->schema()->columns(),
$this->Annonces->alias()
);
$query
->select(array_merge(['AnnoncesSuivis.id'], $fields))
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [ /* ... */ ],
])
->where($arrFiltres)
->order($arrOrder);
This would also work for the fields option that can be passed to Table::find(), though you'd have to use a separate query object in that case, like
$fields = $this->Annonces->query()->aliasFields(
$this->Annonces->schema()->columns(),
$this->Annonces->alias()
);
$this->Annonces->find('all', [
'fields' => array_merge(['AnnoncesSuivis.id'], $fields)
// ...
]);
Use Query::autoFields()
In ealier CakePHP version, you could also make use of Query::autoFields(), which, when set to true, will automatically include the fields of the main table and possible containments.
See Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Passing Conditions to Contain
Auto selecting all fields is the default behavior until you set fields via Query::select(), in that case you'll have to explicitly enable Query::autoFields().
$this->Annonces
->find('all')
->select(['AnnoncesSuivis.id'])
->autoFields(true)
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [ /* ... */ ],
])
->where($arrFiltres)
->order($arrOrder);
This should give you the desired query, however as mentioned this will only work for the main table and containments, if you'd wanted to include all fields of a manually joined table, then you'd have to specify them one by one.
You also can create virtual field in Entity:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class User extends Entity {
protected function _getFullName() {
return $this->_properties['first_name'] . ' ' . $this->_properties['last_name'];
}
}
echo $entity->full_name;
i am beginner in cakephp , and i want use SQL IN operator in find method , i have words table.
my code is :
$this->Word->find('Word.wordid in (83,82)');
, and this code create this query :
SELECT `Userword`.`userwordid`, `Userword`.`userid`, `Userword`.`wordid`,
`Userword`.`date`, `Userword`.`levelid` FROM `userwords` AS `Userword` WHERE
`Userword`.`wordid` = (82)
but i need this query
SELECT `Userword`.`userwordid`, `Userword`.`userid`, `Userword`.`wordid`,
Userword`.`date`, `Userword`.`levelid` FROM `userwords` AS `Userword` WHERE
`Userword`.`wordid` IN (83,82)
how can getting like this query (using IN operator )
thanks.
you need to let cake take care of that - simply use it as it was a string (but make sure it is an array):
$arrayOfIds = [1, 5, ...];
$this->Word->find('all', array(
'conditions' => array('Word.wordid' => $arrayOfIds)
));
I'd like to insert data into a mysql table using 'the cakephp way'.
I have a multi-stage program that stores data to a session, and toward the end of the program I'd like to write the session data to the database. I could do this using a standard sql insert statement but would like to know how this should be done using cakephp. (Most of the cakephp doc discusses sending data from a webform, and I'd like to manually submit session data.)
Should I manually format the session data in this format and then send this to the model? And if so, is there a helper function for this?
Array
(
[ModelName] => Array
(
[fieldname1] => 'value'
[fieldname2] => 'value'
)
)
Yes, that's the way to do it. There's really no need for a helper function, just use the ones you normally would.
$name = 'Foo';
$city = 'Bar';
$this->ModelName->save(
array(
'name' => $name,
'city' => $city
)
);