I am new to PHP and am looking at efficient ways to return data from the database. Lets say I have a UserProfile table that has a one to many relationship with UserInterest and UserContact:
Select p.Id, p.FirstName, p.LastName, i.Name as Interests, c.Email, c.Phone
from UserProfile p
left join UserInterest i on p.Id = i.UserProfileId
left join UserContact c on p.Id = c.UserProfileId
where p.Id = 1
An efficient way to retrieve data would be to create a multidimensional array such as:
$user = array( "FirstName" => "John",
"LastName" => "Doe",
"Gender" => "Male",
"Interests" => array(
"Hiking",
"Cooking"),
"Contact" => array(
"Email" => "john.doe#gmail.com",
"Phone" => "(555) 555-5555"));
I can't seem to get my head around how this would be constructed in PHP. For simple data like interests I could use group_concat(i.Name) as Interests in the query to return interests back as a comma separated list in a single row, however, for an associative array such as Contact, I'd like to be able to get a handle on each key in the array using $user['Contact']['Email'].
From a "Best Practices" standpoint, I would assume that constructing an array like this in one query is a lot better than hitting the database multiple times to retrieve this data.
Thanks!
Neil
You can construct this array in one pass through the data returned by your query. In pseudo-code:
for each row
$user["FirstName"] = row->FirstName;
$user["LastName"] = row->LastName;
$user["Interests"][] = row->Interests;
$user["Contact"]["Email"] = row->Email;
$user["Contact"]["Phone"] = row->Phone;
next
The syntax $user["Interests"][] = $data is valid PHP code. It is equivalent to array_push($user["Interests"], $data).
My guess is that you would have to run separate queries and manually construct the array yourself. I don't personally know of any MySQL database resources in PHP that return rowsets like that in a multidimensional array the way you described.
A better way of doing this could be:
$query = "SELECT p.Id, p.FirstName, p.LastName, i.Name as Interests,
c.Email, c.Phone
FROM UserProfile p
LEFT JOIN UserInterest i ON p.Id = i.UserProfileId
LEFT JOIN UserContact c ON p.Id = c.UserProfileId
WHERE p.Id = 1";
$res = GIDb::pg_query($query);
if(pg_num_rows($res) > 0)
{
while($data = pg_fetch_assoc($res))
{
$id = $data['Id'];
$f_name = $data['FirstName'];
$l_name = $data['LastName'];
$interests = $data['Interests'];
$email = $data['Email'];
$phone = $data['Phone'];
}
}
//You can also directly use $data['Id'] etc.
Also don't forget to include in the beginning of the php code : include_once(dirname(__FILE__)."/../../class.GIDb.php");
EDIT :
This is for Postgres.
For MySQL use: mysql_query(), mysql_fetch_assoc(), mysql_num_rows()
Related
In my events calendar module for Silverstripe 3.1, i have added some additional fields to CalendarEvent. I want to make use of these in my template, but from my researches i have seen that events are exported as being CalendarDateTime so i can't use my additional fields.
I have found that in getStandardEvents function, there is a inner join which i think is causing the problem, but i can't figure it out to join the columns from CalendarEvent
$list = DataList::create('CalendarDateTime')
->filter(array(
'EventID' => [139, 140, 141, 143]
))
->innerJoin('CalendarEvent', "EventID = \"{CalendarEvent}\".\"ID\"")
->innerJoin("SiteTree", "\"SiteTree\".\"ID\" = \"{CalendarEvent}\".\"ID\"")
->where("Recursion != 1");
Note: In my code, i have some of the columns as variables, so i have written them as the values that are reffering to.
Here is the original code:
$list = DataList::create($datetimeClass)
->filter(array(
$relation => $ids
))
->innerJoin($eventClass, "$relation = \"{$eventClass}\".\"ID\"")
->innerJoin("SiteTree", "\"SiteTree\".\"ID\" = \"{$eventClass}\".\"ID\"")
->where("Recursion != 1");
I have tried to add the 'CalendarEvent' as a second parameter inside DataList::create(), but no result. I have the same output.
So how can i select columns from CalendarDateTime and CalendarEvent tables ?
Any help would be appreciated.Thanks
I have found a manual way:
$list = DB::query('SELECT * FROM "CalendarDateTime" INNER JOIN "CalendarEvent" ON "EventID" = "CalendarEvent"."ID" INNER JOIN "SiteTree" ON "SiteTree"."ID" = "CalendarEvent"."ID" WHERE "Recursion" != 1');
Any other solutions are welcome.
I have a list of users on my php application (using codeigniter). Each user may have completed a form with about 1000 or so total fields. The structure looks similar to this:
users
id|username|...
completed_form_fields
id|formid|userid|fieldkey|data
where field key is just a unique key for that particular form field, ie: "first_name"
I have a user search page where people can filter out specific users by the fields they chose (eye color, race, gender...) Then I need to display these fields so I would love (and currently have) an output like this:
$filteredmembers = array(
[0] = Object(
[id] => 1
[username] => kilrizzy
...
[fields] => Array(
[fname] => Jeff
[gender] => Male
...
Currently my script is obviously taking forever since I query all the members who filled out this form, then loop through each one to query all of their fields. THEN filter those out based on criteria + page / offset.
I know there needs to be a way to join these together in one query I am not familiar with
Simplified version of my very slow code:
function get_members(){
$this->db->select('u.*');
$this->db->from('users AS u');
$query = $this->db->get();
if ($query->num_rows() > 0){
$members = $query->result();
//Get fields from each user
foreach($members as $mk => $mv){
$fields = $this->get_form_fields($mv->id,1,true);
$members[$mk]->fields = $fields;
}
return $members;
}else{
return false;
}
}
function get_form_fields($uid,$form,$values=false){
$this->db->where('user', $uid);
$this->db->where('form', $form);
$query = $this->db->get('form_fields');
if ($query->num_rows() > 0){
$result = $query->result();
return $result;
}else{
return false;
}
}
There is a way but it gets over expensive the more you add fields. The same thing occurs with many CMS that choose to store additionnal user data in that form.
You can get a working search SQL using this:
SELECT
users.*,
firstname.data AS firstname,
lastname.data AS lastname,
eyecolor.data AS eyecolor,
FROM
users
LEFT JOIN completed_form_fields AS firstname ON firstname.userid = users.id AND firstname.fieldkey = "firstname"
LEFT JOIN completed_form_fields AS lastname ON lastname.userid = users.id AND lastname.fieldkey = "lastname"
LEFT JOIN completed_form_fields AS eyecolor ON eyecolor.userid = users.id AND eyecolor.fieldkey = "eyecolor"
WHERE
firstname.data LIKE '%searchdata%'
OR lastname.data LIKE '%searchdata%'
OR eyecolor.data LIKE '%searchdata%'
This method gets very big and expensive for the MySQL server the more you add tables. Therefore, i would recommend not to go more than 10-15 joins like that and then again, i'd profile it to make sure.
SELECT u.username, f.formid, f.userid, f.fieldkey, f.data FROM user AS u
LEFT JOIN completed_form_fields AS f
ON f.userid = u.id
you should look at indexing your userid, index(userid), via phpmyadmin or sql file
mysql-indexes
I'm not sure I completely understand the problem. You can use a single query to get all users, and all of their fields (basically, this is what Philip suggested):
SELECT u.username, f.*
FROM user AS u
INNER JOIN completed_form_fields AS f
ON f.userid = u.id
ORDER BY u.id, f.fieldkey
To filter the results, add a WHERE clause with the conditions. For example, to only get data from fieldkeys 'k1', 'k2' and 'k3':
SELECT u.username, f.*
FROM user AS u
INNER JOIN completed_form_fields AS f
ON f.userid = u.id
WHERE f.fieldkey IN ('k1', 'k2', 'k3')
ORDER BY u.id, f.fieldkey
Is this what you're looking for?
table 1 = events -> holds a list of events
table 2 = response -> holds a list of users responses has a foreign key of eid which corresponds with events table
I need to join the tables together so it can return an array similar to this on php.
array(
0 => array(
'title' =>'', //title of events table
'contents' =>'this is a demo', //contents of events table
'users' => array( //users comes from response table
0 = array(
'firstname'=>'John',
),
1 = array(
'firstname'=>'James',
)
)
)
);
can this be done? using mysql only? coz i know you can do it on php.
You can gather all the necessary data in MySQL with a single JOIN query.
However, PHP will not return an array like your example by default. You would have to loop over the query result set and create such an array yourself.
I'm pretty sure the answer is no, since mysql always returns a "flat" resultset. So, you can get all the results you're looking for using:
SELECT e.title, e.contents, r.firstname
FROM events e LEFT JOIN response r ON e.id = r.eid
ORDER BY e.id, r.id
And then massaging it into the array with php, but I imagine this is what you're doing already.
EDIT:
By the way, if you want 1 row for each event, you could use GROUP_CONCAT:
SELECT e.title, e.contents, GROUP_CONCAT(DISTINCT r.firstname ORDER BY r.firstname SEPARATOR ',') as users
FROM events e LEFT JOIN response r ON e.id = r.eid
GROUP BY e.id
Just as Jason McCreary said. For you convenience, here is the query you need (though the field names might not be matching your db structure, as you did not provide this information)
SELECT
*
FROM
events
LEFT JOIN
responses ON (events.id = responses.eid)
The SQL is:
SELECT events.id, events.title, events.contents,
response.id AS rid, response.firstname
FROM events LEFT JOIN response
ON events.id = response.eid
I thought I would show you how to massage the results in to the array as you wished:
$query = "SELECT events.id, events.title, events.contents, response.id AS rid, response.firstname
FROM events LEFT JOIN response ON events.id = response.eid";
$result = mysql_query($query);
$events = array();
while ($record = mysql_fetch_assoc($result)) {
if (!array_key_exists($record['id'], $events)) {
$events[$record['id']] = array('title' => $record['title'], 'contents' => $record['contents'], 'users' => array());
}
if ($record['rid'] !== NULL) {
$events[$record['id']]['users'][$record['rid']] = array('firstname' => $record['firstname']);
}
}
mysql_free_result($result);
So I am doing a search and I am using an implode in my select statement, which I find quite useful. Basically this search engine will have 3 different selects which will select different things based on different criteria and when I use my implode I get an error of invalid arguments passed.
Here is my sql statement:
$sql = "SELECT DISTINCT camp.title, camp.startDay, camp.typeOfCamp, camp.endDay,
camp.link FROM ((camp INNER JOIN gender ON camp.id = gender.camp_id)
INNER JOIN grades ON camp.id = grades.camp_id)
INNER JOIN interests ON camp.id = interests.camp_id
WHERE ((grades.year = '".implode('\' OR grades.year = \'',$age)."')
AND gender.gender = '".$gender."')
OR ((interests.activity = '".implode('\' OR interests.activity = \'',$array)."')
AND (grades.year = '".$age."' AND gender.gender = '".$gender."'))";
The second implode for the interests is where I began having my problem and $array is an array. Another thing I don't understand is that when I run my code I get the correct results, but I am still getting the error that I am passing invalid arguments.
You may believe what PHP says, normally. If it says it isn't an array you probably didn't pass an array.
And a small tip to save you some code: There is an IN() statement in MySQL:
$sql = "SELECT DISTINCT camp.title, camp.startDay, camp.typeOfCamp, camp.endDay,
camp.link FROM ((camp INNER JOIN gender ON camp.id = gender.camp_id)
INNER JOIN grades ON camp.id = grades.camp_id)
INNER JOIN interests ON camp.id = interests.camp_id
WHERE (grades.year IN(".implode(',', $age).")
AND gender.gender = '".$gender."')
OR (interests.activity IN('".implode("','", $array)."')
AND grades.year = ".$age." AND gender.gender = '".$gender."')";
To save the duplicate gender.gender = $gender and (maybe) optimize the query:
$sql = "SELECT DISTINCT camp.title, camp.startDay, camp.typeOfCamp, camp.endDay,
camp.link FROM ((camp INNER JOIN gender ON camp.id = gender.camp_id)
INNER JOIN grades ON camp.id = grades.camp_id)
INNER JOIN interests ON camp.id = interests.camp_id
WHERE gender.gender = '".$gender."'
AND (
grades.year IN(".implode(',', $age).")
OR (
interests.activity IN('".implode("','", $array)."')
AND grades.year = ".$age."
)
)";
Furthermore I think MySQL doesn't require to use all those parentheses for joins:
$sql =
"SELECT DISTINCT camp.title, camp.startDay, camp.typeOfCamp, camp.endDay,
camp.link
FROM camp
INNER JOIN gender ON camp.id = gender.camp_id
INNER JOIN grades ON camp.id = grades.camp_id
INNER JOIN interests ON camp.id = interests.camp_id
WHERE gender.gender = '".$gender."'
AND (
grades.year IN(".implode(',', $age).")
OR (
interests.activity IN('".implode("','", $array)."')
AND grades.year = ".$age."
)
)";
Now the query should be way better to understand.
You're trying to do everything in on big statement. Why not build your imploded array-strings before you include them in the SQL?
Chris, your problem is that you're not passing an array to the second parameter of implode().
If you see www.php.net/implode the second parameter must be of type ARRAY so that PHP can start joining your array together with your delimiter specified in the first parameter.
A good way to check the data type of your variable is to use var_dump() which will tell you if something is an array, or int..etc
There are two variables you're passing to implode() there. $age and $array, I am guessing that it's age and you need to modify your code a bit.
Pasting your whole code that's generating $array and $age would help us further to see where you're going wrong in creating those variables.
Hope this helps.
Since you are using strings:
OR (interests.activity IN ('".implode("','", $array)."')
will neaten up that line.
But I am worried about $age. You use it as if it is an array in:
WHERE ((grades.year = '".implode('\' OR grades.year = \'',$age)."')
but as a string variable:
AND (grades.year = '".$age."' AND gender.gender = '".$gender."'))";
I think you should be using the "IN" and implode() expressions for both places you're using $age if it is an array.
Apologies for all this code, anyhow Im re-working a query into the Zend query way of working, this is what I have so far:
$db = Zend_Registry::get ( "db" );
$stmt = $db->query('
SELECT recipe_pictures.picture_id, recipe_pictures.picture_filename, course.course_name, cuisines.name, recipes.id, recipes.Title, recipes.Method, recipes.author, recipes.SmallDesc, recipes.user_id, recipes.cuisine, recipes.course, recipes.Created_at, recipes.vegetarian, recipes.Acknowledgements, recipes.Time, recipes.Amount, recipes.view_count, recipes.recent_ips, guardian_writers.G_item, guardian_writers.G_type
FROM recipes
LEFT JOIN course ON recipes.course = course.course_id
LEFT JOIN recipe_pictures ON recipes.id = recipe_pictures.recipe_id
LEFT JOIN cuisines ON recipes.cuisine = cuisines.id
LEFT JOIN guardian_writers ON recipes.author = guardian_writers.G_author
WHERE recipes.id = ?', $id);
$stmt->setFetchMode(Zend_Db::FETCH_ASSOC);
$recipes = $stmt->fetchAll();
return $recipes;
That one above works, trying to get the Zend version properly, my effort is below.
$db = Zend_Registry::get ( "db" );
$select = $db->select()
->from(array('r' => 'recipes'))
->join(array('c' => 'course'),
'r.course = c.course_id')
->join(array('rp' => 'recipe_pictures'),
'r.id = rp.recipe_id')
->join(array('cui' => 'cuisines'),
'r.cuisine = cui.id')
->join(array('gw' => 'guardian_writers'),
'r.author = gw.G_author')
->where(' id = ? ', $id);
$recipes = $db->fetchRow($select);
return $recipes;
If anyone can spot an error Id be very grateful, thanks
Use joinLeft instead of join to produce left joins.
To fetch specific columns from a table, rather than all (*) use this:
->from(array('r' => 'recipes'), array('id', 'title', 'method'))
or
->joinLeft(array('rp' => 'recipe_pictures'),
'r.id = rp.recipe_id',
array('picture_id', 'picture_filename')
)
To fetch no columns from a table, pass an empty array as the third parameter.
The join method provides an sql INNER JOIN. If you want to get a LEFT JOIN you should use joinLeft.