Multiple LEFT JOINS with Multiple COUNT() count all colum - php

I have 4 tables.
table groups
| ID | NAME |
1 Premium
2 Silver
table user
| ID | group_id | NAME |
1 1 Serhan
2 2 Farhat
table user_statistics
| ID | user_id | TYPE |
1 1 1
2 2 0
table votes
| ID | user_id | VOTE |
1 1 1
2 2 0
3 1 0
I created an sql query to retrieve user details who is in same group. It's worked! Then I want to retrieve any of vote that have been voted to user in groups. I want to count the vote. So basically I've made this sql query.
global $conn;
$res_groups = array();
$stmt = $conn->prepare("
SELECT * FROM groups
");
$stmt->execute();
$stmt->setFetchMode(PDO::FETCH_ASSOC);
while ($group = $stmt->fetch()){
$groups = array();
$groups['id'] = $group['id'];
$groups['name'] = $group['name'];
$user_arr = array();
$stmts = $conn->prepare('
SELECT l.*,
(SELECT MAX(ls.date) from user_statistics ls WHERE ls.user_id = l.id GROUP BY ls.user_id) as ls_date,
(SELECT SUM(IF(ls.type="0", ls.type, 0)) FROM user_statistics ls WHERE ls.user_id = l.id GROUP BY ls.user_id) as ls_us,
(SELECT SUM(IF(ls.type="1", ls.type, 0)) FROM user_statistics ls WHERE ls.user_id = l.id GROUP BY ls.user_id) as ls_uk,
(SELECT COUNT(*) FROM user_statistics WHERE type="1" AND ls.user_id = l.id GROUP BY ls.user_id) as totals,
(SELECT COUNT(*) FROM votes v WHERE v.vote=0 AND confirm=0 AND v.user_id = l.id) as badvote,
(SELECT COUNT(*) FROM votes v WHERE v.vote=1 AND confirm=0 AND v.user_id = l.id) as goodvote
FROM user l
LEFT JOIN
user_statistics ls on l.id = ls.user_id
LEFT JOIN votes v on v.user_id = l.id
WHERE l.group_id = '.$groups['id'].' AND status = 1
GROUP BY l.id,ls.user_id
');
$stmts->execute();
$stmts->setFetchMode(PDO::FETCH_ASSOC);
while ($usr = $stmts->fetch()){
$totalvote=($usr['badvote']+$usr['goodvote']);
if($totalvote>0){
$badvote=bcdiv($usr['badvote']*100/$totalvote,1,2);
$goodvote=bcdiv($usr['goodvote']*100/$totalvote,1,2);
}else{
$badvote=0;
$goodvote=0;
}
$votes[] = array(
"count" => $usr['badvote'],
"percent" => $badvote
);
$votes[] = array(
"count" => $usr['goodvote'],
"percent" => $goodvote
);
$user_arr[] = array(
"id" => $usr['id'],
"group_id" => $usr['group_id'],
"name" => $usr['name'],
"votes_summary" => $votes
);
}
$groups['list'] = $user_arr;
$res_groups[] = $groups;
}
All code seems work unless one thing. The VOTE is always return to count all column in my database VOTES and apply the data to all of my user. What I want is to get how many vote that each user get based on vote type GOOD or BAD.
Any help will be nice.

Looking to your code you could refactor your query avoiding select subquery one for each row and using two subquery with group by in join.
You have also the same code for bad and good vote could be you need different code for obtain different values
SELECT l.*
, t1.ls_date
, t1.ls_us
, t1.ls_uk
, t2.totals
, t2.badvote
, t2.goodvote
FROM user l
INNER JOIN (
SELECT ls.user_id
, MAX(ls.date) ls_date
, SUM(IF(ls.type="0", ls.type, 0)) ls_us
, SUM(IF(ls.type="1", ls.type, 0)) ls_uk
from user_statistics ls
GROUP BY ls.user_id
) t1 on t1.user_id = l.id
LEFT JOIN (
SELECT v.user_id
, sum( case when type="1" then 1 else 0 end ) totals
/* these are the same */
, sum ( case when v.vote=1 AND confirm=0 then 1 else 0 END ) badvote
/* these are the same */
, sum ( case when v.vote=1 AND confirm=0 then 1 else 0 END ) goodvote
FROM votes v
) t2 ON t2.user_id = l.id
WHERE l.group_id = '.$groups['id'].'
AND status = 1
And you should avoid the use of PHP var in SQL (you are at risk for SQL injection). For this you should take a look at your db driver for prepared statement and binding value, or at least be sure you sanitize properly the php var content

Related

How to select few values with and condition?

I have table example http://prntscr.com/msqpep
I need empty result when i am doing
SELECT * FROM prods
WHERE prod_id = 3 and (fet_id = 1 and fet_id = 5)
but that code so not work (fet_id = 1 and fet_id = 5) or (fet_id = 1 and fet_id = 3)
"Where in" is a best way but i need `WHERE IN(1 and 2 and 3)
By default "WHERE IN" working as (1 or 2 or 3)
When I select fet_id=1 and fet_id=3 i need two results
When i select fet_id=1 and fet_id=5 i need no result
You probably want
SELECT prod_id FROM prods GROUP by prod_id
HAVING (SUM(fet_id=1)>0
AND SUM(fet_id=5)>0)
OR
(SUM(fet_id=1)>0
AND SUM(fet_id=3)>0)
HAVING filters groups WHERE filters rows.
Try This:
SELECT p1.id AS id1, p2.id AS id2
FROM `prods` p1 LEFT JOIN `prods` p2 ON p1.`prod_id` = p2.`prod_id`
WHERE p1.`fet_id` = '1' AND p2.`fet_id` = '3'
Output
id1 id2
1 3
With input fet_id=1 & fet_id=5
SELECT p1.id AS id1, p2.id AS id2
FROM `prods` p1 LEFT JOIN `prods` p2 ON p1.`prod_id` = p2.`prod_id`
WHERE p1.`fet_id` = '1' AND p2.`fet_id` = '5'
Output: No result
And another one if need more then two values
SELECT res.* FROM (SELECT 3 as total,
(SELECT COUNT(*) FROM `prods` as t1 WHERE t1.`fet_id` = 1 OR t1.`fet_id` = 2 OR t1.`fet_id` = 5) as current,
t2.*
FROM `prods` as t2 WHERE t2.`fet_id` in (1,2,5)) as res WHERE res.total = res.current

Update one MySQL table with values from another table based on id match

I have these 2 tables below :
users:
id 7dexpn
=========== ==========
1 0
2 0
3 0
user_pages:
id user_id 7dexpf
========== =========== ==========
99 1 0
98 2 1
97 3 1
96 3 1
95 3 1
94 2 0
I am trying to insert { user_pages aggregate of (7dexpf) flags} into users table (7dexpn) matching by user_pages (user_id) with users table (id)
Expected results should be:
users:
id 7dexpn
=========== ==========
1 0
2 1
3 3
You want to aggregate the values in user_pages and then assign them. I would do:
update users u join
(select user_id, count(*) as cnt
from user_pages
where `7dexpf` = 1
group by user_id
) uu
on uu.user_id = u.id
set u.`7dexpn` = uu.cnt;
Try using below query
UPDATE users u,
(
SELECT
user_id,
SUM(7dexpf) AS 7dexpf
FROM
user_pages up
GROUP BY
user_id
) upp
SET 7dexpn = upp.7dexpf
WHERE
u.id = upp.user_id
i did it successfully by doing like this way cheers to community
$query=" UPDATE users c
INNER JOIN (
SELECT user_id, SUM(7dexpf) as total
FROM user_pages
GROUP BY user_id
) x ON c.id = x.user_id
SET c.7dexpn = x.total
";
You can use a join this way for aggregated value
update users u
join ( select user_id, sum( case when `7dexpf` =1 1 else 0 end ) tot_7dexpf
from user_pages
group by user_id) t1 on t1.user_id = u.id
set u.`7dexpf` = t1tot_7dexpf
And i suggest io of avoid columns name starting with digit or in this case use backticks eg :
`7dexpf`

MySql query, counting occurrences of user_id

My query is:
SELECT * FROM (SELECT user_id
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND validation = 'accepted') AS u
My results are:
| user_id |
| 4 |
| 5 |
| 4 |
| 5 |
| 6 |
I need to get the count of each of them like:
4 > 2
5 > 2
6 > 1
I've tried many kind of queries with subqueries or using distinct but I'm lost and I don't reach my goal.
You can achieve this by using GROUP BY and COUNT. The subquery is not needed.
SELECT goal_results.user_id, COUNT(*) qty
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND goal_results.validation = 'accepted'
GROUP BY goal_results.user_id
Adding group by will do.
SELECT u.user_id, count(*) FROM (SELECT user_id
FROM goals
LEFT JOIN goal_results ON goals.id = goal_results.goal_id
WHERE goals.enabled = 1 AND validation = 'accepted') AS u
group by u.user_id
You don't need to do this with a subquery:
SELECT g.user_id, count(*)
FROM goals g LEFT JOIN
goal_results gr
ON g.id = gr.goal_id
WHERE g.enabled = 1 AND validation = 'accepted'
GROUP BY g.user_id;
My guess, though, is that you really want:
SELECT g.user_id, count(gr.goal_id)
FROM goals g LEFT JOIN
goal_results gr
ON g.id = gr.goal_id
WHERE g.enabled = 1 AND validation = 'accepted'
GROUP BY g.user_id;
This will return 0 for users that have no goals. The first will return 1 for them.

SQL statement I can't wrap my head around (brain too small)

I'm writing sort of a travel-'dating' app.
Users register themselves
Users tell the app if they are male or female
Users tell the app which countries they would like to visit
Users tell the app if they want to travel with males (pref_m=1) or females (pref_f=1)
My tables
table 1: users
id (key) | gender | pref_m | pref_f
------------------------------------
1 male 1 0
2 male 1 1
table 2: countryselection
id (key) | userid | countryid
------------------------------------
1 1 123
2 1 111
3 1 100
4 1 110
5 2 123
6 2 111
7 2 202
8 2 210
So what the select statement has to do
Input: the userid of the current user
Output (in logic): SELECT the userids AND matching countries OF ALL people that want to travel to the same countries as I do, and want to travel with someone that has my gender
(join) Of that selection I obviously only need the people that are of the gender that I am looking for.
ORDERED by people that have the most matching countries with me DESC.
What I have so far (warning: not much)
$sql = "SELECT userid,count(*) AS matches from countryselection";
$sql .= " WHERE countryid IN (SELECT countryid FROM countryselection WHERE userid = :userid) GROUP BY userid ORDER BY matches DESC;";
This gives me a list of all people that want to travel to the same countries as me (and how many countries we have in common)
final note
I'm obviously struggling with the gender-selection part.
Not sure if I have done the right thing to store the user selections in the way that I have.
I might need some guidance there too.
Obviously - thanks all.
SELECT
us2.id, -- etc.
COUNT(cs2.countryid) as countries_in_common
FROM
countryselection cs1 -- let's gather user countries he want to visit
LEFT JOIN -- now let's find other users!
countryselection cs2 ON
(
cs2.userid <> :userid AND -- which are not him
cs2.countryid = cs1.countryid -- and want to visit same countries
)
INNER JOIN -- let's grab our user_data
users us1 ON
(
us1.id = cs1.userid
)
INNER JOIN -- and let's grab other user data!
users us2 ON
(
us2.id = cs2.userid
)
WHERE
cs1.userid = :userid AND -- finding our user countries he want to visit
-- final checks
(
(us1.pref_m = 1 AND us2.gender = 'male')
-- he is looking for male and second user is male
OR
(us1.pref_f = 1 AND us2.gender = 'female')
-- he is looking for female and second user is female
) AND
(
(us2.pref_m = 1 AND us1.gender = 'male')
OR
(us2.pref_f = 1 AND us1.gender = 'female')
)
GROUP BY
cs2.userid -- finally group by user_id
Best thing is there are no sub-queries, and you can easily use this query in many ways. (changing order, group by, and using aggregate functions)
It's pretty easy if you don't do the sorting by most countries in common (you could do it in code later if the result sets won't be too large):
SELECT
o.id userid, u_cs.countryid
FROM users u
JOIN countryselection u_cs ON (u.id = u_cs.userid)
JOIN countryselection o_cs ON (u_cs.countryid = o_cs.countryid)
JOIN users o ON (o_cs.userid = o.id)
WHERE
u.id = :userid AND -- The user we want
u.id <> o.id AND -- Exclude ourselves
( -- Check whether the other person is
-- compatible with us
(u.pref_m = 1 AND o.gender = 'male') OR
(u.pref_f = 1 AND o.gender = 'female')
) AND
( -- Check whether we're compatible with the
-- other person
(o.pref_m = 1 AND u.gender = 'male') OR
(o.pref_f = 1 AND u.gender = 'female')
)
SQL Fiddle
If you do want the sorting, I think the best option is to use GROUP_CONCAT (because MySQL sucks and doesn't support windowing/analytic functions).
SELECT
o.id userid, GROUP_CONCAT(u_cs.countryid) countries
FROM users u
JOIN countryselection u_cs ON (u.id = u_cs.userid)
JOIN countryselection o_cs ON (u_cs.countryid = o_cs.countryid)
JOIN users o ON (o_cs.userid = o.id)
WHERE
u.id = :userid AND -- The user we want
u.id <> o.id AND -- Exclude ourselves
( -- Check whether the other person is
-- compatible with us
(u.pref_m = 1 AND o.gender = 'male') OR
(u.pref_f = 1 AND o.gender = 'female')
) AND
( -- Check whether we're compatible with the
-- other person
(o.pref_m = 1 AND u.gender = 'male') OR
(o.pref_f = 1 AND u.gender = 'female')
)
GROUP BY o.id
ORDER BY COUNT(u_cs.countryid) DESC
You could probably pull this off with some nasty subqueries too, but I get the feeling it'll kill performance.
SQL Fiddle
SELECT t4.id, COUNT(t4.id) AS frequency
FROM users t1
LEFT JOIN countryselection t2
ON t1.id = t2.userid
INNER JOIN countryselection t3
ON t2.userid != t3.userid AND t2.countryid = t3.countryid
INNER JOIN users t4
ON t3.userid = t4.id
AND ((t4.pref_m = 1 AND t1.gender = 'male' OR t4.pref_f = 1 AND t1.gender = 'female')
AND (t1.pref_m = 1 AND t4.gender = 'male' OR t1.pref_f = 1 AND t4.gender = 'female'))
WHERE t1.id = ?
GROUP BY t4.id
ORDER BY frequency DESC
Similar to others here but using appropriate join types and join conditions instead of where conditions.
From MySQL docs:
The conditional_expr used with ON is any conditional expression of the
form that can be used in a WHERE clause. Generally, you should use the
ON clause for conditions that specify how to join tables, and the
WHERE clause to restrict which rows you want in the result set.
I think this works
select me.id meid
, them.id themid
, me.gender mygender
, them.gender themgender
, me.pref_m mepref_m
, me.pref_f mepref_f
, them.pref_m thempref_m
, them.pref_f thempref_f
, csme.countryid
from users me
cross
join users them
inner
join countryselection csme
on me.id = csme.userid
inner
join countryselection csthem
on them.id = csthem.userid
where csme.countryid = csthem.countryid
and ((me.gender = 'male' and them.pref_m) or (me.gender = 'female' and them.pref_f))
and ((them.gender = 'male' and me.pref_m) or (them.gender = 'female' and me.pref_f))
and me.id != them.id
and me.id = 2
http://sqlfiddle.com/#!2/06351/25/0
I intentionally left out the group by so that the results are more easily verified.
inferred DDL :
create table users (id int, gender text, pref_m bool, pref_f bool);
create table countryselection (id int, userid int, countryid int);
Here is CSV that you can .import (using sqlite3, after doing .separator ,) into the tables in the question:
users.csv:
1,male,1,0
2,male,1,1
countryselection.csv
1,1,123
2,1,111
3,1,100
4,1,110
5,2,123
6,2,111
7,2,202
8,2,210
peter's sql, edited to use field names from question:
SELECT
us2.id,
COUNT(cs2.*) as countries_in_common
FROM
countryselection cs1
LEFT JOIN
countryselection cs2 ON
(
cs2.userid <> $userid AND
cs2.countryid = cs1.countryid
)
LEFT JOIN
users us1 ON
(
us1.id = cs1.userid
)
LEFT JOIN
users us2 ON
(
us2.id = cs2.userid
)
WHERE
cs1.userid = $userid AND
cs2.userid IS NOT NULL AND
(
(us1.pref_m = 1 AND us2.gender = 'male')
OR
(us1.pref_f = 1 AND us2.gender = 'female')
) AND
(
(us2.pref_m = 1 AND us1.gender = 'male')
OR
(us2.pref_f = 1 AND us1.gender = 'female')
)
GROUP BY
cs2.userid
;
you can execute it like this:
sqlite3 myDBname < peters_sql.sql
setting $userid to 1, i get 2 as output.
UPDATE: (added the countries)
SELECT u1.id AS uid1
, u2.id AS uid2
, cs.countryid
FROM users u1
, users u2
JOIN countryselection cs ON cs.userid = u2.id
-- WHERE u1.id < u2.id -- tiebreaker
WHERE u1.id = 12345
AND EXISTS (
SELECT * FROM countryselection cs1
JOIN countryselection cs2 ON cs1.countryid = cs2.countryid
WHERE cs1.userid = u1.id
AND cs2.userid = u2.id
)
AND ((u1.pref_m = True AND u2.gender = 'male')
OR (u1.pref_f = True AND u2.gender = 'female') )
-- the love must be mutual ...
AND ((u2.pref_m = True AND u1.gender = 'male')
OR (u2.pref_f = True AND u1.gender = 'female') )
;

mysql query within a query with privacy condition check

I have 3 tables.
myMembers
------------------------------------
id | username | privacy
------------------------------------
1 | userA | 0
2 | userB | 1
3 | userC | 0
4 | userD | 1
following
--------------------------------
id | user_id | follower_id
--------------------------------
1 | 2 | 1
posts
-------------------------------------
id | userID | username | statusMsg
--------------------------------------
1 | 4 | userD | Issac Newton is genius
2 | 2 | userB | Newton Saw apple
3 | 3 | userC | Newtonian Physics
4 | 1 | userA | Calculus came from Sir Newton
There is a search field. When a logged in user searches for 'keyword' in table 'posts', I want to omit results from those users who has set his privacy to '1' and WHERE searcher is not following user B.
The query should logically do this.
SELECT * from posts WHERE (match the keyword)
AND (
if (poster's privacy (which is set in myMembers)==1){
if (seacher is following poster){
select this post
}
}
else { select this post
}
)
LIMIT results to 5 rows
So for a keyword "Newton",
if userA is searching, rows 2,3,4 from 'posts' should be returned.
if userD is searching, only rows 1, 3 and 4 from 'posts' should be returned,
based on privacy and following
Edit: Tagging for future searches: IF condition within WHERE Clause in mySql
Please, try this query (also on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
LEFT JOIN following f ON p.user_id = f.user_id
JOIN members searcher ON searcher.username = 'userA'
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.follower_id = searcher.id)
OR m.id = searcher.id)
AND p.status_msg LIKE '%New%'
ORDER BY p.id
LIMIT 5;
I removed username field from posts table, as it is redundant. Also, I named tables and columns slightly different, so query might need cosmetic changes for your schema.
The first line in the WHERE clause is the one that you're looking for, it selects posts in the following order:
First posts from members without privacy;
Then posts from members that are followed by the current searcher;
Finally, posts of the member himself.
EDIT:
This query is using original identifiers:
SELECT p.id, p.`userID`, m.username, m.privacy,
searcher.username "Searcher", p.`statusMsg`
FROM posts p
JOIN `myMembers` m ON m.id = p.`userID`
LEFT JOIN following f ON p.`userID` = f.user_id
JOIN `myMembers` searcher ON searcher.username = 'userD'
WHERE (m.privacy = 0 OR f.follower_id = searcher.id OR m.id = searcher.id)
AND p.`statusMsg` LIKE '%New%'
ORDER BY p.id
LIMIT 5;
EDIT 2:
To avoid duplicates in case there're several followers for the user from the posts table, join and filtering conditions should be changed the following way (on SQL Fiddle):
SELECT p.id, p.user_id, m.username, m.privacy,
searcher.username "Searcher", p.status_msg
FROM posts p
JOIN members m ON m.id = p.user_id
JOIN members searcher ON searcher.username = 'userC'
LEFT JOIN following f ON p.user_id = f.user_id
AND follower_id = searcher.id
WHERE (m.privacy = 0 OR (m.privacy = 1 AND f.id IS NOT NULL)
OR m.id = searcher.id)
ORDER BY p.id
LIMIT 5;
Try the following:
SET #my_user_id= 1;
SELECT * FROM posts p
INNER JOIN myMembers m ON p.user_id= m.id
WHERE statusMsg LIKE '%'
AND privacy=0
AND user_id IN (SELECT follower_id FROM following f WHERE f.user_id=#my_user_id)
LIMIT 5
try this:
SELECT a.*
FROM posts a
LEFT JOIN (SELECT user_id
FROM following a1
INNER JOIN myMembers b1
ON a1.follower_id = b1.id
WHERE a1.follower_id = 1 AND
b1.privacy = 1
) b
ON a.userID = b.user_id AND
WHERE a.statusMsg LIKE '%search%' AND
b.user_id IS NULL
LIMIT 5;
or better approach without subquery:
SELECT a.*
FROM posts a
LEFT JOIN myMembers b
ON a.userID = b.id AND
b.privacy = 1
LEFT JOIN following c
ON a.userID = c.user_id AND
c.follower_id = 1
WHERE a.statusMsg LIKE '%search%' AND
b.id IS NULL AND
c.user_id IS NULL
LIMIT 5;
See: A Visual Explanation of SQL Joins

Categories