I have some tables that have 'has and belongs to many' type relationships using join tables. The main tables are countries, programs and listings. The basic structure is:
countries
-id
-name
programs
-id
-name
listings
-id
-title
countries_programs
-country_id
-program_id
listings_programs
-listing_id
-program_id
listings_countries
-listing_id
-country_id
I have been doing the following to list the programs for a country when given the country id value (in this case 5):
SELECT programs.*
FROM programs
LEFT JOIN countries_programs ON programs_id = countries_programs.program_id
LEFT JOIN countries ON countries.id = countries_programs.country_id
WHERE countries_id = '5'
What I need to do is only return programs for the country only if the programs for the specific country actually have any listings that are also in that country. So it should return only the case where the programs are in the country specified and have listings that are in that program and also in that country.
If a program for the specified country has no listings, then I don't want it returned. I've been trying various combinations, but can't seem to get this to work. Does anyone see how this could be done?
I think I need to join the listings table, but nothing I've tried has come close.
To avoid returning duplicate data, I'd use an EXISTS clause. Also, switch to INNER joins to satisfy the country requirement.
SELECT p.*
FROM programs p
INNER JOIN countries_programs cp
ON p.id = cp.program_id
WHERE cp.country_id = 5
AND EXISTS (
SELECT 1 FROM listings_countries lc
INNER JOIN listings_programs lp
ON lc.listing_id = lp.listing_id
WHERE lc.country_id = c.id
AND lp.program_id = p.id
)
I've omitted the join to countries as you're only dealing with the country ID which is available in countries_programs.
Related
I have 4 tables
Products, Products_Location, Stores, Locations
Products = [Product_ID, P_Name, Price]
Products_Location = [Location_ID,Product_ID]
Locations = [Location_ID,Stores_ID, L_Name]
Stores = [Stores_ID, S_Name]
I'm trying to show columns from each table in 1 table but its not working
I tried to use inner join twice but didnt work (it work if I use 1 inner join)
here's my code
"SELECT Products.P_Name, Products.Price, Stores.S_Name, Locations.L_Name, Products.Product_ID
From Products, Locations
INNER JOIN Stores ON Locations.Stores_ID = Stores.Stores_ID
INNER JOIN Products_Location ON Products.Product_ID = Products_Location.Product_ID
Where P_Name LIKE '%$search%' OR Price LIKE '%$search%' OR S_Name LIKE '%$search%' OR L_Name LIKE '%$search%'
ORDER BY Price ASC";
As it appears you are relatively new to SQL, instead of getting pounded on your new posts, let me help a bit. Writing your queries with JOINS (left, right, full, outer, etc) are all typically based on TableA some join to TableB. So, try to think of your tables in a mental image. What is the relationship between them. THAT is the join. If one table is joined to multiple, then treat that as its own JOIN. I typically try to indent showing the level where TableA joins to TableB (or TableC, D, E, etc).
Then, also you can get used to using "alias" names so you dont have to keep writing LONG table name references. Taking your original query, it would be something more like...
select
p.p_name,
p.price,
s.s_name,
l.l_name,
p.product_id
From
Products p
-- first get the JOIN from product to its locations where available
JOIN Products_Location pl
on p.product_id = pl.product_id
-- now, from the product location to the location table.
-- notice my indentation to show its under the product_location
-- and not directly from the product table. Visually seeing the
-- hierarchical chain can help writing queries.
JOIN Locations l
on pl.location_id = l.location_id
-- and now, indent once more from location to the stores
JOIN Stores s
on l.stores_id = s.stores_id
where
-- NOW, you can put in your filtering criteria. But if ever a left or right
-- based join, you would just add that part of the criteria directly within
-- the JOIN portion
p.p_name like '%$search%'
OR p.Price LIKE '%$search%'
OR s.S_Name LIKE '%$search%'
OR l.L_Name LIKE '%$search%'
ORDER BY
p.Price ASC
Also, always try to qualify all columns in your query with table.column or alias.column so others know which table(s) the columns come from to prevent any ambiguity in the call.
You are performing a cross join on Products and Locations. Is it not your aim to instead join Products and Locations via the Products_Location table on Products.Product_ID = Products_Location.Product_ID and Products_Location.Location_ID = Locations.Location_ID?
A user has a student (one to one) and a student can have many languages and hobbies (both times many to many).
If I run this query,
SELECT email, hobbies.name, languages.name
FROM users
INNER JOIN students
ON users.id = students.user_id
LEFT OUTER JOIN languages_student
ON students.id = languages_student.student_id
INNER JOIN languages
ON languages_student.language_id = languages.id
LEFT OUTER JOIN hobbies_student
on students.id = hobbies_student.student_id
INNER JOIN hobbies
ON hobbies_student.hobbie_id = hobbies.id
WHERE users.id = 6
I get this result set:
If I add another language to a student, I get six rows in the result set. Is there a way of combining the second and third columns in order to get something more compact and not redundant? Can each hobby appear just once and get a NULL in languages when they run out?
There are a couple of approaches to being able to aggregate this information. One is to do this in your application logic based on the type of result set you currently show. This might be done be reading the rows from the result set into an appropriate data structure you can then use in your application to display this information.
The second approach is to use GROUP_CONCAT() to concatenate values within same group (in this case email name) into a single row. That might lead to a results set like this:
shields.katlynn#swaniaski.biz Endurance Sports,Golf Balochi,Assamesse
This might mean that in your application, you would need to split apart the data in each row to get to individual values.
An example of how you might write the query to get the above result would be:
SELECT
email,
GROUP_CONCAT(DISTINCT hobbies.name) AS hobbies,
GROUP_CONCAT(DISTINCT languages.name) AS languages
FROM users
INNER JOIN students
ON users.id = students.user_id
LEFT OUTER JOIN languages_student
ON students.id = languages_student.student_id
INNER JOIN languages
ON languages_student.language_id = languages.id
LEFT OUTER JOIN hobbies_student
on students.id = hobbies_student.student_id
INNER JOIN hobbies
ON hobbies_student.hobbie_id = hobbies.id
WHERE users.id = 6
GROUP BY email
In either case, you will need some sort of post-retrieval data processing in your application.
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;
I have a few tables:
In the product-table, I have a list of products.
In the user-table, I have a list of users.
In the group-table, I have groups of users.
IN the group_member-table, I have linked group and member (many-to-many)
In the user_product-table, I have linked user and product (many-to-many)
In the group_product-table, I have linked group and product (many-to-many)
So a user could have many products, a product could have many users. A user can be member of many groups, a group could have many members. A group could have many products, a product could have many groups. In other words, a product can have both groups and users.
What I want to ask the database is: "List all the products that a given user has access to, either through a direct relation in the user_product-table, or through the groups that the user is member of. I want the name of the product and the name of the user."
This is the query that I have come up with:
# First get all the products the user has access to via a group.
SELECT product.name,
user.first_name
FROM product
INNER JOIN group_product
ON group_product.product_id = product.product_id
INNER JOIN group
ON group.group_id = group_product.group_id
INNER JOIN group_member
ON group_member.group_id = group.group_id
INNER JOIN user
ON user.user_id = group_member.user_id
WHERE user.user_id = 1
UNION
# Now get all the products via direct access from user_product.
SELECT product.name,
user.first_name
FROM product
INNER JOIN user_product
ON user_product.product_id = product.product_id
INNER JOIN user
ON user.user_id = user_product.user_id
WHERE user.user_id = 1
Is this a good query, or is it better/possible to rewrite this into a JOIN only query? Would this be a fast query if there were 100 000 users, 10 000 groups and 100 products?
Is this a good database design, or is it better to store this logic in another manner?
(This is my first more complex query.)
Your query has the correct approach for your data model. The "correctness" of your data model really depends on volumes and frequency of change- you could opt to always store the explicit user-product relationship whenever a user is added to or removed from a group. This is a denormalizing tactic and moves the overhead from querying to updating - usually best not to consider these moves unless performance is tested and deficient.
A very tiny optimisation may be to avoid the join to user and product until after the union. At present you are only selecting the product name and user first_name, but if you were selecting many columns the sort/distinct would involve more work than strictly necessary, so something like:-
select product.name, user.first_name
from
(
select
group_product.product_id
from
group_product
inner join group on group.group_id = group_product.group_id
inner join group_member on group_member.group_id = group.group_id
where group_member.user_id = 1
union
select product_id product.name,
from user_product
where user_product.user_id = 1
) as d
inner join product on product.product_id = d.product_id
inner join user on user.user_id = 1
To filter a table output of selected entries from a single table i would need something like a multiple JOIN request through several tables.
I want to filter a table of people by a special column in the table. Lets say this column is "tasks." Now tasks is also another table with the column "people" and the values between those two tables are connected with an existant "join" table in the database, which is matching several IDs of one table to each ID of the other table.
Now if this would be simple as that i could just filter with an INNER JOIN and a special condition. The problem is, that the entries of the table "tasks" are connected to another table over a "join" table in the database. To simplify things lets say it is "settings". So each "task" consists of several "settings" which are connected via a join table in their IDs.
So what is the input?
I got an array of IDs, which are representing the settings-ids i do not want to be shown.
What should be the output?
As already said i want a filtered output of "people" while the filter is "settings."
I want the sql request to return each entry of the table "people" with only joined tasks that are not joining any of the "setting-ids" from the array.
I hope you can help me with that.
Thanks in advance!
Example
Settings-Table:
1. Is in progress
2. Is important
3. Has unsolved issues
Tasks-Table: (settings.tasks is the join table between many tasks to many settings)
1. Task from 01.01.2012 - JOINS Settings in 1 and 3 (In progress + unsolved issues)
2. Task from 02.01.2012 - JOINS Settings in 2 (Is important)
3. Task from 03.01.2012 - JOINS Settings in 1 and 2 (...)
People-Table: (people.tasks is the join table between many people to many tasks)
1. Guy - JOINS Tasks in 1, 2, 3 (Has been assigned to all 3 tasks)
2. Dude - JOINS Tasks in 1 (Has been assigned to the Task from 01.01.2012)
3. Girl - JOINS Tasks in 2, 3 (...)
Now there is an array passed to a sql query
[2,3] should return noone because every person is assigned in a task that was either important or had unsolved issues!
[3] would return me only the person "Girl" because it is the only one that is assigned to tasks (2 and 3) that had no unsolved issues.
I hope it is clear now. :)
SELECT DISTINCT PEOPLE.*
FROM PEOPLE INNER JOIN PEOPLE_TASKS ON PEOPLE.PERSON_ID = PEOPLE_TASKS.PERSON_ID
WHERE TASK_ID NOT IN (SELECT DISTINCT TASK_ID
FROM TASK_SETTINGS
WHERE SETTING_ID = <Id you don't want>)
EDIT (for supplying multiple setting ids you don't want)
SELECT DISTINCT PEOPLE.*
FROM PEOPLE INNER JOIN PEOPLE_TASKS ON PEOPLE.PERSON_ID = PEOPLE_TASKS.PERSON_ID
WHERE TASK_ID NOT IN (SELECT DISTINCT TASK_ID
FROM TASK_SETTINGS
WHERE SETTING_ID IN (<Id you don't want>))
First you have to join table people and table tasks with the join table, let's call it people_tasks.
select distinct p.* from people p
inner join people_tasks pt on p.people_id = pt.people_id
inner join tasks on t.tasks_id = pt.tasks_id
Then you have to join table tasks and table settings with the join table, let's call it tasks_settings. You have to join them in the current select.
select distinct p.* from people p
inner join people_tasks pt on p.people_id = pt.people_id
inner join tasks on t.tasks_id = pt.tasks_id
inner join tasks_settings ts on t.tasks_id = ts.tasks_id
inner join settings s on s.settings_id = ts.settings_id
and now you have all people connected with its tasks and its settings. Finally you need the restriction. With the people with the settings selected, you choose the others like this:
select distinct p.people_id from people p
inner join people_tasks pt on p.people_id = pt.people_id
where p.people_id not in (
select distinct p2.people_id from people p2
inner join people_tasks pt2 on p2.people_id = pt2.people_id
inner join tasks t2 on t2.tasks_id = pt2.tasks_id
inner join tasks_settings ts2 on t2.tasks_id = ts2.tasks_id
inner join settings s2 on s2.settings_id = ts2.settings_id
where s2.settings_id in (list of ids)
)