Complex MySQL query for specific problemm - mysql

I have searched and gone through the available topics similar to mine. But, failed to find that satisfies my requirements. Hence, posting it here.
I have four tables as follows:
"Organization" table:
--------------------------------
| org_id | org_name |
| 1 | A |
| 2 | B |
| 3 | C |
"Members" table:
----------------------------------------------
| mem_id | mem_name | org_id |
| 1 | mem1 | 1 |
| 2 | mem2 | 1 |
| 3 | mem3 | 2 |
| 4 | mem4 | 3 |
"Resource" table:
--------------------------------
| res_id | res_name | res_prop |
| 1 | resource1 | prop-1 |
| 2 | resource2 | prop-2 |
| 3 | resource3 | prop-3 |
| 4 | resource4 | prop-4 |
| 5 | resource1 | prop-5 |
| 6 | resource2 | prop-6 |
A constraint of UNIQUE INDEX (res_name, res_prop) is applied in the above table.
"member-resource" table:
--------------------------------------------
| sl_no | mem_id | res_id |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 4 | 3 |
| 5 | 3 | 4 |
| 6 | 2 | 3 |
| 7 | 4 | 3 |
| 8 | 1 | 5 |
| 9 | 1 | 6 |
I want to find out the distinct res_name from Resource table that have more than one res_prop for a specific organization. For example, expected output for organization A would be as follows:
| res_name | res_prop_count |
| resource1 | 2 |
| resource2 | 2 |
Any help in this regard will highly be appreciated.
Regards.

When doing things like this you should build your query up logically, so to start get all your resources and props from organisation A
SELECT r.res_name,
r.res_prop
FROM Resource r
INNER JOIN `member-resource` mr
ON mr.res_id = r.res_id
INNER JOIN Members m
ON m.mem_id = mr.mem_id
INNER JOIN Organization o
ON o.org_id = m.org_id
WHERE o.org_name = 'A'
Then you can start thinking about how you want to filter it, so you want to find resource names that have more than one different res_prop, so you need to group by res_name, and apply a HAVING clause to limit it to res_names with more than one distinct res_prop:
SELECT r.res_name,
COUNT(DISTINCT r.res_prop) AS res_prop_count
FROM Resource r
INNER JOIN `member-resource` mr
ON mr.res_id = r.res_id
INNER JOIN Members m
ON m.mem_id = mr.mem_id
INNER JOIN Organization o
ON o.org_id = m.org_id
WHERE o.org_name = 'A'
GROUP BY r.res_name
HAVING COUNT(DISTINCT r.res_prop) > 1;
Example on SQL Fiddle

I'm not entirely sure I understand what you are looking for, but I think this should work:
SELECT Resource.res_name,
COUNT(DISTINCT Resource.res_prop) AS res_prop_count
FROM Resource
INNER JOIN member_resource
USING (res_id)
INNER JOIN Members
USING (mem_id)
INNER JOIN Organization
USING (org_id)
WHERE Organization.org_name = 'A'
GROUP BY res_name
HAVING res_prop_count > 1;

try this:
select
res_name, count(res_prop) as res_prop_count
from
Resource
where
res_id in (select
res_id
from
member_resource
where
mem_id in (select
mem_id
from
Members
where
org_id = (select
org_id
from
Organization
where
org_name = 'A'))) group by res_name

Try this:
If you have Organization ID then use the query below:
SELECT r.res_name, COUNT(DISTINCT r.res_prop) res_prop_count
FROM member-resource mr
INNER JOIN Resource r ON mr.res_id = r.res_id
INNER JOIN Members m ON mr.mem_id = r.mem_id
WHERE m.org_id = 1
GROUP BY r.res_id HAVING res_prop_count > 1;
If you have Organization name then use the query below:
SELECT r.res_name, COUNT(DISTINCT r.res_prop) res_prop_count
FROM member-resource mr
INNER JOIN Resource r ON mr.res_id = r.res_id
INNER JOIN Members m ON mr.mem_id = r.mem_id
INNER JOIN Organization o ON m.org_id = o.org_id
WHERE m.org_name = 'A'
GROUP BY r.res_id HAVING res_prop_count > 1

Related

Mysql get where not in with a twist

