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 have updated my original post based on what I learned from your comments below. It is a much simpler process than I originally thought.
require '../database.php';
$pdo = Database::connect();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "SELECT * FROM Orders WHERE id = 430";
$q = $pdo->prepare($sql);
$q->execute(array($id));
$data = $q->fetch(PDO::FETCH_ASSOC);
echo 'Order Num: ' . $data['id'] . '<br>';
$sql = "SELECT * FROM Order_items
JOIN Parts ON Parts.id = Order_Items.part_id
WHERE Order_Items.orders_id = 430";
$q = $pdo->prepare($sql);
$q->execute(array($line_item_id));
$data = $q->fetch(PDO::FETCH_ASSOC);
while ($data = $q->fetch(PDO::FETCH_ASSOC))
{
echo '- ' . $data['part_num'] . $data['qty'] . "<br>";
}
Database::disconnect();
Unfortunately, only my first query is producing results. The second query is producing the following ERROR LOG: "Base table or view not found: 1146 Table 'Order_items' doesn't exist" but I am expecting the following results.
Expected Results from Query 1:
Order Num: 430
Expected Results from Query 2:
- Screws 400
- Plates 35
- Clips 37
- Poles 7
- Zip ties 45
Now that I understand where you are coming from, let's explain a couple of things.
1.PDO and mysqli are two ways of accessing the database; they essentially do the same things, but the notation is different.
2.Arrays are variables with multiple "compartments". Most typical array has the compartments identified by a numerical index, like:
$array[0] = 'OR12345'; //order number
$array[1] = '2017-03-15'; //order date
$array[2] = 23; //id of a person/customer placing the order
etc. But this would require us to remember which number index means what. So in PHP there are associative arrays, which allow using text strings as indexes, and are used for fetching SQL query results.
3.The statement
$data = $q->fetch(PDO::FETCH_ASSOC)
or
$row = $result->fetch_assoc()
do exactly the same thing: put a record (row) from a query into an array, using field names as indexes. This way it's easy to use the data, because you can use field names (with a little bit around them) for displaying or manipulating the field values.
4.The
while ($row = $result->fetch_assoc())
does two things. It checks if there is a row still to fetch from the query results. and while there is one - it puts it into the array $row for you to use, and repeats (all the stuff between { and }).
So you fetch the row, display the results in whatever form you want, and then loop to fetch another row. If there are no more rows to fetch - the loop ends.
5.You should avoid using commas in the FROM clause in a query. This notation can be used only if the fields joining the tables are obvious (named the same), but it is bad practice anyway. The joins between tables should be specified explicitly. In the first query you want the header only, and there is no additional table needed in your example, so you should have just
SELECT *
FROM Orders
WHERE Orders.Order_ID = 12345
whereas in the second query I understand you have a table Parts, which contains descriptions of various parts that can be ordered? If so, then the second query should have:
SELECT *
FROM Order_items
JOIN Parts ON Parts.ID = Order_Items.Part_ID
WHEERE Order_Items.Order_ID = 12345
If in your Orders table you had a field for the ID of the supplier Supplier_ID, pointing to a Suppliers table, and an ID of the person placing the order Customer_ID, pointing to a Customers table, then the first query would look like this:
SELECT *
FROM Orders
JOIN Suppliers ON Suppliers.ID = Orders.Supplier_ID
JOIN Customers ON Customers.ID = Orders.Customer_ID
WHERE Orders.Order_ID = 12345
Hope this is enough for you to learn further on your own :).
In my app, the user can type in an indefinite amount of categories to search by. Once the user hits submit, I am using AJAX to call my PHP script to query my DB and return the results that match what the user defined for the categories.
My category column is separated as so for each row: "blue,red,yellow,green" etc.
I have two questions:
How can I pass an array to MySQL (like so: [blue,yellow,green]) and then search for each term in the categories column? If at-least one category is found, it should return that row.
Can MySQL add weight to a row that has more of the categories that the user typed in, therefor putting it further to the top of the returned results? If MySQL cannot do this, what would be the best way to do this with PHP?
Thanks for taking the time and looking at my issue.
For the part 1 you can use the function below:
<?php
function createquery($dataarray){
$query="select * from table where ";
$loop=1;
foreach($dataarray as $data)
{
$query.="col='$data'";
if(count($dataarray)<$loop-1){
$query.=' or ';
}
$loop++;
}
return $query;
}
?>
This will return the long query.
use this some like this:
mysql_query("select * from table where category in (".implode($yourarray,',').")");
1)
Arrays are not passed to a MySQL database. What's past is a query which is a string that tells the database what action you want to preform. An example would be: SELECT * FROM myTable WHERE id = 1.
Since you are trying to use the values inside your array to search in the database, you could preform a foreach loop to create a valid SQL command with all those columns in PHP, and then send that command / query to the database. For example:
$array = array('blue', 'red', 'yellow', 'green');
$sql = "SELECT ";
foreach ($array as $value)
{
$sql .= $value.", ";
}
$sql .= " FROM myTable WHERE id = 1";
IMPORTANT! It is highly recommended to used prepared statements and binding your parameters in order not to get hacked with sql injection!
2)
You are able to order the results you obtained in whichever way you like. An example of ordering your results would be as follows:
SELECT * FROM myTable WHERE SALARY > 2000 ORDER BY column1, column2 DESC
I have 2 tables, one is called post and one is called followers. Both tables have one row that is called userID. I want to show only posts from people that the person follows. I tried to use one MySQL query for that but it was not working at all.
Right now, I'm using a workaround like this:
$getFollowing = mysqli_query($db, "SELECT * FROM followers WHERE userID = '$myuserID'");
while($row = mysqli_fetch_object($getFollowing))
{
$FollowingArray[] = $row->followsID;
}
if (is_null($FollowingArray)) {
// not following someone
}
else {
$following = implode(',', $FollowingArray);
}
$getPosts = mysqli_query($db, "SELECT * FROM posts WHERE userID IN($following) ORDER BY postDate DESC");
As you might imagine im trying to make only one call to the database. So instead of making a call to receive $following as an array, I want to put it all in one query. Is that possible?
Use an SQL JOIN query to accomplish this.
Assuming $myuserID is an supposed to be an integer, we can escape it simply by casting it to an integer to avoid SQL-injection.
Try reading this wikipedia article and make sure you understand it. SQL-injections can be used to delete databases, for example, and a lot of other nasty stuff.
Something like this:
PHP code:
$escapedmyuserID = (int)$myuserID; // make sure we don't get any nasty SQL-injections
and then, the sql query:
SELECT *
FROM followers
LEFT JOIN posts ON followers.someColumn = posts.someColumn
WHERE followers.userID = '$escapedmyuserID'
ORDER BY posts.postDate DESC
Here's my issue: I have 3 tables, with overlapping information (specifically, the username) in each. Except the username row isn't named the same thing in every table. Because the username is specific to the user, it makes sense to get all the other information about the user based on the username. Here's what I have. (The first function returns the query, the second function returns the information in an array (or is supposed to, anyway).
function get_user_by_id($id) {
global $connection;
$query = "SELECT * FROM ownerOrganization, owner, queue_acl";
$query .=" WHERE owner.ownerId=ownerOrganization.ownerId";
$query .=" AND owner.ownerId=queue_acl.user_id";
$query .= " AND owner.ownerId ='{$id}'";
$result_set = mysql_query($query);
confirm_query($result_set);
if ($user = mysql_fetch_array($result_set)) {
return $user;
} else {
return NULL;
}
}
function get_user_id() {
if (isset($_GET['ownerId'])) {
return get_user_by_id($_GET['ownerId']);
}
}
But when I do something like, $sel_user = get_user_id(); on another page, it doesn't actually pull up any of the selected users information... I assume that this is happening because my syntax regarding working with multiple tables is incorrect. Anyway, any input would be much appreciated.
To use JOINS, take this snipcode in example :
$query = "SELECT * FROM (ownerOrganization INNER JOIN owner ON owner.ownerId=ownerOrganization.ownerId) INNER JOIN queue_acl ON owner.ownerId=queue_acl.user_id";
$query .=" WHERE owner.ownerId ='{$id}'";
Regards
I was typing what more or less what #MTranchant wrote. I would suggest renaming your columns for easier query authoring and to avoid confusion. For instance your ownerOrganization.ownerid could be named oo_ownerid, and the other columns in the table could follow that naming convention.
Also, have you run the query against the database with a hard-coded $id that you know exists?
Lastly in the query string being sent to the next page, does a ownerId parameter appear that looks like "&ownerId="?