my situation:
Table "_customers"
----------------------------------------------
customer_id | name | active
----------------------------------------------
1 'customer I' 1
----------------------------------------------
2 'customer II' 0
----------------------------------------------
Table "_projects"
----------------------------------------------
project_id | project_name | customer_id
----------------------------------------------
1 'project I' 1
----------------------------------------------
2 'project II' 2
----------------------------------------------
many-to-many Table "_project_sections"
----------------------------------------------
section_id | project_id
----------------------------------------------
2 1
----------------------------------------------
3 1
----------------------------------------------
4 1
----------------------------------------------
1 2
----------------------------------------------
In my Case, i need now all Customers, which are 'active'. Also they must be from a specific Section. So, as a example, i want all active customers from the Section "1". I also figured it out to get the right data, but - and thats the weird thing - only if need sections with the id > 1. If i try to get all active customers in section 1, the Query still show me all Projects with Section_id 2,3,4...
Query
SELECT c.customer_id, c.name, ps.section_id
FROM _customers c
INNER JOIN _projects p
ON p.customer_id = c.customer_id
INNER JOIN _project_sections ps
ON ps.project_id = p.project_id
WHERE c.active = 1 AND ps.section_id = 1
GROUP BY c.name
ORDER BY LOWER(c.name)
And the wrong result:
Array
(
[customer_id] => 1
[name] => customer I
[section_id] => 1
)
Maybe someone can help me, because i dont't get it. Thank you so much!
You don't need a GROUP BY clause to write this query.
First, let's look at active customers and their projects.
-- All active customers
SELECT c.customer_id, c.name
FROM customers c
WHERE c.active = 1;
customer_id name
--
1 customer I
-- All active customers and their projects
SELECT c.customer_id, c.name, p.project_id, p.project_name
FROM customers c
INNER JOIN projects p ON (p.customer_id = c.customer_id)
WHERE c.active = 1;
customer_id name project_id project_name
--
1 customer I 1 project I
1 customer I 2 project II
Just one active customer, who has two projects.
Now let's look at section 1 projects.
-- All projects from section 1
SELECT ps.project_id, ps.section_id
FROM project_sections ps
WHERE ps.section_id = 1;
project_id section_id
--
2 1
-- All projects from section 1, including project name
SELECT ps.project_id, p.project_name, ps.section_id
FROM project_sections ps
INNER JOIN projects p ON (p.project_id = ps.project_id)
WHERE ps.section_id = 1;
project_id project_name section_id
--
2 project II 1
Just one section 1 project. Now put the pieces together.
-- All active customers and their projects from section 1
SELECT c.customer_id, c.name, p.project_id, p.project_name, ps.section_id
FROM customers c
INNER JOIN projects p ON (p.customer_id = c.customer_id)
INNER JOIN project_sections ps ON (p.project_id = ps.project_id)
WHERE c.active = 1 AND ps.section_id = 1;
customer_id name project_id project_name section_id
--
1 customer I 2 project II 1
That's what I'd expect.
Later . . .
I see that you've changed the data. If I make the same changes here, then my last query above returns no rows. I think that's what you said you expected. (And, with your changes to the data, that's what I'd expect, too.)
From your description, the query should be:
SELECT c.customer_id, c.name, ps.section_id
FROM _customers c
INNER JOIN _projects p
ON p.customer_id = c.customer_id
INNER JOIN _project_sections ps
ON ps.section_id = p.project_ID
WHERE c.active = 1 AND ps.section_id = 1
GROUP BY c.name
ORDER BY LOWER(c.name)
The constraint for the specific section goes in the WHERE clause and not in the JOIN.
Related
I have a simple multiple school management system and I am trying to get total number of teachers, and total number of students for a specific school. My table structures are as follows:
teachers
--------------------------
id | schoolid | Name | etc...
--------------------------
1 | 1 | Bob |
2 | 1 | Sarah|
3 | 2 | John |
students
--------------------------
id | schoolid | Name | etc...
--------------------------
1 | 1 | Jack |
2 | 1 | David|
3 | 2 | Adam |
schools
--------------------------
id | Name | etc...
---------------------------
1 | River Park High |
2 | Stirling High |
I can count just all teachers with the following query:
SELECT COUNT(a.id) AS `totalteachers`
FROM teachers a
LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = '1'
and similarly I can count the number of teachers with the following query:
SELECT COUNT(a.id) AS `totalstudents`
FROM students a
LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = '1'
I am however struggling with trying to combine these two queries to get a simple result like this:
totalstudents | totalteachers
--------------------------------
2 | 2
I have tried the following:
SELECT COUNT(a.id) as `totalteachers`, COUNT(c.id) as `totalstudents`
FROM teachers a
LEFT JOIN schools b ON a.schoolid = b.id
LEFT JOIN students c ON c.schoolid=b.id WHERE b.id = '5'
You can do something like this
SELECT
id, name, s.total AS totalstudents, t.total AS totalteachers
FROM schools
JOIN (SELECT schoolid, COUNT(id) AS total FROM teachers GROUP BY schoolid)
AS t ON t.schoolid = id
JOIN (SELECT schoolid, COUNT(id) AS total FROM students GROUP BY schoolid)
AS s ON s.schoolid = id
then you can add where id = 2 or whatever to limit the school.
The problem with the multiple left joins is it generates additional records for each teacher to each student; artifically inflating your counts
There's four ways to solve this: (best imo is what Andrew bone did)
Simply select inline without the joins so the counts are not inflated. (most desirable in my mind as it's easy to maintain)
SELECT (SELECT COUNT(a.id) AS `totalteachers`
FROM teachers a
WHERE A.SchoolID = '1') as TotalTeachers
, (SELECT COUNT(a.id) AS `totalstudents`
FROM students a
WHERE a.SchoolID = '1') as TotalStudents
Use subqueries to get the counts first before the joins, then join. Since count will always be 1 a cross join works.
SELECT totalTeachers, totalStudents
FROM (SELECT COUNT(a.id) AS `totalteachers`
FROM teachers a
LEFT JOIN schools b
ON a.schoolid = b.id
WHERE b.id = '1')
CROSS JOIN (SELECT COUNT(a.id) AS `totalstudents`
FROM students a
LEFT JOIN schools b ON a.schoolid = b.id
WHERE b.id = '1')
Use key word distinct within the count so as not to replicate the counts and negate the artificial inflation (least desirable in my mind as this hides the artifical count increase)
SELECT COUNT(distinct a.id) as `totalteachers`, COUNT(distinct c.id) as `totalstudents`
FROM teachers a
LEFT JOIN schools b ON a.schoolid = b.id
LEFT JOIN students c ON c.schoolid=b.id WHERE b.id = '5'
Another way would be to use a window functions, however these are not available in mySQL.
SELECT COUNT(t.id) AS TotalTeachers, COUNT(st.id) AS TotalStudents
FROM schools s
INNER JOIN teachers t
ON s.id = t.schoolid
INNER JOIN students st
ON s.id = st.schoolid
Try this SQL. I havn't try it but it should work.
I have two tables projects and projects_meta
Projects
--------------------
id name
--------------------
1 A
2 B
3 C
projects_meta
------------------------------------------------------------
id project_id additional_field additional_value
------------------------------------------------------------
1 1 verified_by Erik
2 1 approved_by Dave
3 2 verified_by Riyaj
4 2 approved_by Mike
5 3 verified_by Erik
6 3 approved_by Dave
Now i want the output where joining both tables to find out what are the projects verified by Erik and approved by Dave
SELECT *
FROM projects a
INNER JOIN projects_meta b
ON a.id = b.project_id
WHERE b.additional_field= 'verified_by'
AND b.additional_value = 'Erik'
AND b.additional_field= 'approved_by'
AND b.additional_value = 'Dave'
The Above query seems to work with a single condition but when there is more it returns empty result.
Thanks in advance
You need to JOIN twice to projects_meta table:
SELECT *
FROM projects a
INNER JOIN projects_meta b ON a.id = b.project_id
INNER JOIN projects_meta c ON a.id = c.project_id
WHERE b.additional_field= 'verified_by' AND b.additional_value = 'Erik' AND
c.additional_field= 'approved_by' AND c.additional_value = 'Dave'
The above query returns all projects being linked both to 'verified_by Erik' AND 'approved_by Dave' records.
I have the table news_feed in which all of my different types of activities data will be stored like admin activities, user activities, company activities etc. The table format looks like:
news_id | type | admin_id | user_id | company_id
1 | admin | 2 | 3 | 0
2 | user | 3 | 4 | 1
3 | company | 0 | 1 | 2
Suppose a user with an id 1 has liked the company which has id 2 then the record will be inserted like
4 user 0 1 2
And I'm listing them in my module and the listing is perfect. But suppose if the company id 2 doesn't exist or if it is inactive, then the news_feed block in listing getting empty. What I want to do is:
If the type is company then JOIN the company table while select listing with condition for status as active
If the type is user then JOIN the user table while select listing with condition for status as active
Well you can use UNION for this issue
SELECT t.column1, t.column2, t.column3
FROM my_table t INNER JOIN company_table c
ON t.company_id = c.id
WHERE c.active=1 AND t.type = "company"
UNION
SELECT column1, column2, column3
FROM my_table t INNER JOIN user_table u
ON t.user_id = u.id
WHERE c.active=1 AND t.type = "user"
Just to add, to increase the efiiciency use UNION ALL rather than UNION (or UNION DISTINCT) as UNION requires internal temporary table with index (to skip duplicate rows) while UNION ALL will create table without such index, but keep in mind it will skip the repeated data.
But more optimized way to do a Conditional Join in MySQL by using a INNER JOIN
SELECT t.column1, t.column2, t.column3
FROM my_table t
INNER JOIN company_table c
ON (t.company_id = c.id AND t.type = "company" AND c.active=1)
INNER JOIN user_table u
ON (t.user_id = u.id AND t.type = "user" AND u.active=1);
I have tables as described below:
subscription_plans (Table for storing all plans)
id plan days_limit added_on status rate
------------------------------------------------
1 PlanA 15 1398249706 1 150.00
2 PlanB 15 1398249706 1 150.00
subscribed_videos (Table for storing details of video in each plans)
id plan_id videoid
----------------------
1 1 1
2 2 2
subscription_groups (Table for storing groups where a plan can be part of another plan. ie, Plan A be a plan with 2 other individual plans, Plan B and C )
id plan_id assosiated_plan_id added_on
----------------------------------------------
1 1 2 1398249706
usersubscription (Table for storing user subscribed plans)
id user_id plan_id subscribed_on
---------------------------------------
1 1 1 1398771106
Now, my problem is that how can I get the count of videos for each plans. If Plan A contains both Plan B and C (subscription_groups table), then the count should return the total video count for each individual plans in that particular plan. Now I have done with a query which will return plan details along with count of videos for a plan but I am not able to join it with subscription_groups. How can I accomplish this in a single query.
$data['planquery']=$this->db->query("select
us.plan_id,us.subscribed_on,sp.plan,sp.days_limit,sp.rate,count(sv.videoid) from
usersubscription as us INNER JOIN
subscription_plans as sp ON us.plan_id=sp.id INNER JOIN subscribed_videos as sv ON sp.id=sv.plan_id where sp.status=1 and us.user_id=1");
Expected Result:
plan_id subscribed_on plan days_limit rate count
-------------------------------------------------------
1 1398771106 PlanA 15 150.00 2
Can anyone help me to find a solution for this?
Thanks in advance.
You can do so
SELECT
us.plan_id,
us.subscribed_on,
sp.plan,
sp.days_limit,
sp.rate,
COUNT(sv.videoid)
FROM
usersubscription AS us
RIGHT JOIN subscription_plans AS sp
ON us.plan_id = sp.id
INNER JOIN subscribed_videos AS sv
ON sp.id = sv.plan_id
INNER JOIN subscription_groups g
ON(g.plan_id =sv .plan_id OR sv.plan_id= g.assosiated_plan_id)
WHERE sp.status = 1
AND (us.user_id = 1 OR us.user_id IS NULL )
Demo
Since user has only plan associated but the associated plan can also has another plan linked so the last condition will check the user id but for is null to for the second linked plan user id will be null due to right join on subscription_plans
Edit
SELECT
u.plan_id,
u.subscribed_on,
p.plan,
p.days_limit,
p.rate
,COUNT(DISTINCT v.`videoid`)
FROM `usersubscription` u
JOIN `subscription_groups` g
ON (u.`plan_id` = g.`plan_id`)
RIGHT JOIN `subscription_plans` p
ON(u.`plan_id` = p.`id` OR g.`assosiated_plan_id` = p.`id`)
INNER JOIN `subscribed_videos` v ON(v.`plan_id`=g.`assosiated_plan_id` OR u.`plan_id`= v.`plan_id`)
WHERE u.`id`=1 AND p.`status` = 1
Demo 1 Demo2
For video ids you can use group_concat
SELECT
u.plan_id,
u.subscribed_on,
p.plan,
p.days_limit,
p.rate
,COUNT(DISTINCT v.`videoid`) `video_count` ,
GROUP_CONCAT(DISTINCT v.`videoid`) `video_ids`
FROM `usersubscription` u
JOIN `subscription_groups` g
ON (u.`plan_id` = g.`plan_id`)
RIGHT JOIN `subscription_plans` p
ON(u.`plan_id` = p.`id` OR g.`assosiated_plan_id` = p.`id`)
INNER JOIN `subscribed_videos` v ON(v.`plan_id`=g.`assosiated_plan_id` OR u.`plan_id`= v.`plan_id`)
WHERE u.`id`=1 AND p.`status` = 1
Demo 1a Demo 2a
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