I want to get the firms that dont have contact method main_phone or phone This is my schema
Firms table
+----+-------+
| id | name |
+----+-------+
| 1 | Firm1 |
| 2 | Firm2 |
| 3 | Firm3 |
+----+-------+
Contacts
+----+----------+---------+
| id | label | firm_id |
+----+----------+---------+
| 1 | Contact1 | 1 |
| 2 | Contact2 | 1 |
| 3 | Contact3 | 2 |
| 4 | Contact4 | 3 |
+----+----------+---------+
contact_methods
+----+-------------+------------+
| id | method_type | contact_id |
+----+-------------+------------+
| 1 | main_phone | 1 |
| 2 | main_fax | 1 |
| 3 | email | 1 |
| 4 | main_fax | 4 |
| 5 | main_fax | 3 |
| 6 | phone | 2 |
| 7 | main_mobile | 1 |
| 8 | url | 4 |
+----+-------------+------------+
and this is my query
SELECT
firms.id
FROM firms
JOIN contacts ON (contactable_id = firms.id)
JOIN contact_methods ON contacts.id = contact_methods.contact_id
WHERE
firms.active = 1
AND
contact_methods.method_type NOT IN ('mobile','phone','main_mobile','main_phone')
I am getting all firms :s
Your code checks whether each firm has any contact type that does not belong to the list - while you want to ensure that none of the firm contact does.
One option uses aggregation:
select f.*
from firm f
left join contacts c on c.firm_id = f.id
left join contact_methods cm on cm.contact_id = c.id
group by f.id
having not max(cm.method_type in ('mobile','phone','main_mobile','main_phone')) <=> 1
Alternatively, you can use not exists:
select f.*
from firm f
where not exists (
select 1
from contacts c
inner join contact_methods cm on cm.contact_id = c.id
where c.firm_id = f.id and cm.method_type in ('mobile','phone','main_mobile','main_phone')
)
SELECT *
FROM firms
WHERE NOT EXIST ( SELECT NULL
FROM contacts
WHERE firms.id = contacts.firm_id )
contact_methods not needed in this task.
If you need the same for definite contacts types then use:
SELECT *
FROM firms
WHERE NOT EXIST ( SELECT NULL
FROM contacts
JOIN contact_methods ON contacts.id = contact_methods.contact_id
WHERE firms.id = contacts.firm_id
AND contact_methods.method_type NOT IN ({types list}) )
i want just the firms.id that does not have a contact method in ('mobile','phone','main_mobile','main_phone')
SELECT *
FROM firms
WHERE NOT EXIST ( SELECT NULL
FROM contacts
JOIN contact_methods ON contacts.id = contact_methods.contact_id
WHERE firms.id = contacts.firm_id
AND contact_methods.method_type IN ('mobile', 'phone', 'main_mobile', 'main_phone') )

Filter left join output

I have
jobs table with fields
jobId, jobTitle, jobDesc
jobQuotes Table with fields
id, user_id, quote
jobQuotes table has the quote of users who gave quote for the job.
I need those jobs for which a specific user has NOT given any quote.
Using LEFT JOIN I get all the jobs irrespective of the jobQuotes table.
And INNER JOIN only gives all the jobs that has a relevant jobQuote.
But I need those jobs for which a specific user has NOT given any quote.
My Query is
SELECT * FROM dummy_jobs J LEFT JOIN jobQuotes JQ ON J.jobId=JQ.jobId WHERE MATCH (J.jobTitle, J.jobDescription) AGAINST ('php, mysql');
How to filter this result set so that output doesn't have specific user_id in jobQuotes?
SELECT jobstable.jobid from jobstable inner join
(SELECT id from jobQuotes where userid = 953 and quote IS NULL) dummy_table
on dummy_table.id == jobstable.jobid;
The Answer is According to the comment you were given
"I want all jobs for which userId= 953 has not given any quote"
An approach might be to associate a specific user with all jobs by using a cross join and then left join to job quotes with a null test to find those not quoted for.
for example
users
+----+----------+
| id | username |
+----+----------+
| 1 | John |
| 2 | Jane |
| 3 | Ali |
| 6 | Bruce |
| 7 | Martha |
+----+----------+
Jobs
+-------+----------+---------+
| jobId | jobTitle | jobDesc |
+-------+----------+---------+
| 1 | a | a |
| 2 | b | b |
| 3 | c | c |
+-------+----------+---------+
Jobquotes
+------+---------+-------+
| id | user_id | quote |
+------+---------+-------+
| 1 | 3 | 10 |
| 2 | 2 | 10 |
+------+---------+-------+
select t.id,t.username,t.jobid,t.jobtitle,t.jobdesc
from
(
select u.id,u.username, s.jobid,s.jobtitle,s.jobdesc
from users u
cross join (select distinct jobid , jobtitle, jobdesc from jobs) s
where u.id = 3
) t
left join jobquotes jq on jq.id = t.jobid and jq.user_id = t.id
where jq.id is null
result
+----+----------+-------+----------+---------+
| id | username | jobid | jobtitle | jobdesc |
+----+----------+-------+----------+---------+
| 3 | Ali | 2 | b | b |
| 3 | Ali | 3 | c | c |
+----+----------+-------+----------+---------+

