LIMIT MySQL subquery using variable value before LEFT JOIN - mysql

I have this MySQL table posts:
id | content | parentid | userid
--------------------------------------
01 | Post test #1 | 0 | 1
02 | Post test #2 | 0 | 1
03 | Comment #1 | 1 | 2
04 | Comment #2 | 1 | 1
05 | Post test #3 | 0 | 3
06 | Comment #3 | 1 | 2
07 | Comment #4 | 2 | 5
08 | Comment #5 | 5 | 6
09 | Comment #6 | 1 | 4
10 | Post test #4 | 0 | 4
This is just an example for stackoverflow
Now I need to LIMIT comments for each post and so far I have used this query:
SELECT
`posts`.`id` AS `post_id`,
`posts`.`content` AS `post_content`,
`posts`.`parentid` AS `post_parentid`,
`posts`.`userid` AS `post_userid,
`comments`.`id`, 0 AS `comment_id`,
`comments`.`content` AS `comment_content`,
`comments`.`parentid` AS `comment_parentid`,
`comments`.`userid` AS `comment_userid,
IF( IFNULL( `comments`.`id`, 0 ) > 0, "comment", "post" ) AS `contenttype`
FROM `posts` AS `posts`
LEFT JOIN ( SELECT "" AS `hello` ) AS `useless` ON #pid := `posts`.`id`
LEFT JOIN ( SELECT
`posts`.`id` AS `id`,
`posts`.`id` AS `id`,
`posts`.`id` AS `id`,
`posts`.`id` AS `id`
FROM `posts`
WHERE `posts`.`parentid` = #pid
LIMIT 10
) AS `comments`ON `comments`.`parentid` = `posts`.`id`
WHERE
`posts`.`userid` = {USERID}
To archive this I have joined an useless "table" just to update #pid (parentid) variable.
Is this the only way to LIMIT subquery results? I don't like the idea of that useless JOIN.
What if I have to LIMIT posts in the example above without affecting the comments LIMIT. Can you please give me a better query?

The real reason of posting this question was to load 10 comments with 10 sub-comments for each comment. On the question I have asked to load posts & comments so the idea is the same.
The example posted in my question doesn't works because the subquery will executed before the variable #pid gets updated.
Because I'm using PHP then I'm posting here the solution in MySQL & PHP for this situation.
1 - First let's load posts with this SQL query
SELECT
`posts`.`id` AS `id`,
`posts`.`content` AS `content`,
`posts`.`parentid` AS `parentid`,
`posts`.`userid` AS `userid
FROM `posts` AS `posts`
WHERE
`posts`.`userid` = {USERID}
AND
`posts`.`parentid` = '0'
ORDER BY `posts`.`id` DESC
LIMIT 10
2 - Store posts information in $posts array:
$posts = [];
while ( $row = $result->fetch_object() )
{
$posts[] = (object) [ "id" => $row->id,
"content" => $row->content,
"userid" => $row->userid,
"comments" => []
];
}
3 - Prepare SQL to load comments:
$size = count( $posts );
$sql = "";
for ( $i = 0; $i < $size; $i ++ )
{
$sql .= ( $sql != "" ? "UNION ALL " : "" )
. "( "
. "SELECT "
. "`comments`.`id` AS `id`, "
. "`comments`.`content` AS `content`, "
. "`comments`.`parentid` AS `parentid`, "
. "`comments`.`userid` AS `userid "
. "FROM `posts` AS `comments` "
. "WHERE "
. "`comments`.`parentid` = '" . $post[ $i ]->id . "' "
. "ORDER BY `comments`.`id` ASC "
. "LIMIT 10 "
. ") ";
}
4 - After executing the $sql code let's store comments for each post:
while ( $row = $result->fetch_object() )
{
$posts[ $row->parentid ]->comments[] = (object)[
"id" => $row->id,
"content" => $row->content,
"userid" => $row->userid,
];
}
As you can see this can be used for also comments (instead of posts ) & sub-comments (instead of comments). MySQL variables are not helpful this time. Of course to create a pagination you have to add additional field (replies) in the table and update that during comment creation.
If someone has a better solution is welcomed.

