What is the best way in PHP to do variable caching? For example, let's assume I have a table, with 4 rows.
name | job
--------------------------
Justin Smith | Plumber
Jack Sparrow | Carpenter
Justin Smith | Plumber
Katie White | Doctor
Which is built like:
foreach($people as $person) {
echo $person->name;
echo get_job($person->name);
}
And the function call get_job() looks like:
function get_job($name) {
//This is pseudo code below
$row = MySQL->Query("SELECT job FROM people WHERE name = $name");
return $row->job;
}
As you can see, once we get the job of Justin Smith, further down we shouldn't and don't need to do a full MySQL query again, since we know it is Plumber.
I was thinking of doing a global variable which is a key=>value array like:
global $jobs = array("Justin Smith" => "Plumber",
"Jack Sparrow" => "Carpenter",
"Katie White" => "Doctor");
Then in the get_job() function I simply just check if the name exists in the array before querying. If not, insert the name and job into the array and return the job.
Basically, is there a better way to do this that is more elegant?
There is many possible solutions. You can store the SQL result in an array that you can use on multipe places on the page. Instead of global you should use static:
function get_job($name)
{
static $people_jobs;
if( !isset($people_jobs[$name]) || empty($people_jobs[$name]) )
{
$row = MySQL->Query("SELECT job FROM people WHERE name = $name");
$people_jobs[$name] = $row->job;
}
return $people_jobs[$name];
}
This function will do a MySQL query only once for a person, no matter how many time you call get_job($name);
this is the typical n + 1 problem where you make 1 query to get the list of "person" and then you make one query for each person to get the "job".
Depending how they are related.. maybe you could get both using one single query. For example: if the relation is a Nx1 (1 person has 1 Job, and 1 job can be used for N Persons) then your initial query should be something like:
SELECT p.name, j.job FROM Person p INNER JOIN Job j ON (p.job_id = j.id)
If the relation is a NxN, it gets tricky :P,
Hope this helps
Related
So I'm working on this/these problem(s), and there's just a lot going on for recently using codeigniter..
So, I have these specific tables:
sect_tbl ----------- sub_tbl
sect_id ----------- sub_code
2 --------------------ST20
3 --------------------AS13
3 --------------------DA11
1 --------------------PE40
2 --------------------SE10
3 --------------------PE20
4 --------------------RT40
I made sect_id the foreign key in sub_tbl.
the display - a side menu:
link of sect_ids:
1
2
3
4
I want to make it so, that if I click the link of sect_id 3, I'll be redirected to a new page with its corresponding set of sub_code -- "AS13, DA11, PE20" as seen in the table above.
Expected outcome is something like this:
sub_code
(in sect_id-3)
| AS13 |
| DA11 |
| PE20 |
Model: (most definitely not right )
function fetch_view($data){
$query = $this->db->query("SELECT a.sub_code, a.sub_name, a.sect_id,
b.block_name, b.year_level FROM subject as a JOIN section as b
WHERE a.sect_id = b.sect_id ");
return $query->result();
}
Controller:
$data["subjects"] = $this->view_model->fetch_view($this->session-
userdata('user_id'));
view: (this lacking since I cant get around much further with foreach loop yet )
<?php
foreach ($subjects as $row) {
?>
<?php echo $row->year_level.$row->block_name; ?>
<?php
}
?>
-> I tried working my way with href to redirect me and then be able to view it dynamically but it backfired. Thanks a lot to those who could spare their time to share solutions.
If you say you want all sub codes where sect ID = 3
$sect_id = 3;
I simplified your query, but the main problem was that you had no use of "on x = y" for your join
$query = $this->db->query("
SELECT
sec.year_level,
sec.block_name,
sub.sub_code,
FROM section as sec
JOIN subject as sub
ON sec.sect_id = sub.sect_id
WHERE sec.sect_id = ?
", [ $sect_id ] );
if( $query->num_rows() > 0 )
return $query->result();
return [];
Then, because I don't have your actual table schemas, I couldn't suggest if your view is correct, but in general, something like this should work:
$this->load->helper('url');
foreach( $subjects as $row )
{
echo anchor( 'subs/show/' . $row->sub_code, $row->year_level . $row->block_name );
}
Also, as a side note, try to always follow a code style with proper indentation. It makes things that are wrong stand out and easier to fix.
I'm having the hardest time figuring this out and it's probably because I'm not using the correct terms. If someone could point me in the right direction, that would be amazing. I'm going to use a hypothetical situation to make things easier:
I have a database with two tables:
tableA contains records for a house sale (house ID,address, price, current owner ID, etc)
tableB contains records for realtors who have shown a house (house ID, realtor ID, time and date, notes, etc).
I would like to have a query that can search a current owner ID and pull down all of their houses with information on everyone who showed the house. What I would like to retrieve is a JSON array that has the info from each tableB record appended/attached/added to a single record from tableA.
For example, if I search the the houses that are owned by ownerX (who owns two houses), I would like it to return two main items with sub items for each related entry in tableB. In the example below, ownerX has two houses. The first house on 1234 Fake St had 2 different realtors make a total of 3 visits. The second house on 555 Nowhere St had 1 realtor visit twice.
Here's how I'd like to retrieve the info:
tableA - Result 1 (House at address 1234 Fake St)
tableB - Result 1 (Realtor ID 1234, etc)
tableB - Result 2 (Realtor ID 1234, etc)
tableB - Result 3 (Realtor ID 2222, etc)
tableA - Result 2 (House at address 555 Nowhere St)
tableB - Result 1 (Realtor ID 1111, etc)
tableB - Result 2 (Realtor ID 1111, etc)
Instead, what I'm getting is this:
tableA - Result 1 (House at address 1234 Fake St),tableB(Realtor ID 1234, etc)
tableA - Result 2 (House at address 1234 Fake St),tableB(Realtor ID 1234, etc)
tableA - Result 3 (House at address 1234 Fake St),tableB(Realtor ID 2222, etc)
tableA - Result 4 (House at address 555 Nowhere St),tableB(Realtor ID 2222, etc)
tableA - Result 5 (House at address 555 Nowhere St),tableB(Realtor ID 2222, etc)
I don't don't want to retrieve tableA information each time. I only need that once, then each sub-result from tableB. This is important because I'm returning the data to an app that creates a new list. I'm currently using mysqli_multi_query
$sql = "SELECT * FROM tableA WHERE ownerID = "ownerX";";
$sql. = "SELECT tableB.*, tableA.houseID FROM tableB,tableA WHERE tableB.houseID = tableA.houseID;";
Again, the actual content is just a hypothetical. I'm looking for more of a, "You're an idiot, you should be using _____" and not, "You misspelled realtor and that's probably causing the problem.".
Also, please note that I'm not asking for the results to be formatted with the dashes and parentheses as they are above. I'm just simply writing it that way so it's easier to understand. I'm looking for a way to have sub-objects in a JSON array.
Any help pointing me in the correct direction would be much appreciated! Thanks to whoever takes the time to take a stab at this!
Tony
Additional information:
Here's the code I'm using to run the query:
$sql = "SELECT * FROM clocks WHERE user_key='".$userkey."';";
$sql .= "SELECT * FROM milestones WHERE (SELECT clock_key FROM clocks WHERE user_key='".$userkey."') = milestones.clock_key";
if (mysqli_multi_query($con,$sql))
{
do
{
if ($result=mysqli_store_result($con)) {
while ($row=mysqli_fetch_row($result))
{
$myArray[] = $row;
}
echo json_encode($myArray);
mysqli_free_result($result);
}
}
while(mysqli_more_results($con) && mysqli_next_result($con));
}
UPDATE WITH ANSWER:
Thanks for #vmachan's post below, I ended up getting all of my data at once, then ran through some loops to adjust the array. I'm going to use the house/relator example from above.
I used his code to get my results ($house_id is a variable input id):
$sql = "SELECT * FROM tableA INNER JOIN tableB ON tableA.houseID = tableB.houseID WHERE tableA.houseID='".$house_id."';";
I was given an array with 5 items because tableB had 5 entries. Since there are only 2 house entries in tableA, it looked like this:
["houseID"=>"1","price"=>"50000", "owner" => "Mike G", "state"=>"CA", "realtor" => "Jane D", "visitDay"=>"Tuesday", "notes" => "They liked the house"],
["houseID"=>"1","price"=>"50000", "owner" => "Mike G", "state"=>"CA", "realtor" => "Jane D", "visitDay"=>"Wednesday", "notes" => "They loved the house"],
["houseID"=>"1","price"=>"50000", "owner" => "Mike G", "state"=>"CA", "realtor" => "Stephanie W", "visitDay"=>"Friday", "notes" => "They didn't like the house"],
["houseID"=>"2","price"=>"65000", "owner" => "Michelle K", "state"=>"AL", "realtor" => "Mark S", "visitDay"=>"Tuesday", "notes" => "They made an offer"],
["houseID"=>"2","price"=>"65000", "owner" => "Michelle K", "state"=>"AL", "realtor" => "Jim L", "visitDay"=>"Monday", "notes" => "They stole stuff"]
The first 3 elements are from tableA and don't change. So, I used a loop to basically check the houseID, if it's a new house, create a new house array item, otherwise, add the details from tableB to the current house element:
<?php
//$house is an array will hold all of our indiviaul houses and their infomation.
$houseArray = array();
//Start the foreach loop
foreach($items as $item){
//$item["houseID"] is the houseID from our database that we got from the above code.
$houseID =$item["houseID"];
//$currentID is a varible that is set after the first iteration.
//This checks to see if we're still working with the same house, or a new house.
if($currentID!=$houseID){
//Create an array to hold all of the relator visit information arrays.
//This is created within the loop as it will erased if a new houseID is found in the array.
$relatorVisitArray = array();
//This is a secondary loop that checks the same array. This time, we are only working with the new houseID that from the condition above.
foreach($items as $rv){
//This cheecks to see if there is a match between the current houseID that we're working with and the other houseIDs in the array. Since we're going through the same array that we're already iterating, it will find itself (which is good).
if($houseID==$rv["houseID"]){
//Once is gets a match, it will create a temporary array to hold the "Relator Visit" information. The array is created within the loop as it needs to be cleared through each iteration.
$tempRealitorVisit = array(
'name' => $rv["name"],
'day' => $rv["day"],
'houseID' => $rv["houseID"],
'notes' => $rv["notes"]
);
//At the end of each iteation, we add the temporary to the main $relatorVisitArray.
$relatorVisitArray[] = $tempRealitorVisit;
}
}
//At this point, the subloop has ended and we're created an array ($relatorVisitArray) which contains all of the $tempRealitorVisit arrays.
//Remember, were are still within the first loop and have determined that this is a new house.
//Now we'll create a new house array based on the current houseID in this iteration.
//This array is created within the loop because we want it to cear at the next iteation when it's determined that it's a new house.
$house = array(
'houseID' => $item["houseID"],
'owner' => $item["owner"],
'price' => $item["price"],
'location' => $item["location"],
'relatorVisits' =>
//Here, we simply add the $relatorVisitArray to a key called, "relatorVisits" (ie an array within an array).
$relatorVisitArray
);
//We then add the $house to the $houseArray.
$houseArray[] = $house;
//Finally, we set $currentID to $item["houseID"]. At the next iteration, it will check this id against the next house ID. If they are the same, this entire code will skip until a new houseID appears from your database.
$currentID= $item["houseID"];
}
}
//This prints all of the information so it's easy to read.
echo '<pre>';
print_r($houseArray);
echo '</pre>';
}
?>
In the end, I'm left with one array that contains two sub arrays. The first sub array (House 1) contains 3 sub arrays (3 visits to that house). The second sub array (House 2) contains 2 sub arrays (2 visits to that house).
I hope this helps anyone that had the same issue as me. If anyone knows of a cleaner way to do this, please post it here! Thanks for the guidance!
Tony
I think you can combine the SQL statements as shown below to JOIN the clocks and milestones tables on the clock_key for a user-provided value i.e. $userkey. Then in your code you could loop thru the results and then check for consecutive house_ids.
$sql = "SELECT * FROM clocks INNER JOIN milestones ON clocks.clock_key = milestones.clock_key WHERE clocks.user_key='".$userkey."';";
You can then use the code similar to the one in ths SO posting. You would need to change it so that inside the loop you check if the previous 'house_id' is the same as the current one and if not, you would start a new parent array other wise keep adding to the existing array. At the end of the loop you could then call the encode to get your JSON format.
Hope this helps.
I have not got time to actually write (and test!) proper code but I would suggest that you collect your data into two php associative arrays: $houses with the owner as the key and $visits with the houseID as a key. Assuming that an owner can have more than one property on the market and knowing that a realtor can pay more than one visit to each property the entries in these two arrays will then themselves be "array of array"s.
sample:
$houses={'ownerx':{'houseID_1':['address_1','price_1'],
'houseID_2':['address_2','price_2']}},
'ownery':{'houseID_3':['address_3','price_3'],
'houseID_4':['address_4','price_4']}}
};
$visits={'houseID_1':['realtorID_1','realtorID_2', ...],
'houseID_2':['realtorID_3']
};
// I used JSON notation for simplicity ...
Doing this would require you to set up a proper structure once but it will save you from querying the same data again and again. The retrieval of the data from the associative arrays should also work very efficiently.
My current config of tables is as follows
PagesTable
$this->belongsToMany('Keywords', ['through' => 'PagesKeywords']);
PagesKeywordsTable
Schema: id, page_id, keyword_id, relevance
$this->belongsTo('Pages');
$this->belongsTo('Keywords');
KeywordsTable
$this->belongsToMany('Pages', ['through' => 'PagesKeywords']);
Now heres what i'm trying to do..
Find pages via keywords, using an array to be precise then order by PagesKeywords.relevance
(This is basically storing how many time that keyword is repeated per page, so no duplicate keywords in join table)
I've currently got this working fine except it groups the results of keywords by the keyword itself, where as I need them to be grouped by Pages.id
Here is what i have in my Pages controller, search action:
$keywords = explode(" ", $this->request->query['q']);
$query = $this->Pages->Keywords->find()
->where(['keyword IN' => $keywords])
->contain(['Pages' => [
'queryBuilder' => function ($q) {
return $q->order([
'PagesKeywords.relevance' =>'DESC'
])->group(['Pages.id']);
}
]]);
$pages = array();
foreach($query as $result) {
$pages[] = $result;
}
I know this seems like a backward way to do things but its the only way I seemed to be able to order by _joinTable (PagesKeywords.relevance)
This returns the results I need but now it needs to be grouped by Pages.id which is where this whole thing goes to pot..
Just to be clear the structure I want is:
Page data 1
Page data 2
Page data 3
Page data 4
Where as its currently returning:
Keyword "google"
------- Page data 1
------- Page data 2
------- Page data 3
------- Page data 4
Keyword "something"
------- Page data 1
------- Page data 2
------- Page data 3
------- Page data 4
If you are able to help me thats great!
Thanks
If you're having issues with complex queries with an ORM, I find it always easier to figure out the SQL I need to get the results I require, then adapt that to the ORM.
The query you're looking for would be like this (Using MySQL engine... MySQL Handles field selects more liberally in GROUP BY clauses than other SQL engines )
SELECT Pages.*, COUNT(DISTINCT(PagesKeywords.keyword_id)) AS KeywordCount
FROM pages Pages
INNER JOIN pages_keywords PagesKeywords ON (PagesKeywords.page_id = Pages.id)
INNER JOIN keywords Keywords ON (Keywords.id = PagesKeywords.keyword_id)
WHERE Keywords.name IN ('keyword1','keyword2')
GROUP BY Pages.id
This will give you all pages that contain the keyword and KeywordCount will contain the number of distinct attached Keyword.id's
So a finder method for this would look like ( going ad-hoc here so my syntax might be shaky )
** Inside your PagesTable model **
public function findPageKeywordRank(Query $q, array $options) {
$q->select(['Pages.*','KeywordCount'=>$q->func()->count('DISTINCT(PagesKeywords.keyword_id)'])
->join([
'PagesKeywords'=>[
'table'=>'pages_keywords',
'type'=>'inner',
'conditions'=>['PagesKeywords.page_id = Pages.id']
],
'Keywords'=>[
'table'=>'keywords',
'type'=>'inner',
'conditions'=>['Keywords.id = PagesKeywords.keyword_id']
]
])
->group(['Pages.id']);
return $q;
}
Then you can all your finder query
$pages = TableRegistry::get("Pages")->find('PageKeywordRank')
->where(['Keywords.name'=>['keyword1','keyword2']]);
I'll note that this is a very special case, hence the question to begin with. Under normal circumstances, such a function would be simple:
I have an array named $post_id, which contains 5 values
(Each numerical)
In order to print each value in the array, I use the following loop:
.
for ($i = 0; $i < $num; $i++)
{
echo $post_id[$i] . ' ';
}
...Which prints the following: 49, 48, 47, 46, 43
3. In my database, I have a table that looks like this:
post_categories
_____________________
post_id | category
__________|__________
43 | puppies
43 | trucks
46 | sports
46 | rio
46 | dolphins
49 | fifa
4. So, using the data in the array $post_id, I'd like to loop a database query to retrieve each value in the category column from the post_categories table, and place them into uniquely named arrays based on the "post id", so that something like...
echo $post_id_49[0] . ' ', $post_id_46[1];
...Would print "fifa rio", assuming you use the above table.
An example of such a query:
//Note - This is "false" markup, you'll find out why below
for ($i = 0; $i < $num; $i++)
{
$query = "SELECT category FROM post_categories WHERE post_id = $post_id[$i]";
fakeMarkup_executeQuery($query);
}
Why is this a "special" case? For the same reason the above query is "false".
To elaborate, I'm working inside of a software package that doesn't allow for "normal" queries so to say, it uses it's own query markup so that the same code can work with multiple database types, leaving it up to the user to specify their database type which leaves the program to interpret the query according to the type of database. It does, however, allow the query to be stored in the same "form" that all queries are, like "$result = *query here*" (With the only difference being that it executes itself).
For that reason, functions such as mysql_fetch_array (Or any MySQL/MySQLi function akin to that) cannot, and will not work. The software does not provide any form of built in alternatives either, effectively leaving the user to invent their own methods to achieve the same results. I know, pretty lame.
So, this is where I'm stuck. As you'd expect, all and any information you find on the Internet assumes you can use these MySQL & MySQLi functions. What I need, is an alternative method to grab one array from the results of a looped query per loop. I simply cannot come to any conclusion that actually works.
tl;dr I need to be able to (1) loop a query, (2) get the output from each loop as it's own array with it's own name, and (3), do so without the use of functions like mysql_fetch_array. The query itself does not actually matter, so don't focus on that. I know what do with the query.
I understand this is horrifically confusing, long, and complicated. I've been trudging through this mess for days - Close to the point of "cheating" and storing the data I'm trying to get here as raw code in the database. Bad practice, but sure as heck a lot easier on my aching mind.
I salute any brave soul who attempts to unravel this mess, good luck. If this is genuinely impossible, let me know so that I can send the software devs an angry letter. All I can guess is that they never considered that a case like mine would come up. Maybe this is much more simple then I make it to be, but regardless, I personally cannot come to an logical conclusion.
Additional note: I had to rewrite this twice due to some un explained error eliminating it. For the sake of my own sanity, I'm going to take a break after posting, so I may not be able to answer any follow up questions right away. Refer to the tl;dr for the simplest explanation of my need.
Sure you can do this , here ( assuming $post_ids is an array of post_id that you stated you had in the OP ), can I then assume that I could get category in a similar array with a similar query?
I don't see why you couldn't simply do this.
$post_id = array(49, 48, 47, 46, 43);
$result = array();
foreach($post_id as $id)
{
//without knowing the data returned i cant write exact code, what is returned?
$query = "SELECT category FROM post_categories WHERE post_id = $id";
$cats = fakeMarkup_executeQuery($query);
if(!empty($cats)) {
if(!isset($result[$id])){
$result[$id] = array();
}
foreach( $cats as $cat ){
$result[$id][] => $cat;
}
}
}
Output should be.
Array
(
[49] => Array
(
[0] => fifa
)
[46] => Array
(
[0] => sports
[1] => rio
[2] => dolphins
)
[43] => Array
(
[0] => puppies
[1] => trucks
)
)
Ok, assuming you can run a function (we'll call it find select) that accepts your query / ID and returns an array (list of rows) of associative arrays of column names to values (row), try this...
$post_categories = [];
foreach ($post_id as $id) {
$rows = select("SOME QUERY WHERE post_id = $id");
/*
for example, for $id = 46
$rows = [
['category' => 'sports'],
['category' => 'rio'],
['category' => 'dolphins']
];
*/
if ($rows) { // check for empty / no records found
$post_categories[$id] = array_map(function($row) {
return $row['category'];
}, $rows);
}
}
This will result in something like the following array...
Array
(
[49] => Array
(
[0] => fifa
)
[46] => Array
(
[0] => sports
[1] => rio
[2] => dolphins
)
[43] => Array
(
[0] => puppies
[1] => trucks
)
)
i have a database which contains 11 tables 9 of which have a one to many relation with a User Information table.
User Table:
ID , Name , Age , phone , . .......
on basis of ID the relation is with tables for e.g
User_cars
ID,User_ID(fk),Make , Model , .....
What i need to be able to do is present this data in DB in a CSV file. Since the There is a one to many relation a simple join doesnt work as rows are duplicated.(Not presentable Format for the client :S)
Im using Yii as web app and need some extension which can present it in some readable way.
or maybe a php script can do the task as well.
You can handle it in one of two ways:
1. Use GROUP_CONCAT (as #Dave mentioned in the comment - this is the link: MYSQL PHP API - Displaying & Fetching multiple rows within rows from another table).
There is a downside however, GROUP_CONCAT has a limit (by default 1024 characters), which you can change with the server variable group_concat_max_len, but you may not have access to do so.
This will look pretty much like this:
ID | Name | Car Model | Year
1 John BMW, Audi, Renault 1999, 2000, 2003
2 David Mercedes, Ford 2000, 2005
This can get very complicated if there are 30-40 entries per user, and non-readable.
2. The second option is to export it in the following format (not hard to do, you just iterate through all the cars a user has, but write the user only in the first iteration).
ID | Name | Car Model | Year
1 John BMW 1999
Audi 2000
Renault 2003
2 David .....
Here's some sample code (I use plenty of made up methods, that have suggestive names).
$csvArray = array();
foreach ($users as $user) {
$cars = $user->getCars(); //random method name. Grabs an array with the cars this user has :)
$firstIteration = 1;
foreach ($cars as $car) {
if ($firstIteration == 1) { // we only set the CSV row the first time we iterate through cars
$csvSingle['ID'] = $user->getId(); // more random method names
$csvSingle['Name'] = $user->getName();
$firstIteration = 0;
}
$csvSingle['car_model'] = $car->getCarModel();
$csvSingle['car_year'] = $car->getCarYear();
$csvArray[] = $csvSingle; // now we add the single row to the big CSV array.
}
}
fputcsv($handle, $csvArray); // $handle is the file you export to
i like first variant by #"Vlad Preda" and in your controller or component for export you can use something like this:
get users with cars (and cars must fill in user model relations)
$users = User::model()->with('cars')->findAll();
and then
foreach ($users as $user) {
$csvOneUser = array('id'=>$user->id, "name"=>$user->name);
if (!$user->cars) {
$csvAll[] = $csvOneUser;
continue;
}
foreach ($user->cars as $car) {
$car_attributes = $car->getAttributes();
foreach ($car_attributes as $k=>$v) {
$csvOneUser[$k][] = $v;
}
}
}