I have three tables: "users", "posts", and "likes" almost formatted as:
For example the three table entries are:
users (two users): 1. uid: 12,
2. uid: 15.
, and
posts (three posts): 1. pid: 3, publisherId = 12, likers = 2,
2. pid: 6, publisherId = 12, likers = 0,
3. pid: 7, publisherId = 12, likers = 1.
, and
likes (three likes): 1. lid: 1, postId = 3, likerId = 12,
2. lid: 2, postId = 7, likerId = 15,
3. lid: 3, postId = 3, likerId = 15.
What I need is: To get all the posts in a multi dimensional array with an array for the unique publisher (user) and another array for the likers (users also). The output I am looking for is something like:
Array: (
post:(
pid = 3,
publisher = Array (uid = 12),
likers = Array (uid=12, uid=15)
),
post:( ....
)
).
I am already getting that with the following time consuming (I believe):
$sql = "SELECT dev_posts.* FROM posts";
if (!$result = mysql_query($sql)) die("Query failed.");
$response = array();
while($result_array = mysql_fetch_object($result)) {
$entries = array();
foreach($result_array as $key => $value) {
if ($key == "byUserId") {
$publisherID = $result_array->byUserId;
$anotherSql = "SELECT * FROM users WHERE users.uid = $publisherID";
if ($anotherResult = mysql_query($anotherSql)) {
$anothers = array();
while($anotherResult_array = mysql_fetch_object($anotherResult)) {
$another = array();
foreach($anotherResult_array as $anotherKey => $anotherValue) {
$another[$anotherKey] = $anotherValue;
}
$anothers[] = $another;
}
$entries[$key] = $anothers;
}
}
else if ($key == "likes") {
if ($value > 0){
$PID = $result_array->pid;
$anotherSql = "SELECT likes.*, users.* FROM likes LEFT JOIN users ON likes.likeUserId = users.uid WHERE $PID = likes.likePostId";
if ($anotherResult = mysql_query($anotherSql)) {
$anothers = array();
while($anotherResult_array = mysql_fetch_object($anotherResult)) {
$another = array();
foreach($anotherResult_array as $anotherKey => $anotherValue) {
$another[$anotherKey] = $anotherValue;
}
$anothers[] = $another;
}
$entries[$key] = $anothers;
}
}
else {
$entries[$key] = array();
}
}
else {
$entries[$key] = $value;
}
}
$posts[] = $entries;
}
Any suggestions are appreciated. I am still looking for join and left join solutions!
it really depends on what you're looking for:
user data for all posts:
SELECT user.*, post.*
FROM post
LEFT JOIN user ON (post.publisherid=user.id)
since it's only one publisher per post, this should give the user's data for each and every post.
liker ids
SELECT post.*, GROUP_CONCAT(likes.likerid) as likerids
FROM post
LEFT JOIN likes ON (likes.postid=post.pid)
GROUP BY post.pid
this will give you rows:
["pid" => 3, "publisherid" => 12, "likerids" => "15,17,19"]
and all you have to do in php then is:
$likerids = explode(',', $row['likerids']);
combine for fun and profit
of course, you can combine both queries into one. However, the second query only works well, if you only need the ids of likers. If you want the user data as well, it might be good, (depending on your actual use case), to collect the likerids first and fetch their user data later
SELECT *
FROM user
WHERE user.uid IN (15,17,19)
Also, you should REALLY REALLY REALLY use prepared statements to protect against sql injections. (this is not bold by accident! this is important) If you don't know what sql injections are, read it up. If anyone finds a query that's vulnerable to user provided input and sql injections, all your users' data can (and most likely will) leak into the darkness that is the internet.
Also, please use pdo or mysqli libraries for your database queries. the mysql library is deprecated and is gone in 7.[something] I believe.
update
There are a bunch of problems associated with fetching both sides of an m:n relation. I mean, essentially it's easy, just fetch it:
SELECT post.*, user.*
FROM post
LEFT JOIN likes ON (post.pid=likes.postid)
LEFT JOIN user ON (likes.likerid=user.uid)
ORDER BY post.pid
however, this will produce these rows:
pid1, publisherid1, userid1, username1
pid1, publisherid1, userid2, username2
...
pid2, publisherid2, userid1, username1
...
as you will notice, the post itself appears multiple times, once for each liker. This is a problem, which cannot be avoided by standard sql alone, because of the fundamentals of sql (being row-based).
This is essentially the data you want, but I suppose in a more aggregated form. This form also contains lots and lots of redundant data, especially assuming the post data is way bigger than the user data. To gather the data, you would have to check the pid for every row, if it's the same pid as in the row before, you somehow merge the records. ... But I would strongly advise against this approach.
I would also advise against using GROUP_CONCAT for every single field of user, although it might work. The problem is, that GROUP_CONCAT needs a delimiter, which YOU need to be different from any character in the username field (or any other field, you want to retrieve). This might or might not be a problem, but it's dirty nonetheless. In any case, you then would have to explode every of those aggregated fields in php, rebuild the users' data to build your wanted structure.
Another solution might be, to create a new field, that holds aggregated userdata as json or something, and with the intelligent use of GROUP_CONCAT and CONCAT one could create a hierarchical string for each row, that could be json itself. But this goes beyond this post. (Also I condone such use of databases that aren't made nor designed for this). There is however a JSON data type, that could be interesting ...
Ultimately, in those cases, you let the database server do the work that IMHO should be done by the client.
I would do this:
two queries, for limited number of users (because YAGNI)
first we're going to fetch the posts we want, we also add a count for likes and the publisher's user data are included as well (if you add a WHERE with data, that comes from outside the server like a browser, use prepared statements! also read up on SQL, if you don't understand all or parts of this query!) - I would assume, this is all the data you would show to a user at first. (With the power of caching, showing likers for distinct posts could be quite efficient.)
$pdo = new PDO('#yourdatabasestring#'); // rtfm!
$postresult = $pdo->query(
'SELECT p.*, '.
' pub.uid, pub.username, '.
' COUNT(likers.uid) as likecount '.
'FROM post p '.
'LEFT JOIN user as pub ON (pub.uid=post.publisherid) '.
'LEFT JOIN likes ON (post.pid=likes.postid) '.
'LEFT JOIN user as likers ON (likers.uid=likes.likerid) '
'GROUP BY p.pid '.
'LIMIT 50' // learn about offsets!!!
);
now, put all results into an array
$pids = []; // this will contain post ids for which we want to fetch likes
$posts = [];
while($post = $postresult->fetch()) {
$pids[] = $post['pid'];
$post['likers'] = []; // prepare for later
$posts[$post['pid']] = $post;
}
At this point, this array only contains the data, that was requested in the first query (post, user data of publisher). Next, we query for the likes, we use the temporarily stored post ids.*
$likers = $pdo->query(
'SELECT likes.postid, user.* '.
'FROM likes '.
'LEFT JOIN user ON (likes.likerid=user.uid) '.
'WHERE likes.postid IN ('.implode(',', $pids).')'
);
and fetching them and assigning them to the right post.
while($like = $likers->fetch()) {
$posts[$like['postid']]['likers'][] = $like;
}
now ... this solution should actually work for almost every sql database. GROUP_CONCAT doesn't provide any benefit here. Two queries are actually quite alright here. If you have a very large set of posts that you want to fetch at once, this might absolutely not be the right approach. For fairly small data sets (some hundred posts or so), this should be very much okay.
*) the WHERE clause could be replaced by WHERE postid IN ([first query with only poist.pid in select]). For certain use cases, this could be preferable.
word of advice
However, for the usual web case, I can't imagine anyone wanting to see more than 50 posts at once with already displayed userdata, likers' data and stuff. don't try to show everything at once. fetch what's necessary, try to cluster information (as I did with the $pids) to reduce the number of queries. Doing a few well-designed and short-running queries in general beats doing many queries (as in your original code), but also is more appropriate than running one huge query, where most data will (on average) be irrelevant.
I am trying to create a login function that would allow me to specify what table I want to select the data from, and also what fields I want to select.
I have managed to pass the table value to the function, and select data from the desired table, but my tables have different fields. How can I specify in the query it is one table select: user_id, user_first_name, user_password , or if it is the other table select member_id, member_first_name, member_password.
Here is what I have coded so far:
public function logIn($id, $password, $table){
$dataArray = array();
$t = ($table === 'employers') ? 'employers' : 'members');
$query = "SELECT user_id, user_first_name, user_password FROM $t";
$stmt = $this->link->prepare($query);
if ($stmt->execute())
{
$result = $stmt->get_result();
while($row = $result->fetch_array(MYSQLI_ASSOC))
{
$dbuser_id = $row['user_id'];
$dbpassword = $row['user_password'];
$dbuser_first_name = $row['user_first_name'];
}
//and so on...
Also, one quick question: Is this a bad programming practice, or should I have 2 login(members, and users)?
Thanks :)
The Single Responsibility Principle in programming is intended to prevent things like this from happening - the goal is for one class/function to have one responsibility, rather than having to manage 2 (or potentially more) as you have here. In short, it is a bad programming practice and you should have 2 login functions like you said. After making 2 different functions, a switch statement might help to check the case of a member logging in versus a user.
Note: There are exceptions to this. However, in your case, it would make more sense to separate the two.
Here is a link to further explain SRP and SOLID design principles:
Single Responsibility Principle on TutsPlus
I have a Laravel 4 app with three tables in question: a client table, an order table with client_id column, and an order_outputs table with an order_id column.
My question is whether the following code is efficient enough for what I'm trying to do, which is find whether the logged in user has ordered a specific output_id . Best practices would be highly appreciated.
public function getEdit($id) {
$order = ShopOrder::find($id);
$client = ShopClient::find($order->client_id);
$clientorders = ShopOrder::where('client_id', $client->id)->get();
$orderlist =array();
foreach ($clientorders as $co)
{
$orderlist[] =($co->id);
}
$orderoutputs = ShopOrderoutput::whereIn('order_id',$orderlist)->where('output_id', '22')->get();
$orderoutputslist = array();
foreach ($orderoutputs as $list)
{
$orderoutputslist[] = $list->output_id;
}
if (in_array(22, $orderoutputslist))
{
echo 'finally';
}
}
Thanks.
If all you want to do is find out whether the user has an order with a specific output_id, I would combine the queries into one Joined query.
I assume that the order_outputs table is related to the order_outputs table, by a foreign key order_id.
I am also assuming that you are trying to find if the client/user has ever had an order with order_output output_id = 22
I'm not familiar with the syntax for the db queries in laravel.
In plain SQL this would be.
SELECT id FROM orders
INNER JOIN order_output ON order.id = order_output.order_id
INNER JOIN clients on order.client_id = client.id
WHERE client.id = $client_id
AND order_output.output_id = 22;
Then count your result set and see if there is at least one.
This way your database engine will do the work for you instead of your php code having to loop through result sets.
So lets say I had two tables, and one (lets call it person) had a field called type that stored an integer linking to another table (lets call it types with a field called id. What would be more performance enhancing?
One larger query storing all the values of type and then looping over the person table and grabbing a value from the array we got from the types query.
On every loop of person grab a value from types
Use the JOIN query to join the type onto the person query.
Note: in the examples I used some fake variables such as $db (obviously). Just think of these like a CodeIgniter database class or something...
Example Code of BP #1:
<?php
$types = $this->db->query("SELECT * FROM `types`")->result_array();
$query = $this->db->query("SELECT * FROM `person`");
foreach($query->result_array() as $value)
{
$type = $types[$value['type']];
}
Example Code of BP #2: (Edited to add of form of caching)
<?php
$query = $this->db->query("SELECT * FROM person");
$types = [];
foreach($query->result_array() as $value)
{
if(empty($types[$value['type']]))
{
$types[$value['type']] = $this->db->query("SELECT * FROM types WHERE id = {$value['type']} LIMIT 1")->row_array();
}
$type = $types[$value['type']];
}
Example Code of BP #3: (NB: I haven't used JOIN at all so please forgive any errors in the query, and feel free to fix it up...)
<?php
$query = $this->db->query("SELECT * FROM person JOIN types ON person.type = types.id");
foreach($query->result_array() as $value)
{
// type is already there for us in $value
}
Note: person and types both (will) have over 100k+ rows, we're expecting.
The join approach (option 3) should certainly be the most efficient - the other two approaches are essentially re-inventing the processing that the database is designed to do in the first place, and all the relevant data has to be sent to the web server before it can be done!
I have quite a complicated situation here. I can't find a better way to solve this without putting a SELECT query inside a loop that rolls over 70000 times when I enter that page (don't worry, I use array_chunk to split the array into pages). I guess this would be a resource killer if I use a query here. Because of this, here I am, asking a question.
I have this big array I need to loop on:
$images = scandir($imgit_root_path . '/' . IMAGES_PATH);
$indexhtm = array_search('index.htm', $images);
unset($images[0], $images[1], $images[$indexhtm]);
Now I have an array with all file names of the files (images) in my IMAGES_PATH. Now the problem comes here:
Some of these images are registered on the database, because registered users have their images listed on my database. Now I need to retrieve the user_id based on the image name that the array above gives me.
Inside a loop I simply did this:
foreach ($images as $image_name)
{
$query = $db->prepare('SELECT user_id FROM imgit_images WHERE image_name = :name');
$query->bindValue(':name', $image_name, PDO::PARAM_STR);
$query->execute();
$row = $query->fetch(PDO::FETCH_ASSOC);
$user_id = $row['user_id'];
echo $user_id;
}
This works just fine, but the efficiency equals to 0. Using that user_id I plan on getting other stuff from the imgit_users table, such as username, which would require another query inside that loop.
This is too much and I need a simpler way to deal with this.
Is there a way to get those user_ids before going inside the loop and use them IN the loop?
This is the table structure from imgit_images:
While this is the schema for imgit_users:
Something like this would work (I'm not sure if it's possible to prepare the WHERE IN query since the # of values is unknown... Else, make sure you sanatize $images):
$image_names = "'".implode("', '", $images)."'";
$query = $db->prepare("SELECT img.user_id, image_name, username
FROM imgit_images img
INNER JOIN imgit_users u ON u.user_id = img.user_id
WHERE image_name IN(".$image_names.")");
$query->execute();
while($row = $query->fetch(PDO::FETCH_ASSOC))
{
echo $row['user_id']."'s image is ".$row['image_name'];
}
You might need to tweak it a little (haven't tested it), but you seem to be able to, so I'm not worried!
Not sure if it is going to help, but I see a couple of optimizations that may be possible:
Prepare the query outside the loop, and rebound/execute/get result within the loop. If query preparation is expensive, you may be saving quite a bit of time.
You can pass an array as in Passing an array to a query using a WHERE clause and obtain the image and user id, that way you may be able to fragment your query into a smaller number of queries.
Can you not just use an INNER JOIN in your query, this way each iteration of the loop will return details of the corresponding user with it. Change your query to something like (i'm making assumptions as to the structure of your tables here):
SELECT imgit_users.user_id
,imgit_users.username
,imgit_users.other_column_and_so_on
FROM imgit_images
INNER JOIN imgit_users ON imgit_users.user_id = imgit_images.user_id
WHERE imgit_images.image_name = :name
This obviously doesn't avoid the need for a loop (you could probably use string concatenation to build up the IN part of your where clause, but you'd probably use a join here anyway) but it would return the user's information on each iteration and prevent the need for further iterations to get the user's info.
PDO makes writing your query securely a cinch.
$placeholders = implode(',', array_fill(0, count($images), '?'));
$sql = "SELECT u.username
FROM imgit_images i
INNER JOIN imgit_users u ON i.user_id = u.id
WHERE i.image_name IN ({$placeholders})";
$stmt = $db->prepare($sql);
$stmt->execute($images);
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// use $row['username']
}
Create a string of comma separated ?s and write them into IN's parentheses. Then pass the array of images to execute(). Easily done, and now all of your desired data is available in a single resultset from a single query. (Add additional columns to your query's SELECT clause as needed.)