Combine select statements with php and sort by time - php

So I have 5 separate select statements.
<?
$stmt=$db->prepare('SELECT * FROM table1 WHERE username = :username');
$stmt->bindParam(':user', $username);
$stmt->execute();
$row = $stmt->fetchAll();
$stmt=$db->prepare('SELECT * FROM table2 WHERE username = :username');
$stmt->bindParam(':user', $username);
$stmt->execute();
$row = $stmt->fetchAll();
$stmt=$db->prepare('SELECT * FROM table3 WHERE username = :username');
$stmt->bindParam(':user', $username);
$stmt->execute();
$row = $stmt->fetchAll();
$stmt=$db->prepare('SELECT * FROM table4 WHERE username = :username');
$stmt->bindParam(':user', $username);
$stmt->execute();
$row = $stmt->fetchAll();
$stmt=$db->prepare('SELECT * FROM table5 WHERE username = :username');
$stmt->bindParam(':user', $username);
$stmt->execute();
$row = $stmt->fetchAll();
?>
I need to combine all the results, organize all the results based on time with the newest time first, and then echo a message based on which table the result came from.
Note each table has a row time.
Example results:
<p>This result was posted 5 minutes ago and came from table 2</p>
<p>This result was posted 10 minutes ago and came from table 5</p>
<p>This result was posted 1 day 6 hours ago and came from table 1</p>
<p>This result was posted 2 weeks ago and came from table 3</p>
<p>This result was posted 3 weeks ago and came from table 1</p>
UPDATES:
Selecting * is an absolute requirement, which is why using mysql is not a viable solution. Mysql can't do full table joins, and instead mirrors the practice by doing cross joins of the tables. However if you understand the woe's of cross joins, you will understand when I say some of these tables will return thousands of results for a user, making cross joins not viable as cross joins will create countless duplicate rows for each entry, and having thousands of entries makes for millions and tens of millions of rows when doing a cross join. PHP seems to be the only solution.
I need a solution that wil use php to combine the selects and sort them based on time NOT a mysql solution.

You can define a new column in each table to hold the tablename. And then this kind of query can solve your problem:
SELECT username,timestamp,tableName from table1 union
select username,timestamp,tableName from table2 order by 2 desc

I think the problem should be solved where it belongs. This is obviously a db problem.
But as you have other concerns then you can try first fetching all rows from tables to an array. All tables should be selected with an order by descending timestamp field. Define a pointer for every table which shows the newest item in the related table. In your loop find the newest record amongst the rows which these pointers pointing to. and increase this pointer after printing out the info. Hope it helps. Here is an example:
$stmt=$db->prepare('SELECT * FROM table1 WHERE username = :username order by timestamp desc');
$stmt->bindParam(':username', $user);
$stmt->execute();
$row[0] = $stmt->fetchAll();
$stmt=$db->prepare('SELECT * FROM table2 WHERE username = :username order by timestamp desc');
$stmt->bindParam(':username', $user);
$stmt->execute();
$row[1] = $stmt->fetchAll();
// table lengts
$table_len[0]=sizeof($row[0]);
$table_len[1]=sizeof($row[1]);
//initialize every table pointers to 0
$table_pointer[0]=0;
$table_pointer[1]=0;
while ($table_pointer[0]<$table_len[0] ||
$table_pointer[1]<$table_len[1]){
// find the newest time
$time1=strtotime($row[0][$table_pointer[0]]['timestamp']);
$time2=strtotime($row[1][$table_pointer[1]]['timestamp']);
$tableWithNewestTime=findNewestTime($table_len,$table_pointer,$time1,$time2);
$timeDif=time()-strtotime($row[$tableWithNewestTime][$table_pointer[$tableWithNewestTime]]['timestamp']);
echo "This result was posted ".$timeDif." s ego and posted from table".($tableWithNewestTime+1)."<br>";
// increase the table pointer
$table_pointer[$tableWithNewestTime]=$table_pointer[$tableWithNewestTime]+1;
}
function findNewestTime(array $table_len, array $table_pointer, $time1,$time2){
if ($table_pointer[0]>=$table_len[0])
return 1;
if ($table_pointer[1]>=$table_len[1])
return 0;
if($time1<$time2)
return 1;
else
return 0;
}

