zend framework 2 and doctrine query builder - php

I am trying to write a query using the doctrine query builder based on the parameters i am getting from an array.
Here's my array,
$query = array('field' => 'number,
'from' => '1',
'to' => '100',
'Id' => '2',
'Decimation' => '10'
);
The query that i am trying to write is,
select * from table where (number between 1 AND 100) AND (Id = 2) AND number mod 10 = 0
Here's where i stand now,
if (is_array($parameters['query'])) {
$queryBuilder->select()
->where(
$queryBuilder->expr()->between($parameters['query']['field'], $parameters['query']['from'], $parameters['query']['to']),
$queryBuilder->expr()->eq('Id', '=?1'),
$queryBuilder->expr()->eq($parameters['query']['field'],'mod 10 = 0')
)
->setParameter(array(1 => $parameters['query']['Id']));
}
I just cant wrap my head around this for some reason. Help !! anyone?

Not tested or anything, just directly typed into SO answer box:
$queryBuilder->select('table')
->from('My\Table\Entity', 'table')
->where($queryBuilder->expr()->andX(
$queryBuilder->expr()->between('table.number', ':from', ':to'),
$queryBuilder->expr()->eq->('table.id', ':id'),
))
->andWhere('MOD(table.number, :decimation) = 0')
->setParameters(array(
'from' => $parameters['query']['from'],
'to' => $parameters['query']['to'],
'id' => $parameters['query']['id'],
'decimation' => $parameters['query']['decimation']
));
It does not dynamically let you set which field to apply the where condition to. However, this is most likely a bad idea without whitelisting the values you want to allow. Once this is done a simple modification to the code above (just interpolate the value in place of number in the table.number string).

I kinda got it,
The main idea here was to use the andWhere clause. Multiple where clauses was what i wanted. and the mod operator that doctrine gives. Worked out pretty well. In between the requirement changed as well.

Related

How to limit number of items I fetch from DynamoDB with PartiQL?

I have this query which I run in PHP:
$result = $client->executeStatement([
'Limit' => 1,
'Statement' => "SELECT * FROM transactions WHERE completed = 0",
]);
I have tried using query function as well but that too supports Limit which is not actually a limit.
$result = $db->query(array(
'TableName' => 'transactions',
'IndexName' => 'completed-index',
'Count' => 1,
'Limit' => 1,
'ScannedCount' => 1,
'KeyConditions' => array(
'completed' => array(
'AttributeValueList' => array(
array('N' => '1')
),
'ComparisonOperator' => 'EQ'
),
),
));
According to their documentation, Limit doesnt necessarily mean a number of matching items:
The maximum number of items to evaluate (not necessarily the number of
matching items). If DynamoDB processes the number of items up to the
limit while processing the results, it stops the operation and returns
the matching values up to that point
Can anyone tell me if there is an actual way to limit the number of rows returned just like we do in SQL databases?
You can only limit how much data is read from disk (pre-filter), not how much is returned (post-filter).
DynamoDB never allows you to request for unbounded work. If DynamoDB allowed you to ask for just 1 row with a filter condition that never matched anything, that would potentially need to read the full database trying to find that 1 row. Behavior like that is what causes relational databases to have issues at scale.
Now, if you're not specifying a filter condition and your query is fully indexed, the amount read will match the amount returned, so the limit should act pretty much like you'd want.
Otherwise you might have to make repeated calls to page the results until you get as many rows as you want.

Dynamically add columns to query results via CakePHP 3 ORM queries

