Is there a way to calculate the percentage between two values in a table and select it as a column? I know it's possible, but I'd like to know if it's possible in a ZF2 context specifically.
I have a select in my ZF2 application that fetches a bunch of data from a db (SQL Server). This query concerns a table "libraries" that I want to order by free disk space (a column in the table). But I don't want it to order by the absolute amount of free space but rather the percentage relative to the total disk space.
So I mean something like
libraries.freeSpace / libraries.totalSpace as 'percentage'
but within a ZF2 select. This is the query currently:
$resultSet = $this->tableGateway->select(function(Select $select) use($report){
$select->where('id = ' . $report[0]->customer)
->order('freeSpace', 'asc');
});
e: ANSWER.
Use Zend\Db\Sql\Expression your model.
Add columns to your select:
$select->columns(array(
'percentage' => new Expression('cast(libraries.freeSpace') as float / cast.('libraries.totalSpace') as float', false);
You can pass an expression to Columns, don't forget the second parameter, you will need to set this to FALSE to stop the table prefix being automatically added for you when you manually supply them.
use Zend\Db\Sql\Expression;
$resultSet = $this->tableGateway->select(function(Select $select) use($report){
$select->columns(array(
'percentage' => new Expression('libraries.freeSpace / libraries.totalSpace')
), FALSE)
->where('id = ' . $report[0]->customer)
->order('freeSpace', 'asc')
;
});
Related
Currently, I have a GORM query that calculates a counter for each table entry using a second table and returns the first table with a new field "locks_total" which doesn't exist in the original.
Now, what I want to achieve is the same table returned (with the new "locks_total" field) but filtered with "locks_total" = 0.
I can't seem to make it happen because it doesn't recognize this field in the table, what can I do to make it happen? is it possible to run the query and then execute the filter on the new result table?
This is how we currently do it-
txn := dao.postgresManager.DB().Model(&models.SecretMetadataResponse{})
txn.Table(dao.firstTableName + " as s").
Select("s.*, (SELECT COUNT(*) FROM " + dao.secondTableName + " as l where s.id = l.secret_id) locks_total ")
var secretsTotal int64
var metadataResponseEntries []models.SecretMetadataResponse
txn = txn.Count(&secretsTotal). // Saving the count before trimming according to the pagination parameters
Limit(params.Limit).
Offset(params.Offset).
Order(constants.FieldName).
Order(constants.FieldId).
Find(&metadataResponseEntries)
When the SecretMetadataResponse contains the SecretMetadata which is the same fields as in the table and the LocksTotal is the new calculated field that we want to have.
type SecretMetadataResponse struct {
SecretMetadata
LocksTotal int `json:"locks_total"`
}
Thanks in advance :)
I have a query in which I want the name of a company and its employee quantity. The thing is I want to filter this result by some conditions (like employee_number > 50 etc.). My problem is that, when building the query, I don't know how to filter this result, as the condition is set over a calculated field, so when applying the condition it gives me the below
Error: `SQLSTATE[42S22]: Column not found: 1054 Unknown column 'employee_number' in 'where clause'`.
I have been trying different things, but this is what I currently have:
$query = $this->Companies->find('all')->where($conditions)->contain(['Users']);
$query
->select(['Users.name',
'Company.modified',
'employee_number' => $query->func()->count('DISTINCT(Employees.id)')])
->where('employee_number >' => 50 )
->leftJoinWith('Employees', function (\Cake\ORM\Query $query) {
return $query->where(['deleted' => 0]);
})
->group(['Employees.company_id', 'Company.id']);
First things first, you cannot refer to an aggregate in the WHERE clause, as grouping happens afterwards, hence the error, the field employee_number doesn't exist when the WHERE conditions are being applied, you have to leverage the HAVING clause instead.
Depending on the DBMS that you are using you can reference the column from the select list, MySQL for example allows that:
$query
->select([
'Users.name',
'Company.modified',
'employee_number' => $query->func()->count('DISTINCT Employees.id')
])
->leftJoinWith('Employees', function (\Cake\ORM\Query $query) {
return $query->where(['deleted' => 0]);
})
->group(['Employees.company_id', 'Company.id'])
->having(['employee_number >' => 50]);
while Postgres for example doesn't, and requires you to repeat the aggregation inside of the HAVING clause:
->having(function (
\Cake\Database\Expression\QueryExpression $exp,
\Cake\ORM\Query $query
) {
return $exp->gt($query->func()->count('DISTINCT Employees.id'), 50);
});
ps. using DISTINCT should only be necessary when you have for example multiple joins that would result in duplicate joined rows.
See also
Cookbook > Database Access & ORM > Query Builder > Aggregates - Group and Having
I am trying to query query the current month, here is my query:
$clients = $this->Clients;
$query = $clients->find();
if($this->Auth->user('role') !== 'admin'){
$query->where(['user_id =' => $this->Auth->user('id')]);
$query->where(['MONTH(dob) = ' => 'EXTRACT(month FROM (NOW()))']);
$query->order(['dob' => 'ASC']);
}
It returns 0 records (my field is a date type), however this query in phpmyadmin works:
SELECT * FROM `clients` WHERE MONTH(dob) = EXTRACT(month FROM (NOW()))
What am I doing wrong?
Just look at the actual generated query (check out your DBMS query log, or try DebugKit), it will look different, as the right hand side value in a key => value condition set is subject to parameter-binding/casting/quoting/escaping. In your case it will be treated as a string, so the condition will finally look something like:
WHERE MONTH(dob) = 'EXTRACT(month FROM (NOW()))'
That will of course not match anything.
You could pass the whole SQL snippet as a single array value, or as an expression object, that way it would be inserted into the query as is (do not insert user values that way, that would create an SQL injection vulnerability!), but I'd suggest to use portable function expressions instead.
CakePHP ships with functions expressions for EXTRACT and NOW, so you can simply do something like:
use Cake\Database\Expression\IdentifierExpression;
use Cake\Database\Expression\QueryExpression;
use Cake\ORM\Query;
// ...
$query->where(function (QueryExpression $exp, Query $query) {
return $exp->eq(
$query->func()->extract('MONTH', new IdentifierExpression('dob')),
$query->func()->extract('MONTH', $query->func()->now())
);
});
Looks a bit complicated, but it's worth it, it's cross DBMS portable as well as auto-quoting compatible. The generated SQL will look something like
WHERE EXTRACT(MONTH FROM (dob)) = (EXTRACT(MONTH FROM (NOW())))
See also
Cookbook > Database Access & ORM > Query Builder > Advanced Conditions
Cookbook > Database Access & ORM > Query Builder > Using SQL Functions
API > \Cake\Database\Expression\QueryExpression::eq()
API > \Cake\Database\FunctionsBuilder::extract()
API > \Cake\Database\FunctionsBuilder::now()
i have a SQL INSERT sequence in PDO like:
INSERT INTO property (id,name,addres...) VALUES (:id,:name,:address...)
And i want to do a UPDATE sequence, with the same fields. The problem is that i have 150 fields and about 3 or 4 different sequences, so if i make the update syntax manually its probably that it takes a lot of time and a lot of mistakes, is there any "automatic" way to convert it?
Thank you a lot
The way I would do this, is have a function which dynamically builds the query based on key-value pairs passed in an array:
function updateTable($table, $values)
{
// Set the base query
$query = 'UPDATE ' . $table;
// Build the query with the key value pairs
foreach($values as $key=>$data) {
$query . ' SET ' . $key . ' = ' . $data . ' ';
}
// Execute your query here
...
}
Obviously you would need to bind your PDO objects on each iteration of the loop but I wanted to give you the basic layout of a loop to handle what you want to achieve, you could then call it like this:
updateTable('Products', { 'product_name' => 'Apple', 'product_price' => 100.00})
This would then build the query:
UPDATE Products SET product_name = 'Apple', product_price = 100.00
You could easily extend this query to provide a WHERE parameter so you can refine your UPDATE query - please remember this is currently in-secure so please spend some time implementing proper sanitsation over variables before committing to the DB!
Hope this helps.
I am using Doctrine2 and Zf2 , now when I need to fetch count of rows, I have got the following two ways to fetch it. But my worry is which will be more optimized and faster way, as in future the rows would be more than 50k. Any suggestions or any other ways to fetch the count ?? Is there any function to get count which can be used with findBy ???
Or should I use normal Zf2 Database library to fetch count. I just found that ORM is not preferred to fetch results when data is huge. Please any help would be appreciated
$members = $this->getEntityManager()->getRepository('User\Entity\Members')->findBy(array('id' => $id, 'status' => '1'));
$membersCnt = sizeof($members);
or
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('count(p)')
->from('User\Entity\Members', 'p')
->where('p.id = '.$id)
->andWhere('p.status = 1');
$membersCnt = $qb->getQuery()->getSingleScalarResult();
Comparison
1) Your EntityRepository::findBy() approach will do this:
Query the database for the rows matching your criteria. The database will return the complete rows.
The database result is then transformed (hydrated) into full PHP objects (entities).
2) Your EntityManager::createQueryBuilder() approach will do this:
Query the database for the number of rows matching your criteria. The database will return a simple number (actually a string representing a number).
The database result is then transformed from a string to a PHP integer.
You can safely conclude that option 2 is far more efficient than option 1:
The database can optimize the query for counting, which might make the query faster (take less time).
Far less data is returned from the database.
No entities are hydrated (only a simple string to integer cast).
All in all less processing power and less memory will be used.
Security comment
Never concatenate values into a query!
This can make you vulnerable to SQL injection attacks when those values are (derived from) user-input.
Also, Doctrine2 can't make use of prepared statements / parameter binding, which can lead to some performance-loss when the same query is used often (with or without different parameters).
In other words, replace this:
->where('p.id = '.$id)
->andWhere('p.status = 1')
with this:
->where('p.id = :id')
->andWhere('p.status = :status')
->setParameters(array('id' => $id, 'status' => 1))
or:
->where($qb->expr()->andX(
$qb->expr()->eq('p.id', ':id'),
$qb->expr()->eq('p.status', ':status')
)
->setParameters(array('id' => $id, 'status' => 1))
Additionally
For this particular query, there's no need to use the QueryBuilder, you can use straight DQL in stead:
$dql = 'SELECT COUNT(p) FROM User\Entity\Members p WHERE p.id = :id AND p.status = :status';
$q = $this->getEntityManager()->createQuery($dql);
$q->setParameters(array('id' => $id, 'status' => 1));
$membersCnt = $q->getSingleScalarResult();
You should totally go to the dql version of the count.
With the first method you will hydrate (convert from db resultset to objects) each of the rows as single object and put them on one array and then count the amount items in that array. That will be a totally waste of memory and cycles if the only objective is to know the number of elements in that result set.
With the second method the dql will be gracefully converted to SELECT COUNT(*) Blah blah blah
plain SQL sentence and will retrieve directly the count from db.
The comment about ORM is not preferred to when to retrieve data is huge is true, in big batch process you should paginate your query to retrieve data instead all at the same time to avoid memory overrides but in that case you are only retrieving a single number, the total count so this rule doesn’t apply.
Query builder is so slow .
Use DQL for faster select .
$query = $this->getEntityManager()->createQuery("SELECT count(m) FROM User\Entity\Members m WHERE m.status = 1 AND m.id = :id ");
$query->setParameter(':id', $id);
You need setParameter for prevent SQL injection .
Stored procedure is fastest but it depend on your DB .
Make all relations of entity Lazy.