Related

MySQL Relational Select Issue

I'm trying to select all jobs that a user has been added to, but exclude jobs that they have declined. I've been looking into NOT EXISTS, but I haven't been able to exclude declined jobs with that.
Table: declined
+---------+---------+---------+----------+
| id | job_id | user_id | declined |
+---------+---------+---------+----------+
| 15 | 223 | 25 | 1 |
| 100 | 156 | 50 | 1 |
| 125 | 651 | 60 | 1 |
+---------+---------+---------+----------+
Current query. Selects all jobs they've been added to, as well as jobs they have declined.
SELECT
job.*, applicants.*, declined.*
FROM job
JOIN applicants ON job.job_id = applicants.job_id
LEFT OUTER JOIN declined ON job.job_id = declined.job_id
WHERE applicants.user_id = '" . $userId . "' AND applicants.recruited = 1
Failed attempt. This obviously does not select any records.
SELECT
job.*, applicants.*, declined.*
FROM job
JOIN applicants ON job.job_id = applicants.job_id
LEFT OUTER JOIN declined ON job.job_id = declined.job_id
WHERE applicants.user_id = '" . $userId . "' AND applicants.recruited = 1 AND declined.declined = 0
you can use NOT IN to exclude the declined jobs:
SELECT
job.*, applicants.*
FROM job
JOIN applicants ON job.job_id = applicants.job_id
WHERE applicants.user_id = '" . $userId . "' AND applicants.recruited = 1
AND job.job_id not in (
SELECT DISTINCT job_id from declined where user_id = '" . $userId . "' and declined = 1
)
One more thing - not sure what language you're using, but you shouldn't set $userId like that, use a parameterized query, it's much safer than building the query like that.
If you want to use NOT EXISTS
SELECT
job.*, applicants.*
FROM job, applicants
WHERE job.job_id = applicants.job_id
AND NOT EXISTS
(SELECT 1 FROM declined WHERE job.job_id = declined.job_id AND declined.user_id = applicants.user_id AND declined.declined = 1)
AND applicants.user_id = '" . $userId . "'
AND applicants.recruited = 1

NHL standings in SQL

