How can I put a condition on the way of join? - php

I have this table structure:
// QandA
+----+---------------------+----------------------------------------+------+---------+
| Id | title | content | type | related |
+----+---------------------+----------------------------------------+------+---------+
| 1 | title of question 1 | content of question1 | 0 | 1 |
| 2 | | content of first answer for question1 | 1 | 1 |
| 3 | title of question 2 | content of question2 | 0 | 3 |
| 4 | | content of second answer for question1 | 1 | 1 |
| 5 | | content of first answer for question2 | 1 | 3 |
+----+---------------------+----------------------------------------+------+---------+
type column: 0 means it is a question and 1 means it is a answer.
related column: for question this column is containing the id of itself and for answer this column is containing the id of its question.
Also there is other dependent tables:
// Votes
+----+---------+---------+-------+
| id | post_id | user_id | value |
+----+---------+---------+-------+
| 1 | 1 | 1234 | 1 |
| 2 | 2 | 1234 | -1 |
| 3 | 1 | 4321 | 1 |
+----+---------+---------+-------+
// Favorites
+----+---------+---------+
| id | post_id | user_id |
+----+---------+---------+
| 1 | 1 | 1234 |
| 2 | 1 | 4321 |
+----+---------+---------+
Ok well, This is the main note in my question: Favorites table is only belong to the questions (not answers). Answers can never be favorite (just questions can be)
Also here is my query:
SELECT
p.title, p.content,
vv.value AS cuvv -- cuvv is stand for current_user_vote_value,
CASE WHEN ff.id IS NOT NULL THEN '2' ELSE '3' END AS cuf -- current_user_favorite
(SELECT SUM(v.value) FROM Votes v WHERE p.id = v.post_id) AS total_votes,
(SELECT COUNT(1) FROM Favorites f WHERE p.id = f.post_id) AS total_favorites,
FROM QandA p
LEFT JOIN Votes vv ON p.id = vv.post_id AND vv.user_id = :user_id_1
LEFT JOIN favorites ff ON p.id = ff.post_id AND f.user_id = :user_id_2
WHERE p.related = :id
Note: For cuf, 2 means current user has marked this question as favorite and 3 means he didn't have (in other word, 3 means this question isn't favorite for current user).
Ok, let me pass some parameters to query and execute it: (as an example)
$user_id = 1234;
$id = 1;
$sth->bindValue(":user_id_1", $user_id, PDO::PARAM_INT);
$sth->bindValue(":user_id_2", $user_id, PDO::PARAM_INT);
$sth->bindValue(":id", $id, PDO::PARAM_INT);
$sth->execute();
And here is the output:
-- cuvv is stand for current_user_vote_value
-- cuf is stand for current_user_favorite
+--------------+----------------------+------+-----+-------------+-----------------+
| title | content | cuvv | cuf | total_votes | total_favorites |
+--------------+----------------------+------+-----+-------------+-----------------+
| title of ... | content of que ... | 1 | 2 | 2 | 2 |
| | content of fir ... | -1 | 3 | -1 | 0 |
| | content of sec ... | NULL | 3 | 0 | 0 |
+--------------+----------------------+------+-----+-------------+-----------------+
Ok So, What's my question?
These two columns cuf and total_favorites are just belong to questions (type = 0). But my query doesn't know it. I mean my query calculates the number of total favorites for all rows, and I want to know, how can tell it: calculate cuf and total_favorites only for questions, not both questions and answers?
In other word, I need to put a IF condition to check if p.type = 0 then execute these two lines:
(SELECT COUNT(1) FROM Favorites f WHERE p.id = f.post_id) AS total_favorites,
and
LEFT JOIN favorites ff ON p.id = ff.post_id AND f.user_id = :user_id_2
Otherwise doesn't execute those two lines, because if p.type = 1, then those two lines are waste and useless.
How can I implement that condition and improve that query?

One way you may want to try is to query the favorite and votes table only once in subqueries, and calculate both the user and all values at once.
SELECT
q.title, q.content,
IFNULL(vv.user_val, 0) cuvv, IFNULL(vv.all_val, 0) total_votes,
IFNULL(ff.user_fav, 0) cuf, IFNULL(ff.all_fav, 0) total_favorites
FROM QandA q
LEFT JOIN (
SELECT post_id,
SUM(value) all_val, SUM(CASE WHEN user_id=1234 THEN value END) user_val
FROM votes GROUP BY post_id
) vv
ON vv.post_id = q.id
LEFT JOIN (
SELECT post_id,
COUNT(1) all_fav, COUNT(CASE WHEN user_id=1234 THEN 1 END) user_fav
FROM favorites GROUP BY post_id
) ff
ON q.type=0 AND ff.post_id = q.id
WHERE q.related = 1;
An SQLfiddle to test with.

