What is the SQL command for a many to many relationship together with one to many? - sql

The following is my sample:
How do List All items belonging to building X, with a category of Y?
I am unsure what to look for (is it joins? inner outer?). I think my difficulty in finding the answer via google is because I do not know how to properly phrase the question. Any help please.

How about exists?
select i.*
from items i
where exists (select 1
from building b
where b.id = i.building_id and b.name = 'X'
) and
exists (select 1
from category_has_item chi join
category c
on c.category_id = chi.id
where chi.item_id = i.id and c.category = 'Y'
);
In your case, you can also reasonably express this directly with joins:
select i.*
from items i join
buildings b
on i.building_id = b.id join
category_has_item chi
on chi.item_id = i.id join
category c
on chi.category_id = c.id
where b.name = 'X' and c.category = 'Y';
I think this is essentially the same in your case, assuming there are no duplicates in category_has_item. If both relationships were many-to-many, then the first approach would probably be faster.

Related

SQL - How do I select rows from one table depending on data from two other tables?

I have an SQL question. It's a simple problem, but I'm not an SQL guy at all.
Here is the situation, I have three tables:
CUSTOMER(
PK(customer_id)
)
LOAN(
PK(loan_id),
customer_it,
behavior_id
)
BEHAVIOR(
PK(behavior_id),
unpaid_number
)
// PK(x): x is a primary key.
I would like to select all of the CUSTOMERs who have an unpaid_number >= 1.
Can anybody show me a way to work this around?
Thanks
You are looking for INNER JOIN. Use like:
SELECT * FROM CUSTOMER c
INNER JOIN LOAN l ON c.customer_id = l.customer_it
INNER JOIN BEHAVIOR b ON b.behavior_id = l.behavior_id
WHERE b.unpaid_number>=1
Use inner join
SELECT c.* FROM CUSTOMER c INNER JOIN LOAN l ON l.customer_id = c.Customer_id INNER JOIN BEHAVIOR b ON b.behavior_id = l.behavior_id WHERE unpaid_number >=1
Actually, if you want all customers, you presumably want one row per customer, regardless of the number of matching rows in behavior.
That would suggest using exists or in:
select c.*
from customer c
where exists (select 1
from loan l join
behavior b
on b.behavior_id = l.behavior_id
where b.unpaid_number >= 1 and
l.customer_id = c.customer_id
);
This is particularly important if you are considering using select distinct.
Please, try below code
SELECT c.*
FROM CUSTOMER c
INNER JOIN LOAN l
ON l.customer_id = c.Customer_id
INNER JOIN BEHAVIOR b
ON b.behavior_id = l.behavior_id
WHERE unpaid_number >=1
try this?
SELECT LOAN.customer_it FROM LOAN
WHERE LOAN.behavior_id IN
(SELECT BEHAVIOR.behavior_id
from BEHAVIOR where BEHAVIOR.unpaid_number>=1)

SQL ACCESS (need ideas and help about a query)

I am new at access SQL and i need help with some query. What i want is to find those customers that prefer a car (manufacturer and model) from prefer_to_buy AND prefer_to_rent that no one else prefer.
For example if 2 customers prefer toyota aygo must not be in the result table.
customer(customer_id,name)
prefer_to_buy(customer_id,manufacturer,model)
prefer_to_rent(customer_id, manufacturer,model)
I have tried a lot of ways including exists and i know there must be about 2-3 subqueries but i cant get it to work, any ideas?
Your problem definition is very vague, so the answer is also sort of generic. You should try to create a Left Outer Join on customer table and, for example, a "prefer_to_buy" Table using customer_id as a join field, and include:
customer_id,name from the left table and manufacturer,model from the right table. The same logic applies to prefer_to_rent Table: you can actually combine these 3 Tables in a single Access SQL query using the aforementioned Outer Joins.
Hope this may help. Best regards,enter code here
There are a couple of parts to cover here. First, you could use the union operator to treat prefer_to_buy and prefer_to_rent as a single table (possibly with an additional literal "column" to indicate preference type). Once you've done this, you could use the exists operator to make sure no other customers prefer this car:
SELECT c.name, p.manufacturer, p.model
FROM customer c
JOIN (SELECT customer_id, manufacturer, model
FROM prefer_to_buy
UNION
SELECT customer_id, manufacturer, model
FROM prefer_to_buy) p ON c.customer_id = p.customer_id
WHERE NOT EXISTS (SELECT *
FROM prefer_to_buy pb
WHERE c.customer_id != pb.customer_id AND
p.manufacturer = pb.manufacturer AND
p.model = pb.model) AND
NOT EXISTS (SELECT *
FROM prefer_to_rent pr
WHERE c.customer_id != pr.customer_id AND
p.manufacturer = pr.manufacturer AND
p.model = pr.model)
First you need to do to two joins one on prefer_to_buy AND one on prefer_to_rent.
Next, you need to check if any one else would like the same manufacturer and model. Do this with a exist
SELECT *
FROM customer AS c
JOIN prefer_to_buy AS pb ON c.customer_id = pb.customer_id
JOIN prefer_rent AS pr ON c.customer_id = pr.customer_id
WHERE NOT EXISTS ( SELECT 'x' FROM prefer_to_buy AS pb1 WHERE pb.manufacturer = pb1.manufacturer AND pb.model = pb1.model AND pb.customer_id <> pb1.customer_id)
AND NOT EXISTS ( SELECT 'x' FROM prefer_to_rent AS pr1 WHERE pb.manufacturer = pr1.manufacturer AND pb.model = pr1.model AND pb.customer_id <> pr1.customer_id)