I have an SQL query that produces team standings based off the old NHL format. The first section of the code gets the top 3 teams in each division, and the 2nd bit gets the rest and sorts them by points/differential.
This can be seen here: http://rgmgstandings.tk
Here is my SQL query:
("(SELECT *, 1 as `SortKey` from `standings_east`
WHERE pts = (select max(pts)
from standings_east as t
where t.`div` = standings_east.`div`))
UNION ALL
(select *, 2 as SortKey from `standings_east`
where team not in
(select team from standings_east
where pts = (select max(pts)
from standings_east as t
where t.`div` = standings_east.`div`)))
order by SortKey, pts desc, diff desc")
If you visit my website, and look at the standings for the Western Conference (blue banner), you will notice 3 teams in 'CEN' that have the same amount of points (Chicago, Winnipeg, Columbus)
I want the query to select only ONE team from that division based on whoever has the most 'Wins/W'.
The correct standings should be:
Edmonton (NW) 80
Anaheim (PAC) 74
Columbus (CEN) 71
Dallas (PAC) 73
Chicago (CEN) 71
Winnipeg (CEN) 71
How can I accomplish this?
Query
select team,`div`,pts,1 as sortOrder
from
( -- note use parentheses to avoid mysql error 1221
(select team,`div`,pts,#cen:=team from `standings_west` where `div`='CEN' order by pts desc limit 1)
union all
(select team,`div`,pts,#pac:=team from `standings_west` where `div`='PAC' order by pts desc limit 1)
union all
(select team,`div`,pts,#nw:=team from `standings_west` where `div`='NW' order by pts desc limit 1)
) xDerived1
cross join (select #cen='',#pac='',#nw='') params
union
select team,`div`,pts,sortOrder
from
( select team,`div`,pts,2 as sortOrder
from `standings_west`
where team!=#cen and team!=#pac and team!=#nw
order by pts desc
limit 3
) xDerived2
order by sortOrder,pts desc;
Results
+----------+-----+-----+-----------+
| team | div | pts | sortOrder |
+----------+-----+-----+-----------+
| EDMONTON | NW | 80 | 1 |
| ANAHEIM | PAC | 74 | 1 |
| WINNIPEG | CEN | 71 | 1 |
| DALLAS | PAC | 73 | 2 |
| CHICAGO | CEN | 71 | 2 |
| COLUMBUS | CEN | 71 | 2 |
+----------+-----+-----+-----------+
Stored Proc
The following depicts a stored proc just to show it in case you are having problems and need it.
drop procedure if exists xdoit;
delimiter $$
create procedure xdoit()
begin
select team,`div`,pts,1 as sortOrder
from
( -- note use parentheses to avoid mysql error 1221
(select team,`div`,pts,#cen:=team from `standings_west` where `div`='CEN' order by pts desc limit 1)
union all
(select team,`div`,pts,#pac:=team from `standings_west` where `div`='PAC' order by pts desc limit 1)
union all
(select team,`div`,pts,#nw:=team from `standings_west` where `div`='NW' order by pts desc limit 1)
) xDerived1
cross join (select #cen='',#pac='',#nw='') params
union
select team,`div`,pts,sortOrder
from
( select team,`div`,pts,2 as sortOrder
from `standings_west`
where team!=#cen and team!=#pac and team!=#nw
order by pts desc
limit 3
) xDerived2
order by sortOrder,pts desc;
end$$
delimiter ;
call stored proc
call xdoit();
A few comments here.
First, your sqlfiddle had data from the west, but a query from the east. Based on table names, I suggest you have all your data in one table not two and have a column for east or west.
The query uses a cross join to merely establish variables for grabbing the division leaders so those division leaders are excluded for the sortOrder=2 teams.
Tweak as necessary for tie-breaks (ie: the teams with 71 points) regarding your implemention of DIFF in comments to #Clockwork
Ask if you have any questions.
The following is the multi_query php solution I came up with for you based on your posting of a comment in that chat room.
The only solution I could come up with was the following based on clearly the results were coming back in two result sets. Thus the while loop that is driven by next_result(). The first result set has the top 3 rows, the 2nd result set has the twelve rows that follow. That is just the way PHP seems to see it.
Also note that in the PHP part, since I seemed to be dealing with a multi_query, I took advantage of that and passed the mysql parameters in versus doing a cross join to pick them up.
PHP
<!DOCTYPE html>
<html lang="en">
<head>
<title>RGMG: Standings</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>
<body>
<?php
//mysqli_report(MYSQLI_REPORT_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
error_reporting(E_ALL); // report all PHP errors
ini_set("display_errors", 1);
try {
$mysqli= new mysqli('localhost', 'dbUser', 'thePassword', 'theDbName');
echo "<table><div><br><br>"; // note you had this line a little bit wrong
// notice below the concat of the $sql and the multi_query()
$sql = "set #cen:='',#pac:='',#nw:=''; ";
$sql .= "select *
from
( select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,1 as sortOrder
from
( -- note use parentheses to avoid mysql error 1221
(select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,#cen:=team from `standings_west` where `div`='CEN' order by pts desc, win desc, diff desc limit 1)
union all
(select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,#pac:=team from `standings_west` where `div`='PAC' order by pts desc, win desc, diff desc limit 1)
union all
(select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,#nw:=team from `standings_west` where `div`='NW' order by pts desc, win desc, diff desc limit 1)
) xDerived1
union all
select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,sortOrder
from
( select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,2 as sortOrder
from `standings_west`
where team!=#cen and team!=#pac and team!=#nw
order by pts desc
) xDerived2
) xDerived3
order by sortOrder,pts desc";
echo "<div class='container'>";
echo "<img src='http://i.imgur.com/sjDHhIV.png' width='100%' alt='West'>";
echo "<table class='table table-condensed'>
<tr class='top'>
<th class='rank'></th>
<th class='team'>TEAM</th>
<th>DIV</th>
<th>GP</th>
<th>W</th>
<th>L</th>
<th class='otl'>OTL</th>
<th class='pts'>PTS</th>
<th>GF</th>
<th>GA</th>
<th>DIFF</th>
<th>GPG</th>
<th>GAA</th>
";
$counter=1;
$mysqli->multi_query($sql);
while(true) {
if ($result = $mysqli->store_result()) {
while ($row = $result->fetch_assoc()) {
$gpg = ($row['goalsF']);
$gaa = ($row['goalsA']);
if ($row['gp'] != 0 ){
$gpg = ($row['goalsF'] / $row['gp']);
$gaa = ($row['goalsA'] / $row['gp']);
}
else {
$row['gp'] = "";
}
echo "<tr>
<td class='rank'>" . "$counter" . "</td>
<td class='team'>" . $row['team'] . "</td>
<td>" . $row['div'] . "</td>
<td>" . $row['gp'] . "</td>
<td>" . $row['win'] . "</td>
<td>" . $row['lose'] . "</td>
<td class='otl'>" . $row['otl'] . "</td>
<td class='pts'>" . $row['pts'] . "</td>
<td>" . $row['goalsF'] . "</td>
<td>" . $row['goalsA'] . "</td>
<td>" . $row['diff'] . "</td>
<td>" . round($gpg, 2) . "</td>
<td>" . round($gaa, 2) . "</td>";
$counter++;
}
$result->free();
}
if ($mysqli->more_results()) {
$mysqli->next_result();
}
else {
break;
}
}
echo "</table></div>";
$mysqli->close();
} catch (mysqli_sql_exception $e) {
throw $e;
}
?>
</body>
</html>
First i love NHL and from your query we can say the table you are showing us is standings_east,so let's try this:
select team,DIV,GP,W,L,OTL,PTS,GF,GA,DIFF,GPG,GAA,1 as sort_key
from standings_east t
where exists
(select 1
from
(select DIV,max(PTS) as PTS,max(W) as W from standings_east
group by DIV) a
where t.DIV=a.DIV and t.PTS=a.PTS and t.W=a.W
)
UNION ALL
select team,DIV,GP,W,L,OTL,PTS,GF,GA,DIFF,GPG,GAA,2 as sort_key
from standings_east t
where not exists
(select 1
from
(select DIV,max(PTS) as PTS,max(W) as W from standings_east
group by DIV) a
where t.DIV=a.DIV and t.PTS=a.PTS and t.W=a.W
)
order by sort_key,PTS DESC
i think there is some better way but this is the one like yours the most and the easiest to understand just add a group by on your code and Merry Christmas

Group by with limit for 2 tables

I have this MYSQL query
select * from {$this->table} sc
join db_sc_profile scp
on scp.subcategory_id = sc.subcategory_id
join db_profile p
on p.profile_id = scp.profile_id
where sc.subcategory_id in (" . implode(',', $subcategories) . ")
and p.profile_id in (" .implode(',', $profiles) .
group by p.profile limit ####
this query doesn't work with this line group by p.profile limit ####. So
i want to group by p.profile but limit for 12/count(profiles).
Can i do that ? please any help.
NB : I'm using Codeigniter 2
Input example
[subcategories] = Array
(
[0] => 101
[1] => 102
[2] => 103
)
[profiles] => Array
(
[0] => 5
[1] => 6
)
they are two tables in database linked by a third table
profile = [profile_id, profile_name]
subcategory = [sc_id, sc_name,...]
profile_subcategory = [profile_id, sc_id]

Select rows from a table where all values of an array exist

I have this query that retreives a list of id's + NAME:
$sql = "SELECT id FROM #__table1 ";
$sql .= " WHERE (";
foreach($explode_tags as $k=>$explode_tag) {
$sql .= "name = ".$db->Quote(trim($explode_tag));
if(($k+1) != count($explode_tags))
$sql .= " OR ";
}
$sql .= ")";
$db->setQuery($sql);
$results = $db->loadResultArray();
The result is an array like this:
keywordID | NAME
1 cat
2 dog
3 horse
Now I have this table2:
id | ItemID | keywordID
1 4 1
2 4 2
3 4 3
4 6 1
5 6 2
6 7 1
I want to find from table2 all ItemID's that have all keywordID's found in table1.
In the example above I want to return only itemID 4 that has all keywords (all 3 of them).
I am running this query but I am not getting results:
...
$query .= " AND i.id IN (SELECT itemID FROM #__table2 WHERE (";
foreach($results as $k=>$result) {
$query .= "keywordID = ".(int)$result;
if(($k+1) != count($results))
$query .= " AND ";
}
$query .= "))";
UPDATE
Sorry, I miss read your question. I've done simple test using this data:
id itemId keywordId
1 4 1
5 4 2
6 4 3
7 5 2
8 5 3
9 6 1
10 6 2
11 6 3
12 7 3
13 9 3
14 9 2
15 9 1
and using this query:
SELECT itemId, GROUP_CONCAT( keywordId ORDER BY keywordId ) AS crpcnct, COUNT( itemId )
FROM `temporary_table_123`
GROUP BY 1
HAVING crpcnct = '1,2,3'
I can get the value that you wanted:
itemId crpcnct count(itemId)
4 1,2,3 3
6 1,2,3 3
9 1,2,3 3
To achieve this, all you have to do is build the keywordID you want to use:
$keywordIds[] = $results['keywordId'];
and then sort accending
sort($keywordIds);
the last step is, supply this array into query:
SELECT itemId, GROUP_CONCAT( keywordId ORDER BY keywordId ) AS crpcnct, COUNT( itemId )
FROM `temporary_table_123`
GROUP BY 1
HAVING crpcnct = '" . implode(",", $keywordIds) . "'
There you have it.
SELECT ItemID FROM table2 WHERE keywordID IN (SELECT keywordID FROM table1)
This might work. I'll have to create local copies of your tables to see for sure, though.
$array_names = array();
foreach($explode_tags as $k=>$explode_tag){
$array_names[] = 'name = ' . $db->Quote(trim($explode_tag));
}
$sql = "SELECT ItemID FROM table_2 WHERE keywordID IN (SELECT keywordID FROM table_1 WHERE " . implode(' OR ', $array_names) .")";
$db->setQuery($sql);
$results = $db->loadResultArray();

MYSQL select based on the selects

You're help would be much appreciated...
If I have the following table and sample data... myGroupTable
group_Id : user_Id
1 : 3
1 : 7
1 : 100
2 : 3
2 : 7
2 : 100
2 : 104
4 : 42
4 : 98
4 : 13
I would like a sql statement that would...
Return a group_Id that has exactly the specified user_Id's in them.
eg... is there a group_Id that has User_Id's 3, 7 and 100
answer: group_id 1.
Please note that I dont want it to return a group_Id 2, as that also has a user_Id of 104 in it...
Kind regards J
SELECT
group_Id,
SUM(
IF user_Id = 3 THEN 1
ELSEIF user_Id = 7 THEN 2
ELSEIF user_Id = 100 THEN 4
ELSE 8
) AS bits
FROM myGroupTable
GROUP BY group_Id
HAVING bits=7
This assumes that you cannot have duplicate user_Ids for the same group_Id, eg this could never happens:
group user
1 3
1 3
Edit: You can build your query in the following way:
<?php
$ids = array(3, 7, 100);
$power = 2;
$query = "
SELECT
group_Id,
SUM(
IF user_Id = " .$ids[0]. " THEN 1 ";
foreach ($id in $ids) {
$query .= " ELSEIF user_Id = $id THEN " . $power;
$power = $power * 2;
}
$query .= " ELSE $power
) AS bits
FROM myGroupTable
GROUP BY group_Id
HAVING bits = " . ($power - 1);
Here's another alternative:
SELECT group_id, GROUP_CONCAT(user_id ORDER BY user_id) AS user_id_list
FROM group_user
GROUP BY group_id
HAVING user_id_list = '3,7,100'
another solution:
SELECT group FROM tbl WHERE group_id NOT IN (SELECT DISTINCT group_id FROM tbl WHERE user_id NOT IN(3,7,100)) AND user_id IN (3,7,100);
select group_id,
sum(
case user_id in(3,7,100)
when 1 then 1
else -99
end
) as must_be_3
from group_user
group by group_id
having must_be_3=3;