I'm writing a web app where people can add and vote on ideas. When outputting the ideas I want them to be ordered by their total vote count or time added, but always have a rank based on the vote count.
This is what I have now:
function get_ideas($status, $sortby, $count, $page){
$offset = ($page - 1) * $count;
$dbh = db_connect();
if($sortby === 'popular'){
$stmt = $dbh->prepare("
SELECT i.idea_id, i.idea_datetime, i.user_id, i.idea_title, i.idea_text,
i.idea_vote_count, u.user_name, #curRank := #curRank + 1 AS rank
FROM ideas i
JOIN (SELECT #curRank := :rankoffset) AS q
JOIN users u ON i.user_id = u.user_id
WHERE idea_status = :idea_status
ORDER BY idea_vote_count DESC
LIMIT :count
OFFSET :offset;");
} else {
$stmt = $dbh->prepare("HOW DO I DO THIS???");
}
$stmt->bindParam(':idea_status', $status, PDO::PARAM_STR);
$stmt->bindParam(':rankoffset', $offset, PDO::PARAM_INT);
$stmt->bindParam(':count', $count, PDO::PARAM_INT);
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = NULL;
$dbh = NULL;
return $result;
}
The code in the "if" block works as intended - it returns an array ordered by "idea_vote_count" with correct ranks.
However, I have no idea what the second statement should be. Ordering by time added would be achieved easily enough by just changing "idea_vote_count" in the "ORDER BY" clause to "idea_id". How do I get the ranks, though? I couldn't think of nor find a solution which didn't involve storing the ranks in the table itself.
Hopefully I've explained my problem clearly, but just in case:
How do I get a table like this:
idea_id | idea_vote_count
1 | 20
2 | 40
3 | 30
4 | 5
To produce output like this:
rank | idea_id | idea_vote_count
4 | 4 | 5
2 | 3 | 30
1 | 2 | 40
3 | 1 | 20
Also, I'm kind of new to PHP and MySQL, so if you spot any other problems, please, point them out.
I look forward to your advice. Thanks :)
EDIT: For Strawberry:
My ideas table:
CREATE TABLE `ideas`(
`idea_id` int NOT NULL AUTO_INCREMENT,
`idea_datetime` datetime NOT NULL,
`user_id` int NOT NULL,
`idea_title` varchar(48) NOT NULL,
`idea_text` text NOT NULL,
`idea_vote_count` int NOT NULL DEFAULT 0,
`idea_status` varchar(16) NOT NULL DEFAULT 'active',
PRIMARY KEY(`idea_id`),
FOREIGN KEY(`user_id`) REFERENCES users(`user_id`))
ENGINE=INNODB;
The sample ideas have been generated by following script. I have then manually changed the idea_vote_count of row 100 to 5.
$i = 1;
set_time_limit(150);
while (i<101) {
$datetime = date('Y-m-d h:m:s');
$dbh->exec(INSERT INTO `ideas` (`idea_datetime`, `user_id`, `idea_title`, `idea_text`, `idea_vote_count`, `idea_status`)
VALUES ('{$datetime}', $i, 'Title{$i}', 'Text{$i}', $i, 'active');
$i++
sleep(1);
}
This is what I ended up with after incorporating Strawberry's SQL into my function:
function get_ideas($status, $sortby, $count, $page){
$offset = ($page - 1) * $count;
$dbh = db_connect();
if($sortby === 'popular'){
$stmt = $dbh->prepare("
SELECT i.idea_id, i.idea_datetime, i.user_id, i.idea_title, i.idea_text, i.idea_vote_count, u.user_name, #curRank := #curRank + 1 AS rank
FROM ideas i
JOIN (SELECT #curRank := :rankoffset) AS q
JOIN users u
ON i.user_id = u.user_id
WHERE idea_status = :idea_status
ORDER BY idea_vote_count DESC
LIMIT :count
OFFSET :offset;");
} else {
$stmt = $dbh->prepare("
SELECT n.*
FROM (
SELECT i.idea_id, i.idea_datetime, i.user_id, i.idea_title, i.idea_text, i.idea_vote_count, u.user_name, #curRank := #curRank + 1 AS rank
FROM ideas i
JOIN (SELECT #curRank := :rankoffset) AS q
JOIN users u
ON i.user_id = u.user_id
WHERE idea_status = :idea_status
ORDER BY idea_vote_count DESC
LIMIT :count
OFFSET :offset) n
ORDER BY idea_id DESC;");
}
$stmt->bindParam(':idea_status', $status, PDO::PARAM_STR);
$stmt->bindParam(':rankoffset', $offset, PDO::PARAM_INT);
$stmt->bindParam(':count', $count, PDO::PARAM_INT);
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = NULL;
$dbh = NULL;
return $result;
}
As you can see, the function takes $count and $page as arguments (usually these have value of $_REQUEST['count/page']) and calculates the offset and limit based on them. This is very important, because I don't want to show all the ideas to users at the same time, I want to split them into several pages. However, this messes with the select/ranking SQL in the following way:
When $page = 1 and $count = 100 you get LIMIT 100 OFFSET 0 and the script works as intended - it shows the most recent row (row 100) as the first one ranked 96 (only rows 1, 2, 3, 4 have lower vote count), followed by the other recent rows ranked 1, 2, 3 and so on.
However, when $page = 1 and $count = 10 you get LIMIT 10 OFFSET 0 and the script outputs row 99 first, because it's the highest rated one, but not the most recent. Row 100 becomes the first result in the result set when $page = 10 (the lowest rated and OLDEST rows, despite the fact that row 100 is the most recent).
I could technically select the entire table and then handle the pagination in PHP, but I fear what the performance impact would be.
EDIT2: I have moved OFFSET and LIMIT into the outer SELECT and now everything works as it's supposed to. The side effect of this is, that the inner SELECT selects the entire table, but hopefully it won't grow too big for the server to handle. This is the solution I'm sticking with for the time being. It's based on Strawberry's SQL, so I'll mark that as the answer. Thanks, Strawberry :)
Here's one idea, using purely MySQL, but it's probably better just to return the data set with the ranks and then do any further ordering in the application code...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(idea_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,idea_vote_count INT NOT NULL
);
INSERT INTO my_table VALUES
(1 ,20),
(2 ,40),
(3 ,30),
(4 ,5);
SELECT n.*
FROM
( SELECT x.*
, #i:=#i+1 rank
FROM my_table x
, (SELECT #i:=0) vars
ORDER
BY idea_vote_count DESC
) n
ORDER
BY idea_id DESC;
+---------+-----------------+------+
| idea_id | idea_vote_count | rank |
+---------+-----------------+------+
| 4 | 5 | 4 |
| 3 | 30 | 2 |
| 2 | 40 | 1 |
| 1 | 20 | 3 |
+---------+-----------------+------+
Related
I'd like to count the first 3 users who has the most attributed lines.
SQL Table:
ID | IdUser | Type |
-----------------------
0 | 1 | like |
1 | 1 | like |
2 | 4 | dislike |
3 | 5 | dislike |
4 | 1 | like |
5 | 4 | like |
6 | 5 | like |
8 | 4 | like |
9 | 4 | like |
10 | 3 | like |
11 | 5 | like |
12 | 9 | like |
Result should be:
idUser[1] with 3 times "like" and 0 "dislike" (3-0 = 3 points)
idUser[4] with 3 times "like" and 1 "dislike" (3-1 = 2 points)
idUser[5] with 2 times "like" and 1 "dislikes (2-1 = 1 point )
So what I'm trying to do is getting idUser 1 (3 points), then idUser4 (2 points) and finally idUser 5 (1 point) with their points.
I've tried different ways but none have worked.
Here I've tried to create a two-dimensional array with all data and then get the highest values but I couldn't do the second part.
Table 'users' has all users of the website
table 'points' has likes and dislikes recorded
$sqlUsers = "SELECT * FROM users";
$resultUsers = $conn->query($sqlUsers);
$recordsArray = array(); //create array
while($rowUsers = $resultUsers->fetch_assoc()) {
$idUser = $rowUsers['id'];
//COUNT LIKES OF THE USER
$sqlLikes = "SELECT COUNT(id) AS numberLikes FROM points WHERE idCibledUser='$idUser' AND type='like'";
$resultLikes = $conn->query($sqlLikes);
$rowLikes = $resultLikes->fetch_assoc();
//COUNT DISLIKES OF THE USER
$sqlDislikes = "SELECT COUNT(id) AS numberDislikes FROM points WHERE idCibledUser='$idUser' AND type='dislike'";
$resultDislikes = $conn->query($sqlDislikes);
$rowDislikes = $resultDislikes->fetch_assoc();
//GET POINTS BY SUBTRACTING DISLIKES FROM LIKES
$points = $rowLikes['numberLikes'] - $rowDislikes['numberDislikes'];
$recordsArray[] = array($idUser => $points);
}
If you ultimately just need the total points without a breakdown of likes and dislikes (not totally clear from your question):
SELECT IdUser, SUM(IF(Type='like',1,-1)) AS points
FROM users
GROUP BY IdUser
ORDER BY points DESC
LIMIT 3
If you want the complete breakdown:
SELECT IdUser,
SUM(IF(Type='like',1,-1)) AS points,
SUM(IF(Type='like',1,0)) as likes,
SUM(IF(Type='dislike',1,0)) as dislikes
FROM users
GROUP BY IdUser
ORDER BY points DESC
LIMIT 3
Explanation
Let's say I wanted to count the total number of rows where the Type column had the value 'like'. I could execute the following:
SELECT COUNT(*) AS cnt FROM users WHERE Type = 'like'
But another, perhaps less direct way, is the following:
SELECT SUM(IF(Type = 'like', 1, 0)) AS cnt FROM users
In the above SQL the Type column in each row is being examined and if equal to 'like', then the value of 1 is assigned to the column otherwise 0. Then all these 1's and 0's are added up using the SUM function. By adding up all the 1's, you are in effect counting the number of rows that had 'like' in the Type column. The second method allows you process the number of likes and dislikes with one pass:
SELECT SUM(IF(Type = 'like', 1, 0)) AS likes,
SUM(IF(Type = 'dislike', 1, 0)) AS dislikes
FROM users
But what if you wanted to get the above counts on a user by user basis? That is the purpose of the GROUP BY clause:
SELECT IdUser,
SUM(IF(Type = 'like', 1, 0)) AS likes,
SUM(IF(Type = 'dislike', 1, 0)) AS dislikes
FROM users
GROUP BY IdUser
The "score" or difference between the likes and dislikes can be computed if we assign the value of 1 to a column if it contains 'like' and a value of -1 if it contains 'dislike' (or isn't 'like') and then sum these values up:
SELECT IdUser,
SUM(IF(Type = 'like', 1, -1)) AS points,
SUM(IF(Type = 'like', 1, 0)) as likes,
SUM(IF(Type = 'dislike', 1, 0)) as dislikes
FROM users
GROUP BY IdUser
Finally, if you want the three top scores, sort the returned rows in descending order (ORDER BY points DESC) and keep only the first 3 rows returned (LIMIT 3):
SELECT IdUser,
SUM(IF(Type = 'like', 1, -1)) AS points,
SUM(IF(Type = 'like', 1, 0)) as likes,
SUM(IF(Type = 'dislike', 1, 0)) as dislikes
FROM users
GROUP BY IdUser
ORDER BY points DESC
LIMIT 3
See the solution below if you need to get likes/dislikes often, points table is being updated often and data relevance is important i.e. you don't want to cache the results.
Create another table like user_points_summary which will have 2 columns e.g. IdUser and Points. IdUser to be unique in this table, the Points recalculation (per user) must be triggered on adding new rows into the points table.
If you need likes/dislikes breakdown then this table will have 3 columns - IdUser (not unique anymore), likes_count, dislikes_count. And then the same - trigger this table update on inserting/updating/deleting rows in the points table.
If you go with the second option (with likes/dislikes breakdown) - here's an example of a create table statement:
CREATE TABLE `user_points_summary` (
`IdUser` int(11) NOT NULL,
`likes_count` int(11) NOT NULL DEFAULT '0',
`dislikes_count` int(11) NOT NULL DEFAULT '0',
KEY `idx_user_points_summary_IdUser` (`IdUser`)
) ENGINE=InnoDB;
Then you can add the following trigger to your users table which will add zero likes/dislikes on adding new users:
CREATE TRIGGER `users_AFTER_INSERT` AFTER INSERT ON `users` FOR EACH ROW
BEGIN
INSERT INTO `user_points_summary` VALUE (NEW.`IdUser`, 0, 0);
END
Then add the following triggers to the points table to update user_points_summary likes/dislikes count:
DELIMITER $$
CREATE TRIGGER `points_AFTER_INSERT` AFTER INSERT ON `points` FOR EACH ROW
BEGIN
IF NEW.`Type` = 'like' THEN
UPDATE `user_points_summary` SET `likes_count` = `likes_count` + 1 WHERE `IdUser` = NEW.`IdUser`;
ELSEIF NEW.`Type` = 'dislike' THEN
UPDATE `user_points_summary` SET `dislikes_count` = `dislikes_count` + 1 WHERE `IdUser` = NEW.`IdUser`;
END IF;
END $$
CREATE TRIGGER `points_AFTER_UPDATE` AFTER UPDATE ON `points` FOR EACH ROW
BEGIN
IF NEW.`Type` = 'dislike' AND OLD.`Type` = 'like' THEN
UPDATE `user_points_summary`
SET
`likes_count` = `likes_count` - 1,
`dislikes_count` = `dislikes_count` + 1
WHERE `IdUser` = `OLD`.`IdUser`;
ELSEIF NEW.`Type` = 'like' AND OLD.`Type` = 'dislike' THEN
UPDATE `user_points_summary`
SET
`dislikes_count` = `dislikes_count` - 1,
`likes_count` = `likes_count` + 1
WHERE `IdUser` = OLD.`IdUser`;
END IF;
END $$
CREATE TRIGGER `points_AFTER_DELETE` AFTER DELETE ON `points` FOR EACH ROW
BEGIN
IF OLD.`Type` = 'like' THEN
UPDATE `user_points_summary`
SET `likes_count` = `likes_count` - 1
WHERE `IdUser` = `OLD`.`IdUser`;
ELSEIF OLD.`Type` = 'dislike' THEN
UPDATE `user_points_summary`
SET `dislikes_count` = `dislikes_count` - 1
WHERE `IdUser` = OLD.`IdUser`;
END IF;
END $$
DELIMITER ;
Then you can use the following query to get user points with likes and dislikes count:
SELECT *, `likes_count` - `dislikes_count` AS `points`
FROM `user_points_summary`
ORDER BY `points` DESC
LIMIT 3
Lets consider the following table-
ID Score
1 95
2 100
3 88
4 100
5 73
I am a total SQL noob but how do I return the Scores featuring both IDs 2 and 4?
So it should return 100 since its featured in both ID 2 and 4
This is an example of a "sets-within-sets" query. I recommend aggregation with the having clause, because it is the most flexible approach.
select score
from t
group by score
having sum(id = 2) > 0 and -- has id = 2
sum(id = 4) > 0 -- has id = 4
What this is doing is aggregating by score. Then the first part of the having clause (sum(id = 2)) is counting up how many "2"s there are per score. The second is counting up how many "4"s. Only scores that have at a "2" and "4" are returned.
SELECT score
FROM t
WHERE id in (2, 4)
HAVING COUNT(*) = 2 /* replace this with the number of IDs */
This selects the rows with ID 2 and 4. The HAVING clause then ensures that we found both rows; if either is missing, the count will be less than 2.
This assumes that id is a unique column.
select Score
from tbl a
where a.ID = 2 -- based off Score with ID = 2
--include Score only if it exists with ID 6 also
and exists (
select 1
from tbl b
where b.Score = a.Score and b.ID = 6
)
-- optional? ignore Score that exists with other ids as well
and not exists (
select 1
from tbl c
where c.Score = a.Score and c.ID not in (2, 6)
)
This is how my table looks like:
id | name | value
-----------------
1 | user1| 1
2 | user2| 1
3 | user3| 3
4 | user4| 8
5 | user5| 6
6 | user7| 4
7 | user8| 9
8 | user9| 2
What I want to do is to select all the other users, in one query, who's value is user1's value lower than it's value plus 3, higher than it's value minus 3 or equal to it's value.
Something like this:
$result = mysqli_query($con,"SELECT * FROM users WHERE value<'4' OR value>'-2'") or die("Error: ".mysqli_error($con));
while($row = mysqli_fetch_array($result))
{
echo $row['name'].'<br/>';
}
The problem is that users1's value can vary every time the query is run.
Sorry for lame names, but this should work:
NOTE: I named table with your data as "st".
SELECT b.user, a.value as "user1val", b.value as "otheruservalue" FROM st as a
join st as b
on a.user = "user1" and a.user != b.user
where
(b.value > (a.value - 3)) and (b.value < (a.value + 3))
We get unique pairs of user1's value and other user's value by joining same table. After that we just do some simple comparison to filter rows with suitable values.
$user1 = mysql_fetch_assoc(mysql_query("SELECT `value` FROM `users` WHERE id='1'"));
$result = mysql_query("SELECT * FROM `users` WHERE value<'".$user1['value']."+3' OR value>'".$user1['value']."-3'");
Or nested queries :
$result = mysqli_query($con, "select * from `users` where `value` < (select `value` from `users` where `name`='user1')+3 OR `value` > (select `value` from `users` where `name`='user1')-3");
I have a table with 3 columns
---QID---TEXT---CID---
I would like to find 20 rows(QID and TEXT) for each distinct CID. I have already prepared string $cid so that I can use WHERE IN statement.
SELECT * FROM questions q1
WHERE cid=(SELECT cid
FROM questions q2
WHERE q2.cid IN ($cids)
GROUP BY q2.cid)
ORDER BY q1.qid LIMIT 20
Thank you!
Simple query:
$query = 'SELECT QID, TEXT FROM yourDb.yourTable WHERE CID = '.$cid;
or, if $cid is an array:
$query = 'SELECT QID, TEXT FROM yourDb.yourTable WHERE CID IN('.implode(',',$cid).')';
To get to the results:
$pdo = new PDO('mysql:host=yourDBServer','login','password');
if (!$stmt = $pdo->query($query))
{
die('query failed');
}
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
For more info on what you can do with the PDO object, refer to the manual
A quick fix (but not a good one) might be:
$q = 'SELECT QID, TEXT FROM yourDB.yourTB WHERE CID = '.$cid.' LIMIT 20';
In the case of CID IN(1,2,3), I'm not sure if there's a strait forward way of doing this. All I can think of is using unions. Mayby this page can help you out with that.
A fugly fix might also be to ORDER BY CID ASC, and insted of using fetchAll(), do this:
$query = 'SELECT CID,QID, TEXT FROM yourDb.yourTable WHERE CID IN('.implode(',',$cid).')';
//execute query, same as above: $stmt holds results
$results = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC))
{
if (!is_array($results[$row['CID']))
{
$results[$row['CID']] = array();
}
if (count($results[$row['CID']]) < 20)
{
$results[$row['CID']][] = $row;
}
}
This way, the $results array, will have a key for each CID that was found, and that key's value will be an array of up to 20 records...
The problem is in using the = operator and passing a set of values instead of single one. Change your query to the following and try again
SELECT * FROM questions q1
WHERE cid
IN $cids
ORDER BY q1.qid LIMIT 20
The following snippet uses the MySQL variable trick to assign a number for each row per CID. To keep the example simple I've limited the amount of returned rows to 2 per CID.
select cid
, qid
, text
from (
select if(#last_cid = cid, #rn := #rn + 1, #rn := 1) as rn
, (#last_cid := cid)
, cid
, qid
, text
from YourTable yt
cross join
(select #rn := 0, #last_cid := -1) r
) as SubQueryAlias
where rn < 3;
Data setup:
create table YourTable (QID int, TEXT varchar(50), CID int);
insert YourTable values
(1, 'hi', 1),
(1, 'hi', 1),
(2, 'hi', 1),
(2, 'hi', 1),
(3, 'hi', 2),
(4, 'hi', 2),
(4, 'hi', 2),
(5, 'hi', 3);
This returns up to two rows per CID:
+------+------+------+
| cid | qid | text |
+------+------+------+
| 1 | 1 | hi |
| 1 | 1 | hi |
| 2 | 3 | hi |
| 2 | 4 | hi |
| 3 | 5 | hi |
+------+------+------+
My issue is that I need to paginate data from this query:
function search($search_term, $limit, $offset)
{
$id = $this->auth->get_user_id();
$query = $this->db->query("
SELECT user_id,
first_name,
cars_name,
cars_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY first_name ASC
");
$search_data = array();
foreach ($query->result() as $row) {
$search_data[$row->user_id]['name'] = $row->first_name;
$search_data[$row->user_id]['cars'][$row->cars_id] = array(
'cars_name' => $row->cars_name);
}
return $search_data;
}
A sample data table / query response would be:
1 JOE HONDA 123
1 JOE TOYOTA 124
2 MAC VW 125
2 MAC HONDA 126
2 MAC TESLA 127
3 STU SUBARU 128
3 STU KIA 129
-----------
Page 1
-----------
1 JOE HONDA 123
TOYOTA 124
2 MAC VW 125
HONDA 126
------------
Page 2
------------
3 STU SUBARU 128
KIA 129
If I enter a limit and offset at the end of MySQL query
...
LIMIT $limit
OFFSET $offset;
");
the limit and offset are applied to the total number of rows, not the the number of rows grouped by user.
I've tried using GROUP BY but was unable to make it work.
My goal is to make the query as above but LIMIT and OFFSET the query by a number of rows that counts users, not all rows.
Any ideas?
I don't see a way to do this in one query. My solution would be to get the count of unique ID's using a group by query with the same parameters:
SELECT COUNT(1) AS uid_count
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
GROUP BY user_profiles.user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
Then fetch the uid_countmysql_num_rows and use that to calculate pagination variables for the above query.
The solution really is to use a GROUP BY clause:
SELECT SQL_CALC_FOUND_ROWS
user_id,
first_name,
cars_name,
cars_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
GROUP BY user_id
ORDER BY first_name ASC
LIMIT 100
The order is important. GROUP BY first, then ORDER BY, and then OFFSET/LIMIT.
Notice the SQL_CALC_FOUND_ROWS up there? After the query has executed, if you want to get the total row count (including those who aren't returned because of the LIMIT clause), just use:
SELECT FOUND_ROWS() AS `count`
And fetch the count column.
However, like you said, the rows will collapse and you will lose some cars_name and cars_id values.
Another solution is to use GROUP_CONCAT, then split it in PHP:
SELECT
user_id,
first_name,
GROUP_CONCAT(cars_name SEPARATOR ','),
GROUP_CONCAT(cars_id SEPARATOR ','),
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY first_name ASC
LIMIT 100
This would give you something like:
1 JOE HONDA,TOYOTA 123,124
2 MAC VW,HONDA,TESLA 125,126,127
3 STU SUBARU,KIA 128,129
If you want to get a list like this
Page 1
----------------------
1 JOE HONDA 123
1 JOE TOYOTA 124
Page 2
----------------------
2 MAC VW 125
2 MAC HONDA 126
2 MAC TESLA 127
Page 3
----------------------
3 STU SUBARU 128
3 STU KIA 129
Forget about limit, do this instead:
A - First retrieve a list of user id's and insert that into a temp table
CREATE TEMPORARY TABLE `test`.`temp_user_ids` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INTEGER UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
)
ENGINE = MEMORY
B - Next insert the relavant user_id's into the table.
INSERT INTO temp_user_ids
SELECT null, user_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY user_id DESC /*insert in reverse order !*/
The lowest user_id is the last_insert_id in the temptable, and the temp_table
items are in sequential order.
C - Set the SQL #var #current_id to the last_insert_id in the temp_table.
SELECT #current_id:= LAST_INSERT_ID()
D - Next select relevant rows from the table, using only the user_id you want.
SELECT count(*) as row_count,
up.user_id,
first_name,
group_concat(cars_name) as car_names,
group_concat(cars_id) as car_ids,
FROM user_profiles up
LEFT JOIN cars
ON cars.id_fk = up.user_id
INNER JOIN temp_user_ids t
ON (t.user_id = up.user_id)
WHERE t.id = #current_id
GROUP BY up.user_id
ORDER BY cars.id
E - Now lower the #current_id
SELECT #current_id:= #current_id - 1;
F - And repeat step D and E until there's no more rows to be had.
The first field row_count tells you the number of rows aggregated in the fields
car_names and car_ids. You can separate these fields by using php's explode.