Split query with doctrine correctly - sql

I have a doctrine query which e.g. look like this:
$object = $this->createQueryBuilder('object')
->leftJoin('object.element', 'element')->addSelect('element')
->leftJoin('object.element2', 'element2')->addSelect('element2')
->leftJoin('object.many', 'many')->addSelect('many')
->leftJoin('many.element3', 'element3')->addSelect('element3')
->leftJoin('many.element4', 'element4')->addSelect('element4')
->where('object.id = 1')
->getQuery()
->getSingleResult();
The real query have more joins and need a lot of memory and the db performance is not good. In native SQL I would split it and load it correctly. What I want todo is load with the first query the object and some basic joined data. This would look like this:
$object = $this->createQueryBuilder('object')
->leftJoin('object.element', 'element')->addSelect('element')
->leftJoin('object.element2', 'element2')->addSelect('element2')
->where('object.id = 1')
->getQuery()
->getSingleResult();
Now I also want to load the many, many.element3 and many.element4. In seperate query. When I just use doctrine lazy loading feature it will create a SQL query foreach but I only want this as 1 query.
I know it would be possible to set EAGER on that relation but I only want to EAGER temporarily for this query not always when somebody join the object and access it.

Did solve my problem the following way:
In my object I did add a new set function for the collection.
/**
* Set manies.
*/
public function setManies($manies)
{
// this clear and foreach is needed to keep it as ArrayCollection so doctrine dont need for the unitofwork request the db again
$this->manies->clear();
foreach ($manies as $many) {
$this->manies->add($many);
}
return $this;
}
In the repository my code look like this:
$object = $this->createQueryBuilder('object')
->leftJoin('object.element', 'element')->addSelect('element')
->leftJoin('object.element2', 'element2')->addSelect('element2')
->where('object.id = 1')
->getQuery()
->getSingleResult();
$object->setManies($this->getEntityManager()->getRepository(Many::class)->loadByObjectId(
$object->getId()
));
return $object;
here the function in the many repository
// loadByObjectId in the many repository
$manies = $this->createQueryBuilder('many')
->leftJoin('many.element3', 'element3')->addSelect('element3')
->leftJoin('many.element4', 'element4')->addSelect('element4')
->where('many.object = 1')
->getQuery()
->getResult();
With this the SQL is successfully splitted in multiple request. In my case instead of 60000 rows in 1 request, I only had 40 rows affected in 4 request which make the hydration of the object a lot faster.

Related

Query builder dbflow conditional query

