In Codeigniter, I have the following model
function get_item_history($id)
{
//from metadata_history get item_id and corresponding metadata
$this->db->from('metadata_history')->where(array('id'=>$id, 'current_revision'=> "TRUE"));
$query = $this->db->get();
$result = $query->result_array(); //store this in an array
// loop through the array
foreach( $result as $key => $row )
{
$array = array('item_id'=>$row['item_id'], 'current_revision'=> "TRUE");
$this->db->from('history')->where($array);
$query = $this->db->get();
$row['items'] = $query->result_array(); //
$result[$key] = $row;
}
return $result;
}
The problem is that this results in multiple queries to the SQL table increasing the execution time significantly (pagination is not an option)
I want to be able to pass the first query results to the second query as an array, so that I would have only a single go at the database, then rebuild an array from the results.
How should I rewrite this code (the second part)? Will it be faster (I suppose so)?
EDIT
Rebuilding the array from the results is what is flummoxing me.
http://www.phpbuilder.com/board/showthread.php?t=10373847
this is what I probably want, but am failing the jump
You can use inner query here. It is ideal situation for that -
function get_item_history($id)
{
// Here the above requirement can be achieved in a single query.
$sql = "select * from history h
where h.item_id IN (select item_id from metadata_history mh where mh.id = $id
AND mh.current_revision = TRUE) AND h.current_revision = TRUE";
$result = $this->db->query($sql);
//Return whichever column of result you want to return or process result if you want.
$result;
}
You should use JOINs to do this. It'll offload the execution of the query to the server. I can't give you too much more detail without knowing how your database is structured, but check out the docs on JOINs:
http://dev.mysql.com/doc/refman/5.0/en/join.html
http://www.webdesign.org/web-programming/php/mysql-join-tutorial.14876.html
http://www.keithjbrown.co.uk/vworks/mysql/mysql_p5.php
Another option would be to do your wheres in the loop and move the query executation outside of the foreach:
// loop through the array
foreach( $result as $key => $row )
{
$array = array('item_id'=>$row['item_id'], 'current_revision'=> "TRUE");
$this->db->or_where($array);
}
$query = $this->db->get();
$row['items'] = $query->result_array(); //
$result[$key] = $row;
OK this took some work, and I also had to do some adjustments in my view
So the problem can be broken down into two main components
1) Pass the results of the first query as an array to the second one using where_in
2) Reorder/regroup the results of the first array by item_id
My earlier code was doing the second component implicitly
So here is what I did (limits, offsets, ordering have been cut out to improve readablity)
function get_item_history($id)
{
//from metadata_history get item_id and corresponding metadata
$this->db->from('metadata_history')->where(array('id'=>$id, 'current_revision'=> "TRUE"));
$query = $this->db->get();
$result_query1 = $query->result_array(); //store this in an array
foreach ($result_query1 as $key-> $row){
$result[$row['item_id']]['meta_info'] = $row; //the first query contains meta info, that must be passed to the view
$selected_id_array[] = $row['item_id']; //Create a array to pass on to the next query
$result[$row['item_id']]['items'] = array(); //declare an array which will hold the results of second query later
}
$this->db->select('h.*');
$this->db->from('history h');
$this->db->where_in('h.item_id', $selected_id_array);
$this->db->where(array('h.current_revision' => 'TRUE'));
$query = $this->db->get();
$row = $query->result_array();
foreach ($row as $key => $datarow) {
$result[$datarow['item_id']]['items'][] = $datarow; //populate the array we declared earlier with results from second query
}
return $result; // Now this variable holds an array which is indexed by item id and contains the results of second query 'grouped' by item_id
}
So the number of queries have been cut from ~10 to 2.
On my local machine this saves ~50 msec/page, though I am not sure how this will do for larger databases.
Related
I made a function in a CI model that first queries for the table to get its fields(because these fields will change dynamically over time,so I can't hard code a list of field names),and then when it gets the results of the first query, and builds a fields name list,it queries the table again to get the values belonging to one row or record.It then stores the second query result in an array,which is passed back to the controller.Here is the complete function that performs these steps:
public function getAssetFeatures($as_id)
{
$data = array();
//this sql query gets the field names from the table I want to query.
$sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '".DATABASE."' AND TABLE_NAME = 'as_features';";
$query = $this->db->query($sql);
$count = 0;
foreach($query->result_array() as $k)
{
foreach($k as $kk=>$v)
{
if( $v != "as_features_id" &&
$v != "as_id" &&
$v != "cid" &&
$v != "lot_size" )
{
$features_array[$count] = $v;
$count++;
}
}
}
$features_string = implode(",",$features_array);
//I got the field names, put them into an array, then concatenated them into a string, which I will use for the fields in the next query:
$sql = "SELECT $features_string FROM as_features WHERE as_id='$as_id'";
$query = $this->db->query($sql);
//mandatory rooms/features:
foreach($query->result() as $row)
{
foreach($row as $k=>$v)
{
$data["$k"] = $v; //build an associative array with the values of each field for the one row I am querying
}
}
return $data; // return the associative array.
}
At first I thought something was broken in my table or view,but as I kept repeating the same call to the model function by refreshing the page and entering the exact same values,I noticed that sometimes the code worked,and I wouldn't get the errors "undefined index".
So I outputted the results of the array with this code:
echo "<pre>";
print_r($asset['features']);
echo "</pre>";
...and the expected output, which only performs successfully sometimes, but not all the time, for the exact same operation using the exact same parameters, looks like this:
Array
(
[kitchen] => 1
[liv_area] => 0
[dining] => 1
[family] => 0
[bed] => 0
[bath] => 1
[half_bath] => 0
[parking] => 0
[car_storage] => 0
[pool] => 0
[miscellaneous] => 0
)
When the query returns a result set and then a populated array,my form works and looks normal.but,most of the time the query fails,and I get what looks like this:
The issue is with the following snippet of code:
foreach($query->result() as $row)
{
foreach($row as $k=>$v)
{
$data["$k"] = $v; //build an associative array with the values of each field for the one row I am querying
}
}
The way it works is that for every result which is returned it will overwrite $data with the relevant keys. However, because the $query->result() will return the row you want, then return false, the array essentially ends up being:
$data[] = false; i.e., set the whole array to false/empty.
If you change the loop to be, the code inside the loop won't be executed for the false value of $query->result():
while ($row = $query->result())
{
// your code
}
It's worth noting that if you're intending to return more that one row from this, it won't work as it will just overwrite the existing values.
Ok, the problem was my code, not the query. I was passing an encrypted as_id, but neglected to unencrypt it before constructing the select query. That's why it would work sometimes, and not others. For some reason MySQL allowed the encrypted as_id to match existing as_id of INT type. After performing the decrypt, the query results became predictable. This is something I wouldn't expect SO members to have divined. Thanks.
public function getAssetFeatures($as_id)
{
$as_id = $this->utilityclass->decryption9($as_id); // <- I had this commented out. In fact, I removed the line from my example code in the original post, because I didn't think it was important to the question. Not only was it important, it was the problem!
$data = array();
$sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '".DATABASE."' AND TABLE_NAME = 'as_features';";
$query = $this->db->query($sql);
i am trying to access all the sub category under category but not working for me. Have written the code like that,
function getAllSubCategory($CatId)
{
$data=array();
$this->db->select('c.catId,c.cat_name,c.cat_alias,c.cat_image,c.parentcatid,c.isHeading');
$this->db->from('coupon_category as c');
$this->db->where('c.parentcatid', $CatId);
$query = $this->db->get();
if($query->num_rows()>0)
{
$data = $dataFetched = $query->result_array();
$query->free_result();
foreach ($dataFetched as $row) {
$this->db->select('c.catId,c.cat_name,c.cat_alias,c.cat_image,c.parentcatid,c.isHeading');
$this->db->from('coupon_category as c');
$this->db->where('c.parentcatid', $dataFetched->catId);
$data = $query->result_array();
$query->free_result();
}
}
echo count($data);
return $data;
}
But not working, don't know where i am wrong.
The problem is here
$this->db->where('c.parentcatid', $dataFetched->catId);
Should be
$this->db->where('c.parentcatid', $row ["catId"]);
The code has following issues:
The inner loop assigns the new result set to the variable $data and thereby you
lose the previous values you had in $data;
You repeat exactly the same query again in each iteration of that loop. You do not
use the $row variable in the loop;
There is no support for deeper nested subcategories, which the data structure seems to
allow for.
If you want to fetch child categories, and their child categories, ... etc, you
should use recursion, and even if you would only have two levels of subcategories in your table, it will
still be good code.
Here is some code for making it work in the direction you were going,
using recursion, and using the function array_merge to append the data as you go.
function getAllSubCategory($CatId)
{
$this->db->select('c.catId,c.cat_name,c.cat_alias,c.cat_image,c.parentcatid,c.isHeading');
$this->db->from('coupon_category as c');
$this->db->where('c.parentcatid', $CatId);
$query = $this->db->get();
$dataFetched = $query->result_array();
$query->free_result();
$data = array();
foreach ($dataFetched as $row) {
$data[] = $row;
$data = array_merge($data, getAllSubCategory($row["catId"]));
}
echo count($data);
return $data;
}
Alternative
You could get better performance by doing a
hierarchical query on the database (as for instance was asked for
here ),
but I don't know if it can be made to work with Active Record. So I will provide
the code using plain query() syntax, according to the answer I have given myself
to that particular question:
function getAllSubCategory($CatId)
{
$CatId = $this->db->escape($CatId); // sanitise to prevent SQL injection
$query = $this->db->query(
"select c.catId,c.cat_name,c.cat_alias,c.cat_image,c.parentcatid,c.isHeading
from (select * from coupon_category
order by parentcatid, catId) as c,
(select #pv := '$CatId') as initialisation
where find_in_set(c.parentcatid, #pv) > 0
and #pv := concat(#pv, ',', catId)");
$data = $query->result_array();
$query->free_result();
echo count($data);
return $data;
}
Note that the last solution requires that
for each record parentcatid < catid, otherwise the results will not be complete.
But if that is the case, this will certainly beat the first solution in performance, as it only executes one database query.
Also, if your table would have cycles where a child of a node is also its parent (for example), the first solution will keep looping and querying until some out-of-memory error occurs. The last solution will not, because it only looks for children for which parentcatid < catid.
`
$poplrRec = mysql_query("SELECT * FROM ".ADD_RECIPE." ORDER BY POPULARITY DESC LIMIT 4");
while($poplrRec1=mysql_fetch_array($poplrRec))
{
$likecount=mysql_query("SELECT RECIPE_ID, COUNT(RECIPE_ID) FROM ".RECIPE_LIKE." WHERE RECIPE_ID=".$poplrRec1['RECIPE_ID']);
while($b=mysql_fetch_array($likecount))
{
$cmnt=mysql_query("SELECT RECIPE_ID, COUNT(RECIPE_ID) FROM ".RECIPE_CMMNT." WHERE RECIPE_ID=".$poplrRec1['RECIPE_ID']." AND TYPE=0");
while($c=mysql_fetch_array($cmnt))
{
}} } `
hi here i use core php while loop and MySQL query so i have to use these type of query in MVC structure of codeigniter. And like here i want result of every query separate to use in mvc structure.
each query provide some data that need to use in somewhere in that relevant .
plz suggest me how can i implement these type of php query in codeigniter or mvc structure .
Try to write query following format
$query = $this->db->get('vehicles_types');
$result = $query->result_array();
// loop through the types e.g. the parents
foreach( $result as $key => $row )
{
// add the "examples" e.g. the children to the result array
$query = $this->db->get_where('vehicles',array('type'=>$row['id']));
$row['examples'] = $query->result_array();
$result[$key] = $row;
}
return $result;
$result=$this->db->query("select * from tableName");
foreach($result->result() as $row){
// $row has the associative array
// you can again query the database with $this->db->query
}
I often need to retrieve results and access them by a given column.
Is there a way to write this without walking through the whole dataset each time?
I looked into various PDO fetch modes, but nothing jumped out as being simpler than that. Thanks.
function get_groups() {
$stmt = $dbc->prepare("SELECT * FROM groups ORDER BY group_name");
$stmt->execute();
$groups = $stmt->fetchAll();
$return = [];
foreach($groups as $group) {
$return[$group['id']] = $group;
}
return $return;
}
My proposed solution was pretty obsolete. The right solution comes from this answer
$stmt = $pdo->query("SELECT foo,baz FROM bar")
$groupedByFooValuesArray = $stmt->fetchAll(\PDO::FETCH_GROUP|\PDO::FETCH_UNIQUE)
to group it by another column, change the first column you select
if your goal is to have your same array indexed by different column values, I think the only option is to actually index them by different column values.
you can do that by controlling by which field the array is returned
function get_groups($sql,$key,$values='') {
if ($values != '') {
$stmt = $dbc->prepare($sql);
$stmt->execute($values);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else {
$rows = $dbc->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
foreach ($rows as $row) {
$indexed[$row[$key]] = $row;
}
return $indexed;
}
then, use get_groups to build an indexed array:
$sql = 'SELECT * FROM groups ORDER BY group_name'
var_dump(get_groups($sql,'id'));
There might be a way to store a resultset in some way that you can fetchAll() it multiple times, that would be awesome. but I don't know it.
I was wondering how I can handle a mysql request in php precisely as an object.
Ex:
//supposing...
$beginning=$_GET['start'];//value equal to 3
$ending=$_GET['end'];//value equal to 17
$conn=new mysqli("localhost","user","password","databasename");
$query=$conn->query("select name, favoriteFood, weight, from tablename");
1- Supposing that tablename has 23 rows, how to printing only 14 rows, beginning for example by 3rd row and ending in 17th row, as following?
Ex:
//supposing... It, I guess, should result in error but is a sketch of my ideia
for($i=$beginning,$colsCol=$query->fetch_array(MYSQLI_ASSOC); $i<$ending; $i++)
printf("%s %s %s<\br>",$colsCol['name'][$i],$colsCol['favoriteFood'][$i],$colsCol['weight'][$i]);
2 - And later, how to order the resulted rows with $query variable?
P.S.: I know that to get results ordered, I could user order by columname, but in this case I would like to order the resulted rows after query been done.
If you want to sort later, after the query's done, then you'd need to store the results in a PHP data structure and do the sorting there. Or re-run the query with new sorting options.
As for fetching only certain rows, it'd be far more efficient to retrieve only the rows you want. Otherwise (for large result sets) you're forcing a lot of data to be pulled off disk, sent over the wire, etc... only to get thrown away. Rather wasteful.
However, if you insist on doing things this way:
$row = 0; $data = array();
while($row = $query->fetch_array(MYSQLI_ASSOC)) {
$row++;
if (($row < 3) || ($row > 17)) {
continue;
}
$data[] = $row;
}
for 2D array you can use it.
function asort2d($records, $field, $reverse=false) {
// Sort an array of arrays with text keys, like a 2d array from a table query:
$hash = array();
foreach($records as $key => $record) {
$hash[$record[$field].$key] = $record;
}
($reverse)? krsort($hash) : ksort($hash);
$records = array();
foreach($hash as $record) {
$records []= $record;
}
return $records;
} // end function asort2d
Use SQL for SQL-tasks:
"how to printing only 14 rows, begging for example by 3rd row and ending in 17th row"
$stmt = $database->prepare('SELECT `name`, `favoriteFood`, `weight` FROM `tablename` LIMIT :from, :count');
$stmt->bindValue(':from', (int)$_GET['start'] - 1);
$stmt->bindValue(':count', (int)$_GET['end'] - (int)$_GET['start']);