Related

How do I count unique rows in php pdo?

Here's my usual way of counting rows...
$query = "SELECT * FROM users";
$stmt = $db->prepare($query);
$stmt->execute();
$count = $stmt->rowCount();
This will count all rows, even if I use a WHERE clause, it'll still count every row that meets that condition. However, let's say I have a table, we'll call it tokensEarned (that's my actual table name). I have the following data...
user_id = 1,2,4,5,8,8,2,4,3,7,6,2 (those are actual rows in my table - clearly, user 1 has 1 entry, 2 has three entries, etc.) In all, I have 12 entries. But I don't want my query to count 12. I want my query to count each user_id one time. In this example, my count should display 8.
Any help on this? I can further explain if you have any specific questions or clarification you need. I would appreciate it. Thank You.
The following query will yield the distinct user count:
$query = "SELECT COUNT(DISTINCT user_id) AS cnt FROM users";
$stmt = $db->prepare($query);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo "distinct user count: " . $row['cnt'];
It isn't possible to get all records and the distinct count in a single query.
Whether you use the query above or you return all the actual distinct rows really depends on whether you need the full records. If all you need are the counts, then it is wasteful to return the data in the records, and what I gave above is probably the best option. If you do need the data, then selecting all distinct rows might make more sense.
You can use distinct in mysql to select only unique fields in your table.
$query = "SELECT distinct user_id FROM users";
$stmt = $db->prepare($query);
$stmt->execute();
$count = $stmt->rowCount();
Change your query to the following, this way you only shows the unique user_id:
$query = "SELECT DISTINCT user_id FROM users";

PHP Get info from another table

How can I get a row's info from another table without having to SELECT each loop?
//get posts from "posts" table
$stmt = $dbh->prepare("SELECT * FROM posts WHERE user_id = :user_id");
$stmt->bindParam(':user_id', $userId);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as $row) {
echo $row['post'];
//get poster's full name from "users" table
$stmt = $dbh->prepare("SELECT * FROM users WHERE user_id = :user_id");
$stmt->bindParam(':user_id', $row['poster_id']);
$stmt->execute();
$result2 = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($result2 as $row2) {
echo $row2['full_name'];
}
}
how can I make this code more efficient and faster?
imagine if i have 1000 posts and each is posted by a different user. i need to get the full name of that user that posted the post. right now, i need to SELECT 1000 times because of those 1000 users. it seems so inefficient right now. how can i make it better?
I heard join might work? what are the solutions?
SELECT * FROM posts
JOIN users ON posts.user_id = users.id
WHERE posts.user_id = :user_id.
You are right, joining the users table onto your posts query will be faster.
Something else you can do to increase performance is to cache the results of your query in something like memcache and then clear the cache when a post is added or deleted. That way you don't need to hit your db every time this data is needed.

Select random row from table making sure that row has not been rated

I'm selecting a random row from a table with sequential records:
$stmt = $db->prepare("SELECT COUNT(*) FROM $table");
$stmt->execute();
$stmt->bind_result($total);
$stmt->fetch();
$stmt->close();
$rand = mt_rand(1,$total);
$try = 0;
do {
$stmt = $db->prepare("SELECT id,link,(select count(*) from $table2 where img_id=? AND ip=?) FROM $table WHERE id = ?");
$stmt->bind_param("isi",$rand,$ip,$rand);
$stmt->execute();
$stmt->bind_result($id,$link,$count);
$stmt->fetch();
$stmt->close();
$try++;
} while ($try < 10 && $count > 0);
I'm checking if a user has rated a particular thing before based on their IP.
I get a random number between the first record and the last one with mt_rand(1,$total), then I select that row.
However, the user may have already rated that particular item before, then I need to 're-select' or try for another random row, and here I try 10 times before giving up in the program.
Any way in SQL to do this better or make sure I select one they have never rated before without the whole inefficient try do while thingy? I can't use ORDER BY RAND() because it's too slow.