Mysql how to combine GROUP BY and COUNT here

This must be quite easy, but I cannot find a good solution myself.
I have two tables:
file
+----+--------+
| id | system |
+----+--------+
| 1 | AA |
| 2 | AA |
| 3 | BB |
| 4 | AA |
+----+--------+
feature
+----+---------+------+
| id | file_id | name |
+----+---------+------+
| 1 | 1 | A |
| 1 | 2 | A |
| 1 | 2 | B |
| 1 | 3 | B |
| 1 | 3 | C |
| 1 | 4 | A |
| 1 | 4 | B |
| 1 | 4 | C |
+----+---------+------+
and I want to count how many times a feature was added to files with a specific system. For that, I have the following query:
SELECT f.name, COUNT(*) AS nr
FROM dossier d
JOIN feature f
ON f.file_id = d.id
WHERE d.system = 'AA'
AND d.id NOT IN (3157,3168,3192)
GROUP BY f.name
which gives the desired output:
+------+----+
| name | nr |
+------+----+
| A | 3 |
| B | 2 |
| C | 1 |
+------+----+
Now I also want to know the total amount of files with the same specific system. A simple separate query would be:
SELECT COUNT(*) FROM file WHERE system = 'AA' AND id NOT IN (3157,3168,3192)
I've added the extra AND id NOT IN (which is irrelevant for this example) just to show that the actual query is much more complex. If I use a separate query to get the total I would have to duplicate that complexity, so I want to avoid that by returning the total from the same query.
So how can I count the number of files in the first query?
Desired output:
+------+----+-------+
| name | nr | total |
+------+----+-------+
| A | 3 | 3 |
| B | 2 | 3 |
| C | 1 | 3 |
+------+----+-------+
Here is one way using Sub-query
SELECT f.NAME,
Count(*) AS nr,
(SELECT Count(*)
FROM FILE
WHERE system = 'AA'
AND id NOT IN ( 3157, 3168, 3192 )) as Total
FROM dossier d
JOIN feature f
ON f.file_id = d.id
WHERE d.system = 'AA'
AND d.id NOT IN ( 3157, 3168, 3192 )
GROUP BY f.NAME
Or Use CROSS JOIN
SELECT *
FROM (SELECT f.NAME,
Count(*) AS nr,
FROM dossier d
JOIN feature f
ON f.file_id = d.id
WHERE d.system = 'AA'
AND d.id NOT IN ( 3157, 3168, 3192 )
GROUP BY f.NAME) A
CROSS JOIN (SELECT Count(*) AS Total
FROM FILE
WHERE system = 'AA'
AND id NOT IN ( 3157, 3168, 3192 )) B

MySQL return distinct accounts from one table, average rating from another, all based on service area in another table

as the title states, I'm trying to return a query which gets account details from the accounts table, gets the average rating for the account from the reviews table, and limits the rows to the service location associated to the account.
Here are the simplified tables:
accounts
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
| 1 | John | Smith |
| 2 | Bob | Doe |
| 3 | Alice | McLovin |
| 4 | Bruce | Wayne |
+----+------------+-----------+
reviews
+----+-------------+-----+--------+
| id | acccount_id | ... | rating |
+----+-------------+-----+--------+
| 1 | 1 | ... | 9 |
| 2 | 1 | ... | 10 |
| 3 | 2 | ... | 7 |
| 4 | 1 | ... | 2 |
| 5 | 4 | ... | 6 |
+----+-------------+-----+--------+
service_area
+----+-------------+---------+
| id | acccount_id | city_id |
+----+-------------+---------+
| 1 | 1 | 1140 |
| 2 | 1 | 1001 |
| 3 | 2 | 1140 |
| 4 | 1 | 1086 |
| 5 | 4 | 1001 |
+----+-------------+---------+
For example, the user may request to view all accounts which have a service area of city_id 1140. The query should then return the first_name, last_name, and average rating for each account within the specified service area. Note that accounts can have multiple service areas (see service_area table).
Thanks in advance!
UPDATE:
The following QUERY did the trick! I needed a LEFT JOIN for the reviews table!
SELECT a.first_name, a.last_name, AVG(r.rating) avg_rating
FROM accounts a
JOIN service_area sa
ON a.id = sa.account_id AND sa.city_id = 1140
LEFT JOIN reviews r
ON a.id = r.account_id
GROUP BY a.id
You can use joins and simple aggregation with group by
SELECT a.*,
AVG(r.rating) avg_rating
FROM accounts a
JOIN reviews r ON a.id = r.acccount_id
JOIN service_area s ON a.id = s.acccount_id
WHERE s.city_id = 1140
GROUP BY a.id
Result set will be like
id first_name last_name avg_rating
------ ---------- --------- ------------
1 John Smith 7.0000
2 Bob Doe 7.0000
Use LEFT join for when there are no reviews available
SELECT a.*,
COALESCE(AVG(r.rating),0) avg_rating
FROM accounts a
LEFT JOIN reviews r ON a.id = r.acccount_id
JOIN service_area s ON a.id = s.acccount_id
WHERE s.city_id = 1140
GROUP BY a.id
DEMO

