SQL Select Many to Many - mysql

I'm using MYSQL 5.X and I'm having trouble figuring out a Query;
I have 2 entities with a many to many relationship.
client and service, therefore I have 3 tables:
clients, clients_has_services and services.
I need to Select EVERY Service that is NOT related to a particular client.
for example:
I have client with ID 1, and 4 Services in Total (Service1, Service2, Service3, Service4), client 1 has a relationship with Service1) so I need to retrieve all Other services (Service2, Service3, Service4)
Suggestions?

First, get a set all services.
Join that to the row from clients for the "particular client", so you have a set of all services for the client.
The "trick" is to use an anti-join pattern to exclude rows where you have "matches" in the clients_have_services table.
If you have the unique identifier for the client (and you only need the list for a single client), something like this:
SELECT s.*
FROM services s
LEFT
JOIN clients_have_services h
ON h.service_id = s.id
AND h.client_id = 42
WHERE h.service_id IS NULL
ORDER BY s.id
The outer join returns all rows from services, along with any "matching" rows from the clients_have_services table. The "trick" is the predicate in the WHERE clause that excludes any rows where a match was found, leaving just the services that are not related to the particular client.
If you are doing this for multiple clients... you'd need to also return the client cross joined with services (as cross product of clients and services), and then exclude the matches.
For example:
SELECT c.id AS client_id
, s.*
FROM clients c
CROSS
JOIN services s
LEFT
JOIN clients_have_services h
ON h.service_id = s.id
AND h.client_id = c.id
WHERE h.service_id IS NULL
ORDER BY c.id, s.id
There are a couple of other query patterns that will return an equivalent result, for example, a NOT EXISTS
SELECT s.*
FROM services s
WHERE NOT EXISTS
( SELECT 1
FROM clients_have_services h
WHERE c.client_id = 42
AND h.service_id = s.id
)
ORDER BY s.id

Assuming that your client_has_services rectifies the many-to-many so you have one to many on both sides, so your client_has_services should have:
ID (Key), Client_ID, Service_ID
SELECT * FROM services WHERE services.id != ANY (select client_has_services.services_id from client_has_services where client_has_services.client_id = ID_num_provided)

Related

mySQL - How to use a COUNT() statement in a JOIN

In a mySQL DB there is a table named clients. Each client row has a column client_num. To list all clients I simply use this statement.
SELECT * FROM clients
Now each client may have or may have not some numbers of subclients which is listed in a seperate table named subclients and referenced through the foreign key client_num in the table.
To find out how many subclient a client may have I can query the subclient table in a seperate call like this: Lets say we have a client with the client_num 254.
SELECT count(1) FROM subclients WEHRE sublicents.client_num = 254
So the query returns something like 0, 1, 2, 3 , n
In a JOIN I assume I have to do something like this :
SELECT
c.*,
sc.?????
FROM clients AS c
LEFT JOIN subclients AS sc ON sc.client_num = 254
But I do not know how to incorporate the count() statement in the JOIN for the amount of subclients. Also I do not know if LEFT JOIN is correct.
Any help is appreciated
EDIT :
I must add that I expect all rows to be returned from clients. The additional returned column COUNT(sc.client_num) carries than the amount of subclients. In the Answer 2 below I get only those clients returned which have subclients. Clients which do not have subclients are not returned. How to fix that ?
You can use the following query to get the count of all sub-clients for each client:
SELECT
c.*,
COUNT(sc.client_num)
FROM clients AS c
LEFT JOIN subclients AS sc ON sc.client_num = c.client_num
WHERE c.client_num = 254
GROUP BY sc.client_sum
solution using a sub-select:
SELECT *, (SELECT COUNT(*) FROM subclients WHERE client_num = clients.client_num)
FROM clients
WHERE client_num = 254
demo: http://sqlfiddle.com/#!9/4930a/8/1
The solution I read from a similar query on Stack Overflow was to use a sub-select. If your only purpose of the join is to Count I would suggest that the join is unneeded. So that:
SELECT clients.*, (SELECT COUNT(*) FROM subclients
WHERE subclients.client_num = clients.id ) AS numb FROM clients WHERE ....
And then for a row of clients, they will have a column numb which will be number of subclients that that client has.
A reference.

using JOIN and subquery in mysql