I'm trying to write a query using CakePHP 3.7 ORM where it needs to add a column to the result set. I know in MySQL this sort of thing is possible: MySQL: Dynamically add columns to query results
So far I've implemented 2 custom finders. The first is as follows:
// src/Model/Table/SubstancesTable.php
public function findDistinctSubstancesByOrganisation(Query $query, array $options)
{
$o_id = $options['o_id'];
$query = $this
->find()
->select('id')
->distinct('id')
->contain('TblOrganisationSubstances')
->where([
'TblOrganisationSubstances.o_id' => $o_id,
'TblOrganisationSubstances.app_id IS NOT' => null
])
->orderAsc('Substances.app_id')
->enableHydration(false);
return $query;
}
The second custom finder:
// src/Model/Table/RevisionSubstancesTable.php
public function findProductNotifications(Query $query, array $options)
{
$date_start = $options['date_start'];
$date_end = $options['date_end'];
$query = $this
->find()
->where([
'RevisionSubstances.date >= ' => $date_start,
'RevisionSubstances.date <= ' => $date_end
])
->contain('Substances')
->enableHydration(false);
return $query;
}
I'm using the finders inside a Controller to test it out:
$Substances = TableRegistry::getTableLocator()->get('Substances');
$RevisionSubstances = TableRegistry::getTableLocator()->get('RevisionSubstances');
$dates = // method to get an array which has keys 'date_start' and 'date_end' used later.
$org_substances = $Substances->find('distinctSubstancesByOrganisation', ['o_id' => 123);
if (!$org_substances->isEmpty()) {
$data = $RevisionSubstances
->find('productNotifications', [
'date_start' => $dates['date_start'],
'date_end' => $dates['date_end']
])
->where([
'RevisionSubstances.substance_id IN' => $org_substances
])
->orderDesc('RevisionSubstances.date');
debug($data->toArray());
}
The logic behind this is that I'm using the first custom finder to produce a Query Object which contains unique (DISTINCT in SQL) id fields from the substances table, based on a particular company (denoted by the o_id field). These are then fed into the second custom finder by implementing where(['RevisionSubstances.substance_id IN' ....
This works and gives me all the correct data. An example of the output from the debug() statement is as follows:
(int) 0 => [
'id' => (int) 281369,
'substance_id' => (int) 1,
'date' => object(Cake\I18n\FrozenDate) {
'time' => '2019-09-02T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'comment' => 'foo',
'substance' => [
'id' => (int) 1,
'app_id' => 'ID000001',
'name' => 'bar',
'date' => object(Cake\I18n\FrozenDate) {
'time' => '2019-07-19T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
]
],
The problem I'm having is as follows: Each of the results returned contains a app_id field (['substance']['app_id'] in the array above). What I need to do is perform a count (COUNT() in MySQL) on another table based on this, and then add that to the result set.
I'm unsure how to do this for a couple of reasons. Firstly, my understanding is that custom finders return Query Objects, but the query is not executed at this point. Because I haven't executed the query - until calling $data->toArray() - I'm unsure how I would refer to the app_id in a way where it could be referenced per row?
The equivalent SQL that would give me the required results is this:
SELECT COUNT (myalias.app_id) FROM (
SELECT
DISTINCT (tbl_item.i_id),
tbl_item.i_name,
tbl_item.i_code,
tbl_organisation_substances.o_id,
tbl_organisation_substances.o_sub_id,
tbl_organisation_substances.app_id,
tbl_organisation_substances.os_name
FROM
tbl_organisation_substances
JOIN tbl_item_substances
ON tbl_organisation_substances.o_sub_id = tbl_item_substances.o_sub_id
JOIN tbl_item
ON tbl_item.i_id = tbl_item_substances.i_id
WHERE
tbl_item.o_id = 1
AND
tbl_item.date_valid_to IS NULL
AND
tbl_organisation_substances.app_id IS NOT NULL
ORDER BY
tbl_organisation_substances.app_id ASC
) AS myalias
WHERE myalias.app_id = 'ID000001'
This does a COUNT() where the app_id is ID000001.
So in the array I've given previously I need to add something to the array to hold this, e.g.
'substance' => [
// ...
],
'count_app_ids' => 5
(Assuming there were 5 rows returned by the query above).
I have Table classes for all of the tables referred to in the above query.
So my question is, how do you write this using the ORM, and add the result back to the result set before the query is executed?
Is this even possible? The only other solution I can think of is to write the data (from the query I have that works) to a temporary table and then perform successive queries which UPDATE with the count figure based on the app_id. But I'm really not keen on that solution because there are potentially huge performance problems of doing this. Furthermore I'd like to be able to paginate my query so ideally need everything confined to 1 SQL statement, even if it's done across multiple finders.
I've tagged this with MySQL as well as CakePHP because I'm not even sure if this is achievable from a MySQL perspective although it does look on the linked SO post like it can be done? This has the added complexity of having to write the equivalent query using Cake's ORM.

Paginate do not sort DESC

I can't display Agreements with agreement_number less than 7 and order it by agreement_number DESC.
I have read Pagination CakePHP Cook Book and can't find where my code is wrong. It display only less than 7, but always ASC. I have found similar question here, [that works],(CakePHP paginate and order by) and do not know why. Agreement.agreement_number is int(4).
$this->Agreement->recursive = 0;
$agreements = $this->Paginator->paginate('Agreement', array(
'Agreement.agreement_number <' => '7'
), array(
'Agreement.agreement_number' => 'desc'
)
);
$this->set('agreements', $agreements);
}
Exact cake version is 2.5.2.
... Where did you read that that was the correct syntax?
The paginate function's third parameter is for sorting (and I mean, within the table... with those down and up arrows).
List of allowed fields for ordering. This allows you to prevent
ordering on non-indexed, or undesirable columns.
You have the exact link used for documentation of the API, but you don't seem to be following it (like, from here and here)
$this->Paginator->settings = array(
'Agreement' => array(
'order' => array('Agreement.agreement_number' => 'desc')
)
);
$agreements = $this->Paginator->paginate('Agreement', array(
'Agreement.agreement_number <' => '7'));

Get results in magento just like BETWEEN keyword in SQL

I've got this problem that I can't solve. Partly because I can't explain it with the right terms. I'm new to this so sorry for this clumsy question.
Below you can see an overview of my goal.
I'm using Magento CE 1.7.0.2
In SQL we have a keyword BETWEEN for filter the records between two values.
For Example: Select * from Mytable where rollnum BETWEEN 10 AND 100;
Like that I want to use in Magento
$_productCollection = Mage::getResourceModel('reports/product_collection')->addAttributeToSelect('*')->setOrder($choice, 'asc');
in the above i want to use that & that should work just like BETWEEN keyword in SQL..
Any Ideas....
$collection->addAttributeToFilter('start_date', array('date' => true, 'to' => $todaysDate))
->addAttributeToFilter('end_date', array(
array('date' => true, 'from' => $todaysDate),
array('null' => true)
)
Simple way, taking your problem :
$_productCollection->addAttributeToFilter(
array(
'attribute' => 'rollnum',
'in' => array(10, 100)
)
);

CakePHP database queries DISTINCT / GROUP BY error

In my CakePHP model I'm trying to get some data from my table.
I tried using DISTINCT but it seems like using DISTINCT doesn't change the query results.
I can see many rows that has the same nick
with 'DISTINCT Mytable.nick'
$this->Mytable->find('all',
array(
'fields'=> array(
'DISTINCT Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id >=' => 1, 'Mytable.id <=' => 100),
'order' => array('Mytable.id DESC')
));
with 'group Mytable.nick'
$this->Mytable->find('all',
array(
'fields'=> array(
'Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id >=' => 1, 'Mytable.id <=' => 100),
'group' => 'Mytable.nick',
'order' => array('Mytable.id DESC')
));
with 'Mytable.nick'
$this->Mytable->find('all',
array(
'fields'=> array(
'Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id >=' => 1, 'Mytable.id <=' => 100),
'order' => array('Mytable.id DESC')
));
Edit: It seems like even CakePHP 2.1 can't use DISTINCT option. When I tried "GROUP BY" it solved my issue. But as you can see from my query I need to order results with Mytable.id descended. When I use GROUP BY, when Mysql finds relevant row, it doesn't take others. For example.
id=1, nick=mike, age=38, location=uk
id=2, nick=albert, age=60, location=usa
id=3, nick=ash, age=42, location=uk
id=4, nick=albert, age=60, location=new_zelland
When I use group Mytable.nick, I don't see 4th row in my results, I see 2nd row. Because when mysql saw "albert" second time, it doesn't put it into my results. But I need latest "albert" result. Is it not possible?
Edit2: It seems like order by/group by conflict is a common problem. I found some tips in this question. But it gives solution for native Mysql queries. I need a solution for CakePHP type queries.
Not clear on why you want to group by nick and order by id. Do you intend to use an aggregate function like COUNT() to see how many occurrences of the same nick there are? In short you overall goal still is not clear to me. Might be worth being aware of the HAVING MySQL keyword.
Updated: Ok, that makes more sense. So you need to use a sub select on the condition or perhaps express that as a join. I'll try and show an example using the sub select in the WHERE clause.
/* select last occurrence for each nick (if you need one for each location )*/
SELECT nick, age, location
FROM myTable t1
WHERE id =
(SELECT MAX(id)
FROM myTable t1
WHERE t1.nick = t2.nick);
Would think something like this would work:
$this->Mytable->find('all',
array(
'fields'=> array(
'Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id =' => '(SELECT MAX(id) FROM myTable t2 WHERE myTable.nick = t2.nick)', 'Mytable.id <=' => 100)
));

Categories