Search the last comment in a many-to-many relation

I have 3 tables :
Post, PostComment and Comment.
It's a many-to-many relation.
I want to select for each post the last comment.
So something like (select * from comment order by create_at DESC limit 1) doest not work here.
And I want something like:
select *
from post as p
left join post_comment as pc on (pc.post_id = p.id)
left joint comment as c on (c.id = pc.comment_id)
left joint comment as c2 on (c2.id = pc.comment_id and c2.id > c.id)
where c2.id is null
It works very well for one-to-many relation, but I can't get ride of this for many-to-many.
Note: I renamed my table. In my code, I do not use comment and post. And I do need a many-to-many relation.
Thanks you
the main table on your query should be postcomment and you can group by postid and get the max(postcomment) assuming it is an autoincrementid.
then you just join the result with the other tables to get the rest of the data.
Since you probably have a lot of data you need from the other tables and to avoid adding all these data to the group by, I would use a CTE:
(CTE is a sql server syntax, if you are not using sql server you'll have to use another mechanism to store this temporary data)
with my_cte as
(
select idpost, max(idcomment) as last_comment_id
from postcomment pc
group by idpost
)
select *
from my_cte
join post p on p.idpost=my_cte.idpost
join comment c on c.idcomment=my_cte.last_comment_id
I did something like that :
select *
from post as p
left join post_comment as pc on (pc.post_id = p.id)
left join comment as c on (c.id = pc.comment_id)
left outer join
(post_comment as pc2
inner join comment as c2 on (c2.id = pc2.comment_id)
) on (bc2.post_id = p.id and c1.created_at < c2.created_at)
where c2.id is null

Translate an SQL subquery to a more efficient version

I'm working with a moderately large MSAccess .mdb file that I need to manipulate with SQL. Unfortunately some statements which work in theory seem to cause it to hang, and I've run into a brick wall.
Here is a simplified representation in SQL Fiddle
Three tables: products, product_category, and categories
I need to SELECT categories that ONLY contain items that have the field 'HIDE = 1'
If a category contains products that are hide = 0, it should not be selected.
I can do this relatively easily with subqueries, but the query stalls out. In the past queries that rely on left joins seem to execute efficiently, but I cannot wrap my mind around joins enough to translate this query into that format.
EDIT:
SELECT c.categoryid
FROM product_category AS c
LEFT JOIN
(
SELECT DISTINCT c.categoryid
FROM product_category AS c
LEFT JOIN products AS p
ON c.catalogid = p.catalogid
WHERE p.hide = 0
) y ON y.categoryid = c.categoryid
WHERE y.categoryid IS NULL
Someone posted the above query as an answer but then for some reason deleted it. As far as I can tell it works and works quickly. I consider this question to be answered. If I remember I will self-post the answer once the timer allows me to.
I believe you just need to un-correlate the subquery eg...
SELECT c.categoryid FROM product_category AS c
WHERE c.categoryid NOT IN
(SELECT DISTINCT c1.categoryid FROM product_category AS c1
LEFT JOIN products AS p ON c1.catalogid = p.catalogid
WHERE p.hide = 0)
Note how I have aliased the subquery product_category table as c1 instead of c - This means the subquery will only execute once as opposed to once for every row of the your main query.
SQL Fiddle
Note that there will no doubt be more efficiencies still to be found however I think this will suffice for your purposes.
In fact there is no need for a LEFT JOIN here I don't think ie...
SELECT c.categoryid FROM product_category AS c
WHERE c.categoryid NOT IN
(SELECT DISTINCT c1.categoryid FROM product_category AS c1
INNER JOIN products AS p ON c1.catalogid = p.catalogid
WHERE p.hide = 0)
..This will afford you some extra speed.
If there is only one categoryid per catalogid then you can get rid of the distinct:
Select
c.id, c.categoryname
From
category c
Where
Not Exists (
Select
'x'
From
products p
Inner Join
product_category pc
on pc.catalogid = p.catalogid
Where
pc.categoryid = c.id and
p.hide = 0
)
Edited - the test data in the fiddle seems wrong, I've corrected it. This should work now
http://sqlfiddle.com/#!6/56f5e/1/0

SQL SELECT and SUM from three

I have been cracking my head for hours on what I thought to be simple SQL SELECT command. I searched every where and read all questions related to mine. I tried an SQL Command Builder, and even read and applied complete series of SQL tutorials and manuals to try to build it from scratch understanding it (which is very important for me, regarding next commands I'll eventually have to build...).
But now I'm just stuck with the results I want, but on separates SELECT commands which I seem to be unable to get together !
Here is my case : 3 tables, first linked to the second with a common id, second linked to the third with another common id, but no common id from the first to the third. Let's say :
Table A : id, name
Table B : id, idA, amount
Table C : id, idB, amount
Several names in Table A. Several amounts in Table B. Several amounts in Table C. Result wanted : each A.id and A.name, with the corresponding SUM of B.amount, and with the corresponding SUM of C.amount. Let's say :
A.id
A.name
SUM(B.amount) WHERE B.idA = A.id
SUM(C.amount) WHERE C.idB = B.id for each B which B.idA = A.id
It's okay for "the first three columns", and "the first two columns and the fourth", both with a WHERE clause and/or a LEFT JOIN. But I can't achieve cumulating all fourth columns together without messing everything !
One could say "it's easy, just put an idA column in Table C" ! Should be easier, sure. But is it really necessary ? I don't think so, but I could be wrong ! So, I just please anyone (who I will give an eternal "SQL God" decoration) with SQL skills to answer laughing "That's so simple ! Just do that and you are gone ! Stupid little newbies..." ;)
Running VB 2010 and MS SQL Server
Thanks for reading !
Try this:
SELECT A.Id, A.Name, ISNULL(SUM(B.amount), 0) as bSum, ISNULL(SUM(C2.Amount), 0) as cSum
FROM A
LEFT OUTER JOIN B ON A.Id = B.idA
LEFT OUTER JOIN (SELECT C.idB, SUM(C.AMOUNT) AS Amount FROM C GROUP BY C.idB) AS C2 ON C2.idB = B.Id
GROUP BY A.Id, A.Name
Try this:
SELECT
a.id,
a.name,
sum(x.amount) as amountb,
sum(x.amountc) as amountc
from a
left join (
select
b.id,
b.ida,
b.amount,
SUM(c.amount) as amountc
from b
left join c
on b.id = c.idb
group by
b.id,
b.amount,
b.ida
) x
on a.id = x.ida
group by
a.id,
a.name
This should give you the result set you're looking for. It sums all C.Amount's for each B.id, then adds it all together into a single result set. I tested it with a bit of sample data in MSSQL, and it works as expected.
Select a.id, a.name, sum(b.amount), sum(c.amount)
from a inner join b on a.id = b.idA
inner join c on b.id = c.idB
group by a.id, a.name
You need to add them separately:
select a.id, a.name, (coalesce(b.amount, 0.0) + coalesce(c.amount, 0.0))
from a left outer join
(select b.ida, sum(amount) as amount
from b
group by b.ida
) b
on a.id = b.ida left outer join
(select b.ida, sum(amount) as amount
from c join
b
on c.idb = b.id
group by b.ida
) c
on a.id = c.ida
The outer joins are to take into account when b and c records don't both exist for a given id.