I posted a question about 2 weeks ago about 'one to many' relation between SQL tables. Now I have a bit of a different scenario. Basically, there are two tables - coffee_users and coffee_product_registrations. The latter is connected to coffee_users table with 'uid' column. So basically coffee_users.uid = coffee_product_registrations.uid
A single user can have multiple products registered.
What I want to do is to display some product information (from coffee_product_registrations) along with some user information (from coffee_users), BUT retrieve only those rows that have more than 1 product registrations.
So to simplify, here are the steps I need to take:
Join two tables
Select users that have multiple products registered
Display all their products along with their names and stuff
My current SQL query looks like this:
SELECT c.uid, c.name, cpr.model
FROM coffee_users c
JOIN coffee_product_registrations cpr on c.uid = cpr.uid
GROUP BY c.uid
HAVING COUNT(cpr.uid) > 1
This joins the two tables on 'uid' column but displays only 1 row for each user. It selects just users that have multiple products registered.
Now I need to take these IDs and select ALL the products from coffee_product_registrations based on them.
I cannot figure out how to put this in one query.
Replace cpr.*, c.* with columns which you want to extract feom the query
Try this:
SELECT cpr.*, c.*
FROM coffee_product_registrations cpr
INNER JOIN coffee_users c ON c.uid = cpr.uid
INNER JOIN (SELECT cpr.uid
FROM coffee_product_registrations cpr
GROUP BY cpr.uid
HAVING COUNT(DISTINCT cpr.productId) > 1
) AS A ON c.uid = A.uid;

MySQL select count with join

I am trying to get a list of data from multiple tables in a view for analytics - most of the data comes from one table and then I need to get the counts from multiple other tables to show number of logins, number of users and so on.
I am using the below query (simplified to show only some rows and one join):
Select
companies.company_name, companies.last_login, companies.last_ip,
(SELECT COUNT(*) FROM auditing_master GROUP BY company_id) AS audit_count
From
companies
Left Join
auditing_master On auditing_master.company_id = companies.id
Group by
companies.id
When I run this I get an error:
#2014 - Commands out of sync; you can't run this command now
If I run it without the GROUP BY in the SELECT COUNT (*) then it doesn't give any error but it returns a count of all entries in the auditing_master table regardless of which company it refers to.
How can I get a single query to show me the required data from the companies table whilst also showing a total from auditing master (and others) grouped by the company ID?
Edit
Code when using multiple count/joins:
Select
c.company_name, c.last_login, c.last_ip,
COUNT(am.company_id) AS audit_count,
COUNT(u.company_id) AS users,
COUNT(e.company_id) AS employees
From
companies c
Left Join
auditing_master am On am.company_id = c.id
Left Join
users u On u.company_id = c.id
Left Join
employees e On e.company_id = c.id
Group by
c.id
This query locally in PHPMyAdmin on WAMP with only around 10 companies takes 7 seconds to complete and give "meanngless" results that don't correlate to anything.
Thanks
Inner queries in the SELECT clause must return a SINGLE VALUE RESULT.
Something similar to that, should bring what u need.
Select
companies.company_name, companies.last_login, companies.last_ip,
COUNT(*) AS audit_count
From
companies
Left Join
auditing_master On auditing_master.company_id = companies.id
Group by
companies.id
Did you try this? (No need a subquery because no where clause into it, and LEFT JOIN already add auditing_master rows grouped by company_id = companies.id)
Select
companies.company_name, companies.last_login, companies.last_ip,
COUNT(company_id) AS audit_count
From
companies
Left Join
auditing_master On auditing_master.company_id = companies.id
Group by
companies.id
EDIT
Maybe if you add IF statement to avoid COUNT when there is no auditing_master for a given company.
SELECT
companies.company_name, companies.last_login, companies.last_ip,
IFNULL(am.company_id,0,COUNT(am.company_id)) AS audit_count
FROM
companies
INNER JOIN
auditing_master am ON auditing_master.company_id = companies.id
GROUP BY
companies.id
Feel free to put the entire SQL... Because the problem can be somewhere else!

More Efficient Version of this Correlated Subquery