Select random row from table with deleted records

If I have a table with 3 rows with IDs 1,3,5 because rows with ID 2 and 4 were deleted, how do I make sure I select a row that exists?
$stmt = $db->prepare("SELECT COUNT(*) FROM table");
$stmt->execute();
$stmt->bind_result($numRows);
$stmt->fetch();
$stmt->close();
$random = mt_rand(1,$numRows);
$stmt = $db->prepare("SELECT link FROM table WHERE id=$random");
This won't ever select row with id 5, and also will select one that doesn't exist (2).
If the number of rows are small (and you are sure that it will stay that way), you can use ORDER BY RAND()
(Please note that this will create performance problems with big tables).
Other way is first counting how many rows are there
SELECT COUNT(*) AS total FROM table;
then pick a random number
$rand = rand(1, $total);
and select that row with limit
SELECT * FROM table LIMIT $rand, 1;
U can use a SQLstatement with EXISTS
SELECT link
FROM table
WHERE EXISTS (SELECT link
FROM table
WHERE id = $random);
If you just want a random row and don't care about the id, then you could use:
SELECT link FROM table
ORDER BY RAND()
LIMIT 1
For large numbers of rows (10000+), then you may need to implement another solution, as this query can be slow. This site has a good explanation and alternative solutions
If you want to follow your approach then you have to do some changes in your query.
1.) Query one : select id from table. // It will give you array of existing id.
2.) You have to use array_rand(). and use your second query.
Example :
$stmt = $db->prepare("SELECT ID FROM table");
$result = $stmt->fetchAll();
$random = array_rand(array_flip($result), 1);
$stmt = $db->prepare("SELECT link FROM table WHERE id=$random");
You could select one, randomly order, like this:
SELECT link FROM table ORDER BY RAND() LIMIT 1
UPDATE:
You should benchmark the different solutions you have, but I'm thinking this one could be nice with large amount of rows:
$stmt = $db->prepare("SELECT COUNT(*) FROM table");
$stmt->execute();
$stmt->bind_result($numRows);
$stmt->fetch();
$stmt->close();
$random = mt_rand(1,$numRows);
$stmt = $db->prepare("SELECT link FROM table WHERE id>=$random LIMIT 1");
$stmt->execute();
$stmt->bind_result($link);
if(!$link){
$stmt = $db->prepare("SELECT link FROM table WHERE id<$random LIMIT 1");
$stmt->execute();
$stmt->bind_result($link);
}

MySQL PDO, selecting a single row from a result-set

If I run a query that returns multiple rows, is there a way I can select just one row out of that result?
So if I do something like
SELECT * FROM table WHERE number = 10
and it returns 33 results, is there a way I can go through those one at a time instead of returning the whole result set at once, or just return, for example, row 5 of the result set?
I have read about scrollable cursors but it seems they don't work on MySQL, although that seems to be what I am looking for....
I am using PDO with MySQL and PHP. I hope this makes sense, if not I will try and explain better.
Edit: This worked for what I wanted. Thanks.
$stmt = $dbh->prepare("SELECT * FROM $table WHERE user_points = '$target' ORDER BY tdate DESC LIMIT $count,1");
is there a way I can select just one row out of that result?
Yes there is, you can use LIMIT:
SELECT * FROM table WHERE number = 10 LIMIT 1;
$sql= "SELECT * FROM $table WHERE user_points = '$target' ORDER BY tdate";
$stmt= $pdo -> prepare($sql);
$stmt->execute();
$data = $stmt ->fetchAll();
//You asked about getting a specific row 5
//rows begin with 0. Now $data2 contains row 5
$data2 = $data[4];
echo $data2['A_column_in_your_table'];//row 5 data

Categories