I have a problem in writing a query with Zend 1.11.3. I'm not able to get a field whose name contains a dot. Since I cannot edit the table I tried escaping the name, with no success.
My code is the following:
<?php
class RoomsTable extends Zend_Db_Table_Abstract {
...
function getRecord($id)
{
$fields = array(
'id' => 'id',
...
'attrib.floor' => 'attrib.floor',
...
);
$Select = $this->select()
->from($this->_name, $fields)
->where('id = ?',$id);
$list = $Select->query()->fetchAll();
}
}
?>
When I call:
$rooms_table = new RoomsTable();
$first_room = $rooms_table->getRecord(1);
I get:
Zend_Db_Table_Select_Exception: Select query cannot join with another table
So, the attrib.floor field is interpreted as an attempt to get a field from a different table, while it is an actual field name from this table.
I tried with the following without success:
'attrib\.floor' => 'attrib\.floor',
'`attrib.floor`' => '`attrib.floor`',
'"attrib.floor"' => '"attrib.floor"',
Do somebody know how to escape the dot, so that Zend allows me to get that field?
http://framework.zend.com/issues/browse/ZF-953 in the comments:
Identifiers that contain a dot (".") character are automatically split on the dot(s), and each piece quoted separately. Thus identifiers support the "schema"."table" or "table"."column" syntax.
If you have a database design that has table names or column names containing a dot character, this should be a very uncommon case, and it is not recommended. But you can work around it by using Zend_Db_Expr and quote the identifier yourself.
http://framework.zend.com/apidoc/1.9/Zend_Db/Expr/Zend_Db_Expr.html
So in this case the right way to write the field would be:
'attrib.floor' => new Zend_Db_Expr('`attrib.floor`')
Related
CakePHP 3.7. Trying to use the ORM to write a query which contains a MySQL COALESCE condition.
Followed advice on CakePHP 3 - How to write COALESCE(...) in query builder? and ended up having to write it manually using newExpr() as this was the given solution.
The code I have is as follows:
$TblRegulatoryAlerts = TableRegistry::getTableLocator()->get('TblRegulatoryAlerts');
$subscribed_to = $TblRegulatoryAlerts->getUserRegulations($u_id, $o_id, false);
$query = $this->find()
->contain('Filters.Groups.Regulations')
->select(['id', 'date', 'comment', 'Filters.label', 'Filters.anchor', 'Groups.label']);
$query->select($query->newExpr('COALESCE((SELECT COUNT(*)
FROM revision_filters_substances
WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances'));
$query->where(['date >=' => $date_start, 'date <=' => $date_end, 'Regulations.id' => $regulation_id, 'Filters.id IN' => $subscribed_to]);
$query->enableHydration(false)->orderDesc('date');
This produces the following SQL (output of debug($query->sql()):
SELECT RevisionFilters.id AS `RevisionFilters__id`, RevisionFilters.date AS `RevisionFilters__date`, RevisionFilters.comment AS `RevisionFilters__comment`, Filters.label AS `Filters__label`, Filters.anchor AS `Filters__anchor`, Groups.label AS `Groups__label`, (COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances) FROM revision_filters RevisionFilters INNER JOIN dev_hub_subdb.filters Filters ON Filters.id = (RevisionFilters.filter_id) INNER JOIN dev_hub_subdb.groups Groups ON Groups.id = (Filters.group_id) INNER JOIN dev_hub_subdb.regulations Regulations ON Regulations.id = (Groups.regulation_id) WHERE ...
Unfortunately this doesn't execute because Cake is putting in un-necessary parentheses surrounding the COALESCE statement, which changes the SQL.
In the above code it generates:
(COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances)
Whereas it needs to omit the parentheses surrounding COALESCE so it's just:
COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances
Is this possible?
Don't specify the alias in the expression, instead specify it using the key => value syntax of the Query::select() method, like this:
$query->select([
'count_substances' => $query->newExpr('...')
]);
It would still wrap the expression in parentheses, but that would then be valid as it doesn't include the alias.
That being said, using the function builders coalesque() method should work fine, the problem described in the linked question can be fixed by using the key => value syntax too, where the value can specify the kind of the argument, like ['Nodes.value' => 'identifier'], without that it would bind the value as a string.
However there shouldn't be any such problem with your example, using the function builders coalesce() method should work fine.
$query->select([
'count_substances' => $query->func()->coalesce($countSubquery, 1, ['integer'])
]);
The type argument is kinda optional, it would work with most DBMS without it, but for maximum compatibility it should be specified so that the integer value is being bound properly, also it will automatically set the return type of the function (the casting type) to integer too.
I have an images table with a column called type. I simply want to update all the rows to change the type to gallery where the user_id matches a particular user.
I am using this code
$this->Image->updateAll(array('Image.type' => 'gallery'),
array('Image.user_id' => $this->Auth->user('id')));
But I get this error: SQL Error: 1054: Unknown column 'gallery' in 'field list'
Why is gallery being added to the field list ?
Isn't the syntax supposed to set type to gallery?
Thanks!
Found this on the manual:
The $fields array accepts SQL expressions. Literal values should be quoted manually.
Thus, the following should work:
$this->Image->updateAll(
array('Image.type' => "'gallery'"),
array('Image.user_id' => $this->Auth->user('id'))
);
In your model do something like this in your method ....
public function saveImage($type='')
{
// I would add a test for $type
$db = $this->getDataSource();
$fields = array('type' => $db->value($type, 'string')); // $db->value() will format strings needed for updateAll()
$condition = array('user_id' => $this->Auth->user('id'));
// I would add a test for user id before running updateAll()
$this->updateAll($fields, $conditions);
}
Has anyone encountered a bug with table names using the PostgreSQL adapter in ZF2?
I have a column named UserId and when I try to update/delete a row based on this I get this error:
ERROR: column 'userid' does not exist
as opposed to UserId.
This is my code:
$delete = $this->delete(
'Users'
, array(
'UserId = ?' => $UserId
)
);
Might have something to do with autoQuoteIdentifiers but I couldn't find much online about it.
Any ideas?
The problem was that camel cased column names need to be enclosed in double quotes so the array should be:
array('"UserId" = ?' => $UserId)
In my controller I am retrieving records from my institutions table with the following fields
$params = array(
'fields' => array(
'Institution.id',
'Institution.name',
'Institution.about',
'Institution.picture'),
);
$institutions = $this->Institution->find('all',$params);
How can I prefix each 'Institution.picture' field with the full URL address, 'Institution.picture' itself only holds the name of the file.
I would also like to perform html_entity_decode() on each 'Institution.about' value from the returned set.
I know how to do this only without the framework if I make custom queries from scratch, then I would iterate each row and apply PHP functions to the field of interest. But is there a place in CakePHP (find or paginator) that I can specify such PHP manipulation on each field value from the returned set?
NOTE: I don't like to do this in the View, as I want to output it as json directly
You can define a virtualField for model:
public $virtualFields = array('image_url' => "CONCAT('/img/', Institution.picture)");
$params = array(
'fields' => array(
'Institution.id',
'Institution.name',
'Institution.about',
'Institution.picture',
'Institution.image_url'),
);
$institutions = $this->Institution->find('all',$params);
Unfortunaly MySQL doesn't have a function to decode HTML entities. You may utilize an afterFind() callback instead of virtualField. This lets you to decode entities as well as add a prefix.
CakePHP is php
Just iterate over the array and prepare it however you want:
$institutions = $this->Institution->find('all',$params);
$prefix = '/img/'; // <- define this
foreach ($institutions as &$row) {
$row['Institution']['about'] = html_entity_decode($row['Institution']['about']);
$row['Institution']['picture'] = $prefix . $row['Institution']['picture'];
}
If this is always required it can be applied to all finds via an afterFind method in the institution class.
I think you should do it in the View. See this example.
Hash::map can be very useful here. By specifying path you can only modify slices of the set.
I'm writing a script to import csv's into MySQL. The csv's will have variable numbers of fields, different data types and field sizes.
My current plan is to do a pass through the csv and collect statistical information on it to inform the MySQL CREATE TABLE query.
My conception was to create an array in this format:
$csv_table_data = array(
['columns'] => array(
[0] => array(
['min_size'] = int,
['max_size'] = int,
['average'] = int
['type'] = string
),
[1] => array(
['min_size'] = int,
['max_size'] = int,
['average'] = int
['type'] = string
),
[2] => array(
['min_size'] = int,
['max_size'] = int,
['average'] = int
['type'] = string
),
),
['some_other_data'] = array()
);
So, by accessing $csv_table_data['columns'][$i] I can access each column's attributes and use that to create the MySQL table. Besides $csv_table_data['columns'] I also have other data like total_rows, number_of_fields, and may add more.
I can gather all this data in one pass, create the appropriate table, and then populate it in a second pass.
I haven't used much object-oriented programming, but with all this data should I consider creating an object with these various properties, rather than creating this complex array?
What do you think about it in terms of readability, maintainability, speed of execution, and any other considerations that occur to you?
Thanks
I think you should use Classes, not only one big Object. Maybe you split it up to 2 or 3 Classes. It's much more cleaner as only arrays.
Something like this
class Table{
private $data = array();
private $otherData = 'what_ever';
public function getData(){
return $this->data;
}
public function addData(Row $row){
$this->data[] = $row;
}
//Add getter and setter
}
class Row{
private $min_size;
private $max_size;
private $avg_size;
private $type;
public function setMinSize($minSize){
$this->min_size = $minSize;
}
public function getMinSize(){
return $this->min_size;
}
//Add more getter and setter
}
if you have a limited number of files you want to insert you can use MySql's internal functions.
To import the datafile, first upload it to your home directory, so that the file is now located at /importfile.csv on our local system. Then you type the following SQL at the mysql prompt:
LOAD DATA LOCAL INFILE '/importfile.csv'
INTO TABLE test_table
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(field1, filed2, field3);
The above SQL statement tells the MySQL server to find your INFILE on the LOCAL filesystem, to read each line of the file as a separate row, to treat any comma character as a column delimiter, and to put it into your MySQL test_table as columns field1, field2, and field3 respectively. Many of the above SQL clauses are optional and you should read the MySQL documentation on the proper use of this statement.
On the other hand if you have to many files or you want to allow users to upload csv which you then in turn add them to the DB, I recommend using the 'array' version as it seems simpler to traverse.
Cheers,
Denis