I've got 3 different tables and I need to pull data once from 2 of them, and twice from the third. The tables are jobs, customers, and customers_attributes. I'm trying to pull data for a specific job, and part of that data is information about the customer who owns the job. Customer data is stored in customers_attributes where the type of data is defined as an integer that corresponds with a type(using strings here for simplicity's sake) and then a content field contains the data itself.
In this case, I need to pull 2 rows from the customers_attributes table that correspond to the customer that corresponds to the job. One row for 'PhoneNumber', and another row for 'CustomerInfo'. I used an INNER JOIN for one of them, but because I can't put WHERE values for both, I used a subquery for the other one. I think this is really nasty and I'm sure there's got to be a cleaner way of doing it:
SELECT jobs.*, customers.Name AS CustomerName,
customers_attributes.Content AS PhoneNumber,
( SELECT `Content`
FROM customers_attributes
WHERE Type = 'CustomerInfo' AND ForeignCustomer = jobs.Customer
LIMIT 1) AS CustomerInfo
FROM jobs
INNER JOIN customers ON jobs.Customer = customers.ID
INNER JOIN customers_attributes ON jobs.Customer = customers_attributes.ForeignCustomer
WHERE jobs.ID = $jobID AND customers_attributes.Type = 'PhoneNumber'
LIMIT 1
I should mention that a customer could have multiple rows for the same attribute if they have more than 1 job, and this query ideally should either return the latest information, or the information that was submitted at the same time as the job(based on corresponding ID orders).
Just join the same table again under a different alias.
SELECT j.*, c.Name AS CustomerName,
ca.Content AS PhoneNumber,
ca2.Content as CustomerInfo
FROM jobs j
INNER JOIN customers c ON j.Customer = c.ID
INNER JOIN customers_attributes ca ON (j.Customer = ca.ForeignCustomer)
INNER JOIN customers_attributes ca2 ON (j.Customer = ca2.ForeignCustomer)
WHERE j.ID = '$jobID'
AND ca.Type = 'PhoneNumber'
AND ca2.Type = 'CustomerInfo'
LIMIT 1
Warning
It looks like you're using PHP. If you must insist on using the outdated mysql_ library and not the much improved mysqli_ lib.
Please remember to use mysql_real_escape_string() and to quote your $vars.
If not you'll be hid by SQL-injection problems.

MYSQL populate foreign id with value

I have a printed table here, and I issue a query to attempt to join the tables where the Tech_id, clients_id, job_id, part_id should populate with corresponding key in their tables / column too.
Here is my query:
SELECT * FROM work_orders, technicians as tech, parts_list as parts, job_types as job, clients as client
LEFT JOIN technicians ON tech_id = technicians.tech_name
LEFT JOIN parts_list ON part_id = parts_list.Part_Name
LEFT JOIN job_types ON job_id = job_types.Job_Name
LEFT JOIN clients ON clients_id = clients.client_name
I've messed around with multiple different variations, this one seem to be syntax correct, but now I'm getting: Column 'clients_id' in on clause is ambiguous
I'm sure that it will happen for not only clients but maybe others. I want to be able to print the table as in the picture above, but with the clients listed. Is it possible to be done via one query as well? thanks.
You have two problems.
First (this might not be your problem, but that's a "good practice"), you shouldn't use SELECT *, as you could indeed have a field with same name in different tables.
This is one (of the many) good reason to avoid * in a Select clause.
Then, your main problem is that you select tables in your from clause, and then again by joining.
Problematic line :
FROM work_orders, technicians as tech, parts_list as parts, job_types as job, clients as client
So (I don't know your table structure, so they may be errors, but you've got the idea)
SELECT
w.client_id,
t.tech_name
--etc
FROM work_orders w
LEFT JOIN technicians t ON c.tech_id = t.tech_name
LEFT JOIN parts_list p ON c.part_id = p.Part_Name
LEFT JOIN job_types j ON w.job_id = j.Job_Name
LEFT JOIN clients c ON w.clients_id = c.client_name
This means that clients_id exists in multiple tables. You need to specify which one you want. So if you for example want the clients_id of the clients table, do SELECT clients.clients_id
If all the fiels listed in your question are in the clients table you could do:
SELECT clients.* FROM work_orders, technicians as tech, parts_list as parts, job_types as job, clients as client
LEFT JOIN technicians ON tech_id = technicians.tech_name
LEFT JOIN parts_list ON part_id = parts_list.Part_Name
LEFT JOIN job_types ON job_id = job_types.Job_Name
LEFT JOIN clients ON clients_id = clients.client_name