how can i build a query based on certain conditions .
i tried doing this
QueryBuilder builder = SQlite.Select().from(Table)
if(condition) {
builder.where(something)
}
Cursor c = builder.query;
but it is not permitted.
I have to query my database on conditions that i have saved in preferences . I googled and searched everywhere in thr docs but couldn't find a single example . do this feature exists in dbflow if yes then how if no is thr any other orm (like greenDAO) with this feature
You can create conditional queries in DBFlow. To query a table's columns you must append _Table to your class name, then access its property. These _Table classes are generated during build time.
The most simple query would be this one:
SQLite.select()
.from(YourTable.class)
.where(YourTable_Table.id.eq(someId)
.queryList();
You can also add new conditions by using .and and .or in your query:
SQLite.select()
.from(YourTable.class)
.where(YourTable_Table.id.eq(someId)
.and(YourTable_Table.name.eq(someName)
.queryList();
For a cleaner code, you can also group conditions into condition groups like this:
ConditionGroup conditionGroup = ConditionGroup.clause();
conditionGroup.and(YourTable_Table.id.eq(someId);
if (someCondition) {
conditionGroup.and(YourTable_Table.name.eq(someName);
}
return SQLite.select()
.from(YourTable.class)
.where(conditionGroup)
.queryList();
found two ways of achieving my problem
1.from #trevjonez(trevor jones)
Where<SomeModel> query = SQLite.select()
.from(SomeModel.class)
.where(SomeModel_Table.date_field.greaterThan(someValue));
if(someCondition) {
query = query.and(SomeModel_Table.other_field.eq(someOtherValue));
} else {
query = query.and(SomeModel_Table.another_field.isNotNull());
}
Cursor cursor = query.query();
//do stuff with cursor and close it
—
2.from #zshock using ConditionalGroup
ConditionGroup conditionGroup = ConditionGroup.clause();
conditionGroup.and(YourTable_Table.id.eq(someId);
if (someCondition) {
conditionGroup.and(YourTable_Table.name.eq(someName);
}
return SQLite.select()
.from(YourTable.class)
.where(conditionGroup)
.queryList();

Customise zf2 join tables to single table to get arrays

Hi I have a working public function that joins 3 tables to get values for 2 arrays. Now the process only has one table (table2) which contains all the colvalues required for the arrays.
I have tried everything I know to customise this so only table2 is used to get the arrays, but I just get blank results.
Can anyone point to me an example of something similar which takes values from a single table to get 2 separate arrays. I inherited this so I do not know what the thinking behind it was.
thank you in advance.
public function get2ColArray($condition)
{
$sql =new Sql($this->adapter);
$select = $sql->select();
$select->from('table1');
$select->columns(array('colval1'));
$select->join('table2', "table1.colval2 = table2.colval3", array('colval4'), 'inner');
$select->join('table3', "tabl1.colval1= table3.colval1", array('colval5'), 'left');
if(!empty($condition['in']))
foreach ($condition['in'] as $key=>$val) {
$select->where->in($key,$val);
}
if(!empty($condition['where']))
$select->where($condition['where']);
$select->order(array('table2.colval4'=>'ASC'));
$statement = $sql->prepareStatementForSqlObject($select);
$sql_result = $statement->execute();
if ($sql_result->count() > 0) {
$results = new ResultSet();
$data = $results->initialize($sql_result);
}
return $data;
}
Got it, its abit blunt but works, thanks ...
$select->from('table2');
$select->columns(array('colval1','colval_whateveryouwantaslongasitsintable2',));

Get response using pdo sql?

I am trying to get the actual response (the data) from my database using prepared statements:
$stmt=$dbconn->prepare("SELECT user_videos FROM public.account_recover_users WHERE user_mail= :email");
$stmt->execute(array(':videos'=>$json_videos,':email'=>$email));
I know that $stmt->execute(array(':videos'=>$json_videos,':email'=>$email)); will return a boolean, not the actual data. But how to get the data from my database into an array? I will need to later return that data, the script is accessed via a GET request, and I will need to do exit("{'data':$data_from_db}"); so I don't want to fetch each row using foreach($stmt as $row). Just pass it all as it is.
$results = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$results[] = $row;
}

Linq strategy for complex set

Am developing a ViewModel/PresentationModel which is getting complex.
I want the Linq query to return an IQueryable<UserPresentationModel>
Using EntityFramework against MSSQL
Is it possible to do any sort of iteration over the set before returning it to the presentation layer ie
List<UserPresentationModel> list = new List<UserPresentationModel>();
foreach (var person in listOfPeople)
{
UserPresentationModel u = new UserPresentationModel();
int userUIStatus = GetColourStateOfPerson(person);
u.FirstName = person.FirstName;
u.UserUIStatus = userUIStatus;
list.Add(u);
}
return list
This feels like it would always be N+1, and I'd never get the advantages of deferred execution, composing of queries..
Or (and I think am answering my own question) do I need to think in a SQL set based manner.
First, we can convert your code to LINQ.
IEnumerable<UserPresentationModel> models =
from person in listOfPeople
select new UserPresentationModel
{
FirstName = person.FirstName,
UserUIStatus = GetColourStateOfPerson(person)
}
return models.ToList();
Now, if GetColourStateOfPerson is making a DB round-trip, you definitely want to pull that out.
IDictionary<int, int> colourStatesByPersonId = GetColourStatesOfPeople(listOfPeople);
IEnumerable<UserPresentationModel> models =
from person in listOfPeople
select new UserPresentationModel
{
FirstName = person.FirstName,
UserUIStatus = colourStatesByPersonId[person.PersonId]
}
return models.ToList();
You could probably manage to create a single LINQ query that grabs just the first names and colour states of the people you want in a single query, but you haven't provided enough information about your data context for me to help you with that.
I would personally avoid passing around an IQueryable, which could continue making database trips any time somebody touches it. Let your data layer get out all the data you're likely to need, compose it into a list, and return that.
use IEnumerable<T>.Aggregate() instead of looping.
return listOfPeople.Aggregate(new List<UserPresentationModel>(), person => {
return new UserPresentationModel {
FirstName = person.FirstName,
UserUIStatus = GetColourStateOfPerson(person)
};
}).AsQueryable();
return listOfPeople.AsEnumerable().Select(p =>
new UserPresentationModel
{
FirstName = p.FirstName,
UserUIStatus = GetColourStateOfPerson(p)
}).AsQueryable();
I'm assuming that listOfPeople is an IQueryable that will eventually execute against your database. If that is the case then AsEnumerable() is important because SQL Server won't know what to do with GetColourStateOfPerson(). AsEnumerable() will force the IQueryable's expression tree to execute, pull the resulting rows out of your database and then apply Select() transformation in code as oppose to in SQL Server.
If you can implement GetColourStateOfPerson() as a stored proc or database function then you can omit AsEnumerable() and AsQueryable() and allow execution to delay even longer.

SQL to Magento model understanding

Understanding Magento Models by reference of SQL:
select * from user_devices where user_id = 1
select * from user_devices where device_id = 3
How could I perform the same using my magento models? getModel("module/userdevice")
Also, how can I find the number of rows for each query
Following questions have been answered in this thread.
How to perform a where clause ?
How to retrieve the size of the result set ?
How to retrieve the first item in the result set ?
How to paginate the result set ? (limit)
How to name the model ?
You are referring to Collections
Some references for you:
http://www.magentocommerce.com/knowledge-base/entry/magento-for-dev-part-5-magento-models-and-orm-basics
http://alanstorm.com/magento_collections
http://www.magentocommerce.com/wiki/1_-_installation_and_configuration/using_collections_in_magento
lib/varien/data/collection/db.php and lib/varien/data/collection.php
So, assuming your module is set up correctly, you would use a collection to retrieve multiple objects of your model type.
Syntax for this is:
$yourCollection = Mage::getModel('module/userdevice')->getCollection()
Magento has provided some great features for developers to use with collections. So your example above is very simple to achieve:
$yourCollection = Mage::getModel('module/userdevice')->getCollection()
->addFieldToFilter('user_id', 1)
->addFieldToFilter('device_id', 3);
You can get the number of objects returned:
$yourCollection->count() or simply count($yourCollection)
EDIT
To answer the question posed in the comment: "what If I do not require a collection but rather just a particular object"
This depends if you still require both conditions in the original question to be satisfied or if you know the id of the object you wish to load.
If you know the id of the object then simply:
Mage::getModel('module/userdevice')->load($objectId);
but if you wish to still load based on the two attributes:
user_id = 1
device_id = 3
then you would still use a collection but simply return the first object (assuming that only one object could only ever satisfy both conditions).
For reuse, wrap this logic in a method and place in your model:
public function loadByUserDevice($userId, $deviceId)
{
$collection = $this->getResourceCollection()
->addFieldToFilter('user_id', $userId)
->addFieldToFilter('device_id', $deviceId)
->setCurPage(1)
->setPageSize(1)
;
foreach ($collection as $obj) {
return $obj;
}
return false;
}
You would call this as follows:
$userId = 1;
$deviceId = 3;
Mage::getModel('module/userdevice')->loadByUserDevice($userId, $deviceId);
NOTE:
You could shorten the loadByUserDevice to the following, though you would not get the benefit of the false return value should no object be found:
public function loadByUserDevice($userId, $deviceId)
{
$collection = $this->getResourceCollection()
->addFieldToFilter('user_id', $userId)
->addFieldToFilter('device_id', $deviceId)
;
return $collection->getFirstItem();
}