Try this:
SELECT
p.id, p.type,p.title, p.content,
vv.value AS cuvv,
CASE WHEN ff.id IS NOT NULL THEN '2' ELSE '3' END AS cuf,
(SELECT SUM(v.value) FROM Votes v WHERE p.id = v.post_id) AS total_votes,
(SELECT COUNT(1) FROM Favorites f WHERE p.id = f.post_id) AS total_favorites
FROM QandA p
LEFT JOIN Votes vv ON p.id = vv.post_id AND vv.user_id = '1234'
LEFT JOIN Favorites ff ON p.id = ff.post_id AND ff.user_id = '1234'
WHERE p.related = 1 and p.type=0
union all
SELECT
p.id, p.type,p.title, p.content,
vv.value AS cuvv,
'3' AS cuf,
(SELECT SUM(v.value) FROM Votes v WHERE p.id = v.post_id) AS total_votes,
NULL AS total_favorites
FROM QandA p
LEFT JOIN Votes vv ON p.id = vv.post_id AND vv.user_id = '1234'
WHERE p.related = 1 and p.type=1;

Related

Iget data from 1 table and check the data does not exist in second table

I have 3 tables created forum groups and group_members I want to get groups only those which are not in group members along with user id currently it is getting group which group id and user id is not present in the group members table if data is not in the table only if 1 group member exist it pulls up the record . In simple words I want to show show groups which user have not joined here is my table schema for both 3 tables
Groups
+----+----------+
| id | name |
+----+----------+
| 1 | group 1 |
| 2 | group 2 |
| 3 | group 3 |
| 4 | group 4 |
+----+----------+
forums
+------------------+-------------+
| id | title | group_id |
+------------------+-------------+
| 1 | test 1 | 2 |
| 2 | test 2 | 3 |
| 3 | test 3 | 2 |
| 4 | test 4 | 3 |
| 5 | test 5 | 2 |
| 6 | test 6 | 4 |
+------------------+-------------+
Group_members
+-----------------+-------------+
| id | user_id | group_id |
+-----------------+-------------+
| 1 | 107 | 2 |
| 2 | 106 | 3 |
+-----------------+-------------+
Here is my sql I have written
<?php
$sql_grp_chk = $this->db->query("SELECT * FROM groups WHERE NOT EXISTS (SELECT * FROM group_members WHERE groups.id == group_members.group_id)");
foreach($sql_grp_chk->result() as $data_ct):
$sql_gr_coun = $this->db->query("SELECT groups.*, (SELECT count(group_id) FROM forums WHERE groups.id = forums.group_id) as forumcount FROM groups WHERE groups.id != '".$data_ct->id."' ORDER BY forumcount DESC LIMIT 5");
foreach($sql_gr_coun->result() as $data_count):
$sql_follow = $this->db->get_where('group_members', array('group_id' => $data_count->id));
var_dump($data_count);
?>
<?php endforeach; ?>
<?php endforeach; ?>
Not sure why forums is there, but to select all groups that are not linked to a user you can do left join:
select g.* from groups g
left join group_members m on m.group_id = g.id and m.user_id = :userId
where m.id is null;
EDIT:
Select top 5 groups, by number of forums linked:
select g.*, count(nullif(f.id, 1)) as cnt from groups g
inner join forums f on f.group_id = g.id
group by g.id
order by cnt desc
limit 5;
Both queries together - top 5 groups, by number of forums linked, which user has not joined yet:
select g.*, count(nullif(f.id, 1)) as cnt from groups g
left join group_members m on m.group_id = g.id and m.user_id = :userId
left join forums f on f.group_id = g.id
where m.id is null
group by g.id
order by cnt desc
limit 5;

How can I check two conditions before inserting?

