I am working in a legacy system that uses row() plus limit() to get one result. I didn't understand why, because row() already give me one result, but a coworker said that improves performance. Example:
$this->db
->select()
->select('extract(epoch from cadevolucao.dt_sistema) as data_sistema')
->select('extract(epoch from cadevolucao.dt_previsao_alta) as data_previsao')
->select('cadevolucao.cd_evolucao, cadevolucao.dt_sistema')
->join('contatnd', 'cadevolucao.num_atend = contatnd.num_atend')
->join('cadplanejamento', 'cadevolucao.cd_evolucao = cadplanejamento.cd_evolucao')
->where('contatnd.cd_pessoa', $cd_pessoa)
->where('tp_evolucao', -1)
->where('tipo', 1)
->order_by('cadevolucao.cd_evolucao','desc')
->limit(3)
->get('cadevolucao')
->row();
I looked for in the CI Documentation and Google, not founding anything useful about that.
Can someone explain if it's needed the limit() when using row() in Active Record's CI and why?
According to what i know row method returns a single result row. If your query has more than one row, it returns only the first row.But internally its still fetching all the rows fetched by the query and storing it in an array. Yes i think i must agree with your co-worker indeed limit will have a performance impact.
this is what row method does internally
/**
* Returns a single result row - object version
*
* #param int $n
* #return object
*/
public function row_object($n = 0)
{
$result = $this->result_object();
if (count($result) === 0)
{
return NULL;
}
if ($n !== $this->current_row && isset($result[$n]))
{
$this->current_row = $n;
}
return $result[$this->current_row];
}
as you its either returning the first element or the argument supplied i.e the row index.
row is actually an alias to this row_object
Related
In the documentation it shows the following:
To limit the number of results returned from the query, or to skip a given number of results in the query, you may use the skip and take methods:
$users = DB::table('users')->skip(10)->take(5)->get();
Alternatively, you may use the limit and offset methods:
$users = DB::table('users')
->offset(10)
->limit(5)
->get();
What are the differences between these two? Are there any differences in execution speed?
With the Query Builder, take() is just an alias for limit():
/**
* Alias to set the "limit" value of the query.
*
* #param int $value
* #return \Illuminate\Database\Query\Builder|static
*/
public function take($value)
{
return $this->limit($value);
}
NB This is not to be confused with take() on Collections.
limit only works for eloquent ORM or query builder objects, whereas take works for both collections and the ORM or Query Builder objects.
Model::get()->take(20); // Correct
Model::get()->limit(20); // Incorrect
Model::take(20)->get() // Correct
Model::limit(20)->get() // Correct
if take is used before get, it is the same as limit. If used after get, the action is performed by php itself.
that is
Model ::limit(10)->get() = Model ::take(10)->get()
and take to get
Model :: take(10)->get()
Launched through Query/Builder
public function take ($value)
{
return $this->limit($value);
}}
method to be starts.
Sql
select * from `table` where
`table`.`deleted_at` is null
limit 20
If used after get
Model :: get()->take(10);
Launched through Collection
public function take ($limit)
{
if ($limit<0) {
return $this->slice($limit, abs($limit));
}}
return $this->slice(0, $limit);
}}
method worked. and all the data is retrieved via sql and then $limit (10) are allocated via php
Sql
select * from `table` where
`table`.`deleted_at` is null
Limit works for eleqoent. Mostly used with offset
e.g
Model::offset(10)->limit(10)->get()
In the case above, it means get 10 elements(limit) starting from the 10th element onwards(offset).
Take is mostly used with collections but can also be used as an alias of limit for eloquent models
I'm struggling with the TYPO3 l10n and the modifying of localized records.
Short Question:
How can I get the localized record from my extbase model?
In more detail:
I am using a backend module to modify multiple records at the same time. At the moment it only works for origin records. But the customer wants to use this module to edit localized records also.
This is what I tryed so far:
An array is passing the origin uid's to the repository class. Depending on the SysLanguageUid I am doing a findByUid if its an origin record and if the SysLanguageUid is anything higher than 0 I do the following query:
protected function findByUidAndSysLanguageUid($uid, $sysLanguageUid) {
$query = $this->createQuery();
$query->matching(
$query->equals('l10n_parent', $uid),
$query->equals('sys_language_uid', $sysLanguageUid)
);
return $query->execute();
}
This query works fine for the first record. But what really confuses me is, ongoing from the second entry the query returns the origin records (even while the sys_language_uid in the query is set to >0).
Any ideas how to handle this?
PS: If you need some more information then let me know it.
UPDATE:
So far I managed it to get the raw query from the above constraint:
Query of the first record:
SELECT tx_extkey_domain_model_mymodel.*
FROM tx_extkey_domain_model_mymodel
WHERE (tx_extkey_domain_model_mymodel.l10n_parent = '133' AND tx_extkey_domain_model_mymodel.sys_language_uid = '1') AND
(tx_extkey_domain_model_mymodel.sys_language_uid IN (1, -1) OR
(tx_extkey_domain_model_mymodel.sys_language_uid = 0 AND
tx_extkey_domain_model_mymodel.uid NOT IN (SELECT tx_extkey_domain_model_mymodel.l10n_parent
FROM tx_extkey_domain_model_mymodel
WHERE tx_extkey_domain_model_mymodel.l10n_parent > 0 AND
tx_extkey_domain_model_mymodel.sys_language_uid = 1 AND
tx_extkey_domain_model_mymodel.deleted = 0))) AND
tx_extkey_domain_model_mymodel.hidden = 0 AND (tx_extkey_domain_model_mymodel.starttime 1479390060) AND
tx_extkey_domain_model_mymodel.deleted = 0
ORDER BY tx_extkey_domain_model_mymodel.name ASC
LIMIT 1;
Query of the second record:
SELECT tx_extkey_domain_model_mymodel.*
FROM tx_extkey_domain_model_mymodel
WHERE (tx_extkey_domain_model_mymodel.l10n_parent = '134' AND tx_extkey_domain_model_mymodel.sys_language_uid = '1') AND
(tx_extkey_domain_model_mymodel.sys_language_uid IN (1, -1) OR
(tx_extkey_domain_model_mymodel.sys_language_uid = 0 AND
tx_extkey_domain_model_mymodel.uid NOT IN (SELECT tx_extkey_domain_model_mymodel.l10n_parent
FROM tx_extkey_domain_model_mymodel
WHERE tx_extkey_domain_model_mymodel.l10n_parent > 0 AND
tx_extkey_domain_model_mymodel.sys_language_uid = 1 AND
tx_extkey_domain_model_mymodel.deleted = 0))) AND
tx_extkey_domain_model_mymodel.hidden = 0 AND (tx_extkey_domain_model_mymodel.starttime 1479390360) AND
tx_extkey_domain_model_mymodel.deleted = 0
ORDER BY tx_extkey_domain_model_mymodel.name ASC
LIMIT 1;
UPDATE 2
This now confuses me even more...
I put both of the sql queries into heidisql and run them manually. They work perfectly!
So it seems like there is no problem with the query itself.
UPDATE 3
This is the method of the repository which gets called by the controller.
/**
* #param array $parentUidCollection
* #param int $L
*/
protected function updateByCollection(array $parentUidCollection, $L = 0) {
//$L is the language $_GET parameter. cant use TSFE because of inside of a backend module
if($L > 0) {
$this->setTempQuerySettings($L);
}
foreach ($parentUidCollection as $parentUid){
$myModel = $this->findTranslatedByParentId($parentUid)->getFirst();
$myModel->setDescription('foo');
$this->update($myModel);
}
}
My defaultQuerySettings are overwritten in the third line if the actual language is not the default language.
/**
* #param $sysLanguageUid
*/
protected function setTempQuerySettings($sysLanguageUid) {
/** #var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings $tempQuerySettings */
$this->originalQuerySettings = $this->objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Typo3QuerySettings');
$tempQuerySettings = clone $this->originalQuerySettings;
$tempQuerySettings->setRespectStoragePage(false);
$tempQuerySettings->setRespectSysLanguage(true);
$tempQuerySettings->setLanguageUid($sysLanguageUid);
$tempQuerySettings->setLanguageMode(false);
$tempQuerySettings->setLanguageOverlayMode(false);
$this->setDefaultQuerySettings($tempQuerySettings);
}
And now with the function suggessted by Toke Herkild but without the query settings inside. they are set in the above snipped.
/**
* #param int|string $parentUid
* #return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
*/
public function findTranslatedByParentId($parentUid)
{
$query = $this->createQuery();
$query->matching($query->equals('l10n_parent', $parentUid));
return $query->execute();
}
UPDATE 4:
After executing the code the database looks like this:
The 100 uid's are the origin and the 200 are the localized records in this picture.
NOTICE: Below solution would work except for this bug:
https://forge.typo3.org/issues/47192
Maybe just make it simple, inside your ModelRepository do something like:
public function findTranslatedByParentId($parentUid) {
$query = $this->createQuery()
$qrySettings = $query->getQuerySettings();
$qrySettings->setLanguageMode('ignore');
$qrySettings->setLanguageOverlay(FALSE);
$query->setDefaultQuerySettings($qrySettings);
return $query->matching($query->equals('l18n_parent', $parentUid))->execute();
}
You need to disable the persistence layers language handling or it believes you try to fetch the localized version of the record for your current sys_language.
The question boils down to finding the proper way how to getPrimaryKey when iterating over a yielded result. When using select method, the result is an object of ArrayCollection which doesn't provide the getPrimaryKey method. A simple snippet
$q = UserQuery::create();
$q->select('a', 'b'); //yields an ArrayCollection object, doesn't have getPrimaryKey method when iterated
$q->find();
However,
$q = UserQuery::create();
$q->find(); //yields an ObjectCollection object, has getPrimaryKey method when iterated
Update
I have tried to use the setFormater to force using the ObjectCollection. Ultimately, it resulted in exception being thrown.
$q = UserQuery::create()
->setFormater(ModelCriteria::FORMAT_OBJECT)
->select('a', 'b')
->find(); //Error populating object
Update 2
Providing an exact use case, since it may be unclear at first what I am looking for. I have a table with >100 columns. I am providing the functionality using behaviour to not disable (not select) some of them. Thus, I am unseting some of the columns, and basing the $q->select on the remaining ones.
if (!empty($tableQuery->tableColumnsDisable)) {
$columns = $tableQuery->getTableMap()->getColumns();
foreach ($columns as $index => $column) {
if (!empty($tableQuery->tableColumnsDisable[$column->getName()])) {
unset($columns[$index]);
continue;
}
$columns[$index] = $column->getName();
}
//TODO - returns array collection, object collection needed
$tableQuery->select($columns);
}
When using select(), Propel will skip object hydration and will just return an ArrayCollection containing an array for each result row.
To retrieve the id of each result row, you need to add the column name to the select(). You can then just retrieve the value from the row arrays by using the column name:
$users = UserQuery::create()
->select(['id', 'a', 'b'])
->orderBy('c')
->find();
foreach ($users as $user) {
$id = $user['id'];
}
The select functionality is described in the documentation and in the docblock of Propel\Runtime\ActiveQuery\ModelCriteria#select() (source).
When you are using Propel 1, the functionality is the same. Read more about it in the Propel 1 documentation or the docblock of ModelCriteria#select() (source).
I have a doctrine entity called Site with the following field:
/**
* #ORM\OneToMany(targetEntity="SiteAsset", mappedBy="site")
*/
protected $assets;
I also have a function for getting an element of $assets with a given value of its url field:
public function getAssetByUrl($url) {
$c = Criteria::create()->
where(Criteria::expr()->eq('url',$url));
$matching = $this->assets->matching($c);
return $matching[0];
}
This function behaves very strangely. It appears to work if I run it immediately after fetching the entity from the database. But after a few database operations have been queued up, it begins to fail. I know it is failing, as I can find the asset I want as follows:
public function getAssetByUrl($url) {
foreach($this->assets as $asset) {
if($asset->getUrl() === $url) {
return $asset;
}
}
}
Furthermore, if I combine the two functions into:
public function getAssetByUrl($url) {
foreach($this->assets as $asset) {
if($asset->getUrl() === $url) {
error_log('found');
}
}
$c = Criteria::create()->
where(Criteria::expr()->eq('url',$url));
$matching = $this->assets->matching($c);
error_log($matching[0] ? 'found' : 'not found');
return $matching[0];
}
Then the Criteria always fails to find a match (i.e., when the function is called it always prints out 'found' followed by 'not found'). This would suggest that Doctrine is failing to find the entity I want in the case when it has already cached the entities in memory.
How can I ensure that matches are always found, while still making use of Doctrine's Criteria filtering system?
The issue was with the following line:
return $matching[0];
This returns the right answer if the PersistentCollection has not yet been fully fetched from the database. This is because the criteria is converted to an SQL query that returns an array containing only matching items; therefore getting the item at index 0 succeeds.
This returns the wrong answer if the PersistentCollection has already been fully fetched. This is because, internally, doctrine calls array_filter on the in-memory collection and returns the result. This means that the first element in the result is not necessarily at index 0, as array_filter preserves keys.
The solution was to wrap $matching in array_values($matching).
The Context
I'm using Laravel's Eloquent as my ORM. I am creating an API endpoint which provides access to Cars which have several attributes (color, make, status).
My endpoint allows clients to filter the return value by any subset of those attributes, if they provide no attributes then I will return everything.
The Question
I want to build a conditional query, which starts from "all" and narrows down based on which parameters have been specified. Here's what I've written:
public function getCars(Request $request)
{
$results = Cars::all();
if($request->has('color'))
$results = $results->where('color', $request->input('color'));
if($request->has('make'))
$results = $results->where('make', $request->input('make'));
if($request->has('status'))
$results = $results->where('status', $request->input('status'));
return $results->toJson();
}
If I call this with no parameters the API returns a list of all cars in the database.
If, however, I specify (for instance) status of 0 the API returns an empty set, despite the fact that some cars have status of 0.
Am I approaching this incorrectly? Is there something fundamental I'm missing?
Note that if instead I write:
$results = Cars::where('status', 0);
return $results->get();
The list of cars is properly generated
You should change your function like this:
public function getCars(Request $request)
{
$results = Cars::query();
if($request->has('color'))
$results = $results->where('color', $request->input('color'));
if($request->has('make'))
$results = $results->where('make', $request->input('make'));
if($request->has('status'))
$results = $results->where('status', $request->input('status'));
return $results->get()->toJson();
}
You could try this, for simplicity.
$query = Cars::query(); // no query executed, just give us a builder
$query->where(array_only($request->all(), ['color', 'make', 'status'])); // where can take a key value array to use
// update: only taking the vars you need, never trust incoming data
return $query->get(); // will be converted to Json for you
This only queries the DB for what you need. Yours is returning all results then filtering through them in a collection.
Update:
As Joseph stated, there is different functionality between $request->only() and array_only. The functionality of array_only is wanted here.