SELECTing rows where no other rows match

This seemed pretty simple to start with, but it's getting awkward.
Suppose we have a table containing...
+---------+-----------+
| chat_id | friend_id |
+---------+-----------+
| A | 1 |
| A | 2 |
| A | 3 |
| B | 1 |
| B | 2 |
| C | 1 |
| C | 2 |
| C | 3 |
| D | 1 |
| D | 2 |
| D | 3 |
| D | 4 |
| D | 5 |
| E | 0 |
| E | 1 |
| E | 2 |
| E | 3 |
| E | 4 |
| E | 5 |
| E | 6 |
| E | 7 |
| F | 0 |
| F | 1 |
| G | 1 |
| G | 2 |
+---------+-----------+
And I wish to select only those chat_id that have friend_ids 1 and 2 and no other friend_id, what would the SQL be to get B and G returned?
So far, the best I've come up with is:
SELECT DISTINCT a.chat_id, COUNT(*)
FROM tt2 a
LEFT JOIN tt2 b
ON a.chat_id = b.chat_id
AND b.friend_id NOT IN (1,2)
WHERE a.friend_id in (1,2)
and b.chat_id IS NULL GROUP BY a.chat_id HAVING COUNT(*) = 2;
+---------+----------+
| chat_id | count(*) |
+---------+----------+
| B | 2 |
| G | 2 |
+---------+----------+
2 rows in set (0.00 sec)
And just in case I was looking for chat_id where only 1,2,3 exist...
SELECT DISTINCT a.chat_id, COUNT(*)
FROM tt2 a
LEFT JOIN tt2 b
ON a.chat_id = b.chat_id
AND b.friend_id not in (1,2,3)
WHERE a.friend_id IN (1,2,3)
AND b.chat_id IS NULL
GROUP BY a.chat_id
HAVING COUNT (*) = 3;
+---------+----------+
| chat_id | count(*) |
+---------+----------+
| A | 3 |
| C | 3 |
+---------+----------+
But this table could get massive and I need the SQL to be swift, does anyone know a better way?
To try and clarify... I get given a bunch of friend_id's and I want to get chat_id where only those friend_id exist for that chat_id.... with the SQL being quick (on sqlite)
Many thanks in advance!
Here's an option that should be able to limit the amount of data needed
SELECT
d.chat_id,
COUNT(DISTINCT s.friend_id) AS matchedFriends,
COUNT(DISTINCT d.friend_id) AS totalFriends
FROM tt2 AS d
INNER JOIN tt2 AS s
ON s.chat_id = d.chat_id
AND s.friend_id IN (1,2)
GROUP BY d.chat_id
HAVING matchedFriends = 2
AND totalFriends = matchedFriends
The INNER JOIN s makes sure that it only hits rows that have got at least one of the requested friends in. The matchedFriends count checks how many of the requested friends are found.
The totalFriends count then checks how many friends in total are on that chat.
Finally the HAVING first makes sure there are 2 matched friends, and then checks the number of friends in total equals the number of matched friends.
This will require you to supply both a list of friends, and a number of friends you are looking for, but should be efficient.
For increased efficiency, have an index on (chat_id,friend_id) (if you don't already, assuming it's a 2-part PK at time of writing)
Try this:
SELECT chat_id, GROUP_CONCAT(DISTINCT friend_id ORDER BY friend_id) AS friends
FROM table_1
GROUP BY chat_id
HAVING friends = '1,2'
Note: This works in mysql but I doubt that it will work on sqlite.