I have three tables:
// Posts
+----+----------+---------------+-----------+
| id | title | content | id_author |
+----+----------+---------------+-----------+
| 1 | title1 | content1 | 1234 |
| 2 | title2 | content2 | 5678 |
+----+----------+---------------+-----------+
// Users
+----+--------+--------+
| id | name | active |
+----+--------+--------+
| 1 | jack | 1 |
| 2 | peter | 0 |
| 3 | John | 1 |
+----+--------+--------+
// Votes
+----+---------+---------+
| id | id_post | id_user |
+----+---------+---------+
| 1 | 32 | 1234 |
| 2 | 634 | 5678 |
| 3 | 352 | 1234 |
+----+---------+---------+
Now I need to check two conditions before inserting a new vote into Votes table:
The id of author and what I have passed are the same? Posts.id_user = :author (I know I can do that by a FK, but I don't want)
The account of current user is active? Users.active = 1
Also here is my query:
INSERT INTO Votes (id_post,id_user)
SELECT ?,?
FROM Posts p
WHERE p.id_user = :author limit 1;
How can I add second condition Users.active = 1 to my query?
EDIT: Actually I'm trying to don't let people be able to vote who are inactive (active = 0). For example if SO bans men, then I cannot vote to post anymore, because I (current user) am banned. So I'm pretty sure $_SESSION['id'] should be used in the query.
INSERT INTO Votes (id_post,id_user)
SELECT p.id,u.id
FROM Posts p, Users u
WHERE p.id_user = :author
AND u.id = :user
AND u.active = 1 limit 1;
then you set parameter user equal to the current user id.
EDIT: I suppose id_user in table Votes must be the voter's id, not the author of the post (correct?), so I fixed the query eliminating the JOIN.
Use and with where
INSERT INTO Votes (id_post,id_user)
SELECT ?,?
FROM Posts p, Users u
WHERE p.id_user = :author and u.active = 1 limit 1;
INSERT INTO Votes (id_post,id_user)
SELECT p.id, u.id
FROM Posts p
INNER JOIN Users u ON 1 = 1
WHERE p.id_user = :author
AND u.id = :current_user_id
AND u.active = 1
LIMIT 1;

Group by 2 columns regardless of order

The following query
select a.message, a.sender_id, a.rec_id, a.id, a.is_seen, b.total_msg, b.last_id, users.name
from tbl_message a left join users on (users.id=a.sender_id)
inner join
(select sender_id, rec_id, max(id) last_id, count(*) total_msg
from tbl_message group by sender_id,rec_id
)b on a.id=b.last_id
order by a.id desc
gives the result as below:
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
| message | sender_id | rec_id | id | is_seen | total_msq | last_id | name |
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
| latest testing l5 aug | 2 | 1 | 12 | 0 | 5 | 12 | B |
| testing | 1 | 2 | 11 | 1 | 3 | 11 | A |
| this msg of A | 1 | 3 | 9 | 0 | 1 | 9 | A |
| this is again 3rd msg of C | 3 | 1 | 6 | 1 | 3 | 6 | C |
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
I want the result as:
For sender_id/rec_id = 1 or 2 id = 12 and for sender_id/rec_id = 1 or 3 id = 9
It sounds like you want to group rows by the sender_id,rec_id participants pair regardless of which order they appear in (i.e. sender_id,rec_id or rec_id,sender_id should be part of the same group).
If so, change your group by from
group by sender_id, rec_id
to
group by least(sender_id,rec_id), greatest(sender_id,rec_id)
Using greatest and least will ensure that each conversation will be grouped by the participants regardless of which order they appear in.
Looks like you need to join with all the grouped columns
Try this
SELECT a.message
,a.sender_id
,a.rec_id
,a.id
,a.is_seen
,b.total_msg
,b.last_id
,users.NAME
FROM tbl_message a
LEFT JOIN users ON (users.id = a.sender_id)
INNER JOIN (
SELECT sender_id
,rec_id
,max(id) last_id
,count(*) total_msg
FROM tbl_message
GROUP BY sender_id
,rec_id
) b ON a.sender_id=b.sender_id and a.rec_id=b.rec_id and a.id = b.last_id
ORDER BY a.id DESC
I think you need to remove one column from the GROUP BY clause of the derived table (subquery)
select a.message, a.sender_id, a.rec_id, a.id, a.is_seen, b.total_msg, b.last_id, users.name
from tbl_message a left join users on (users.id=a.sender_id)
inner join
(select sender_id, rec_id, max(id) last_id, count(*) total_msg
from tbl_message group by sender_id
)b on a.id=b.last_id
order by a.id desc
I would expect this to result in rows only for the following
SenderID ID
2 12
1 9
3 6

MySql PDO - How to fetch "newest rows from group" when using GROUP BY, ORDER BY and INNER JOIN?

products_table: | p_id | name |
| 1 | name1 |
| 2 | name2 |
| 3 | name3 |
favourites_table: | id | p_id | deleted | group_id |
fetch-> | 1 | 1 | 0 | 11 |
| 2 | 1 | 0 | 11 |
fetch-> | 3 | 2 | 0 | 22 |
| 4 | 2 | 0 | 22 |
fetch-> | 5 | 3 | 0 | 33 |
| 6 | 3 | 0 | 33 |
$sth = $db->prepare(' SELECT a.p_id, b.name
FROM favourites_table AS a
INNER JOIN products_table AS b
ON a.p_id = b.p_id
WHERE a.deleted=0
GROUP BY a.group_id
ORDER BY a.id ASC
LIMIT 0, 10;');
$sth->execute();
while(($query_data = $sth->fetch()) !== false) {
echo $query_data['p_id'] . ':' . $query_data['name'] . '<br>';
}
This query fetches rows 1, 3, 5 from 'favourites_table'.
How to change it so it fetches "newest rows" (2, 4, 6) ?
Do I have to change the whole query or am I missing something?
You're confused by the pernicious misfeature in MySQL called the GROUP BY extension. Read this. http://dev.mysql.com/doc/refman/5.6/en/group-by-extensions.html
You want the rows you define as latest for each value of group_id. These rows are in fact the undeleted ones with the highest id values.
So, first you need to use a subquery -- a virtual table -- to find those rows, as follows:
SELECT MAX(id) AS id, group_id FROM favourites_table WHERE deleted = 0 GROUP BY group_id
Then, you need to use that resultset to find the right rows in your main query. You would do this like so:
SELECT a.p_id, b.name
FROM favourites_table AS a
INNER JOIN products_table AS b ON a.p_id = b.p_id
INNER JOIN (
SELECT MAX(id) AS id, group_id FROM favourites_table WHERE deleted = 0 GROUP BY group_id
) AS c ON a.id = c.id
GROUP BY a.group_id
ORDER BY a.id ASC
LIMIT 0, 10
This should get your results.
Question: Why order them oldest (lowest id value) first? Why only show the oldest ten results? Is that what you want?

mySQL "Rank in Highscore"-Query

Hi there coders around the world,
I'm working on a project where users can do certain things and gain points for it. To simplify this question let's say we got 2 tables user and points.
-- table user -- table points
+---------------+ +-----------------------------+
| id | name | | id | points | user_id |
+---------------+ +-----------------------------+
| 1 Tim | | 1 5 1 |
| 2 Tom | | 2 10 1 |
| 3 Marc | | 3 5 1 |
| 4 Tina | | 4 12 2 |
| 5 Lutz | | 5 2 2 |
+---------------+ | 6 7 1 |
| 7 40 3 |
| 8 100 1 |
+-----------------------------+
Now to get the complete highscore-list I use the following query
SELECT u.*, SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
ORDER BY sum_points DESC
resulting in a fine highscore-list with all users from first to last
+------------------------------+
| id | name | sum_points |
+------------------------------+
| 1 Tim 127 |
| 3 Marc 40 |
| 2 Tom 14 |
| 4 Tina 0 |
| 5 Lutz 0 |
+------------------------------+
Alright back to the question itself. On the profile of a single user I'd like to show his ranking within the highscore-list.
Can this be done using a single query just showing that for example Tom (id=2) is ranked in place 3?
Thanks alot :-)
The idea is to ask, "how many players rank above #this_user":
select count(*) + 1 from
(
/* list of all users */
SELECT SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
) x
/* just count the ones with higher sum_points */
where sum_points > (select sum(points) from points where user_id = #this_user)
Edited to make result 1-based instead of 0-based
SELECT q.*,
#r := #r + 1 AS rank
FROM (
SELECT #r := 0
) vars,
(
SELECT u.*,
SUM(p.points) AS sum_points
FROM
user u
LEFT JOIN
points p
ON p.user_id = u.id
GROUP BY
u.id
ORDER BY
sum_points DESC
) q

Categories