SELECT statement to exclude results through join table relationship - sql

Given three tables like this:
fruit: taste: fruit_taste:
id name id name id fruit_id taste_id
-- ---- -- ---- -- -------- --------
1 apple 1 sweet 1 1 1
2 lime 2 tart 2 1 2
3 banana 3 2 2
4 lemon 4 4 2
How can I SELECT only the fruit which do have not been identified by the join table as having a sweet taste? My desired results should also include 'banana', since there is not a fruit_taste identifying it as sweet.
I tried (along with a ton of other things):
SELECT *
FROM fruit
LEFT OUTER JOIN fruit_taste
ON fruit_taste.fruit_id = fruit.id
WHERE (fruit_taste.taste_id != 1 OR fruit_taste.id IS null)
which almost works, but still gives me 'apple' in my result, since my apple is a Granny Smith, and is both sweet and tart. How can I exclude 'apple' in my query? Is my only option a subquery with NOT IN?

select *
from fruit
where id not in
(select ft.fruit_id
from fruit_taste ft
inner join taste t
on ft.taste_id = t.id
where t.name = 'sweet')

Aside from using NOT IN, you can also use LEFT JOIN. To filter out fruits that have no sweet taste, the total number of sweet for every fruit should be 0.
SELECT a.ID, a.Name
FROM fruit a
LEFT JOIN fruit_taste b
ON a.id = b.fruit_id
LEFT JOIN taste c
ON b.taste_id = c.id
GROUP BY a.ID, a.Name
HAVING SUM(IFNULL(c.name = 'sweet', 0)) = 0
SQLFiddle Demo

You can use a LEFT JOIN and avoid the subquery if you add the excluded taste to the JOIN filter and then exclude matching rows. This should generally be better for performance than using NOT IN on a subquery, but YMMV.
SELECT *
FROM fruit
LEFT JOIN fruit_taste
ON fruit_taste.fruit_id = fruit.id
AND fruit_taste.taste_id = 1
WHERE fruit_taste.id IS NULL

Related

SQL/Presto: right join bigger than original table due to NULL

I need to right join 2 tables with 3 conditions but the resulting table is bigger than left or right table.
left_table a is like the following:
capacity value group_id level_id tags
100 3 a ab
120 5 a afb lala
122 4 b afg hhh
122 6 c adfg
right table b is like the following: bigger than left table
user group_id level_id tags
adsf a ab
af a abf df
sf a afb lala
dsf b afg hhh
sdf c adfg
I want to append the value and capacity value to the right table b. I have used the following query but the resulting table is larger than the right table. I noticed that it is due to the NULL in tags in both the right and left tables, but i am wondering how to resolve this issue.
select a.capacity, a.value, b.*
from a
right join b
on a.group_id = b._group_id
and a.level_id = b.level_id
and a.tags = b.tags
I noticed that it is due to the NULL in tags in both the right and left tables
No, this is not the cause of duplicates. In fact NULL values fail the comparison, so you will not get a match at all if either value is NULL. That is, the row in b will be returned with NULL values for the columns from a.
If you want NULL values to match as being equal, then you need a NULL-safe comparison -- and Presto supports the SQL Standard is not distinct from. I also strongly prefer left join over right join:
select a.capacity, a.value, b.*
from b left join
a
on a.group_id = b._group_id and
a.level_id = b.level_id and
a.tags is not distinct from b.tags;
If you are getting duplicates, it is because you have duplicates in a. You can check for this using:
select group_id, level_id, tags, count(*)
from a
group by group_id, level_id, tags
having count(*) >= 2;

Is it possible to replace content from multiple foreign keys when doing a query?

I have the following tables:
TABLE PLAYER
id | name
1 | A
2 | B
3 | C
4 | D
TABLE PAIRINGS
id | player_a | player_b
1 | 3 |1
2 | 2 |4
Both columns in table Pairings are foreign keys to column id of table player.
My problem is, I would like to avoid making a query from code every time I want to know which is my player's name (like, Select name From Player Where Id = pairings.player_a). I have thought about adding Name as an extra columnd to Pairings table, but that would denormalize it.
Instead, it would be nice if I could get the names in just one query, like:
RESULT
player_a | player_b
C | A
B | D
Is it this possible? Thanks so much in advance.
You may join the PAIRINGS table to the PLAYER table, twice:
SELECT
p1.name AS player_a,
p2.name AS player_b
FROM PAIRINGS pr
INNER JOIN PLAYER p1
ON pr.player_a = p1.id
INNER JOIN PLAYER p2
ON pr.player_b = p2.id;
Demo
Don't do it! One of the points of using a relational database is that data is stored in only one place. That is a big convenience. Of course, there are exceptions, but these exceptions should have firm reasons.
In this case, just define a view:
CREATE VIEW vw_pairings AS
SELECT p.*, pa.name AS player_a_name,
pb.name AS player_b_name
FROM PAIRINGS p JOIN
PLAYER pa
ON p.player_a = pa.id JOIN
PLAYER pb
ON p.player_b = pb.id;
When you query from the view, you will see the names, along with all the other information in the PAIRINGS table.
Hope can help you
Select * Into #PLAYER From (
Select 1 [ID], 'A' [Name] Union All
Select 2 [ID], 'B' [Name] Union All
Select 3 [ID], 'C' [Name] Union All
Select 4 [ID], 'D' [Name]
) A
Select * Into #PAIRINGS From (
Select 1 [ID], 3 [PLAYER_A], 1 [PLAYER_B] Union All
Select 2 [ID], 2 [PLAYER_A], 4 [PLAYER_B]
) A
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
You can create view, for avoid making query
Example
Create View vwPAIRINGS As
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
After that, just select usual
Select * from vwPAIRINGS

In a left join, select row with value A, if not select row with value B

This must be simple, but I think I'm lost. I have a table A:
name id
Tom 1
Barbara 2
Gregory 3
...and table B:
id nickname preferred
1 Spiderman 0
1 Batman 1
2 Powerpuff 0
3 Donald Duck 0
3 Hulk 1
How do I query the table to get a nickname when it is preferred (1), or any other nickname if preferred is not available.
So the result for Tom would be "Batman", while the result for Barbara would be "Powerpuff".
Just an immediate solution:
select a.id,
b.nickname
from a
join b on a.id = b.id and b.prefered = 1
union all
select a.id,
b.nickname
from a
join b on a.id = b.id and b.prefered = 0
where a.id not in(
select a.id
from a
join b on a.id = b.id and b.prefered = 1
)
Fiddle http://sqlfiddle.com/#!7/0b7db/1
Try below Query:
Which 1. selects row with value A, otherwise, 2. select row with value B
using LEFT JOIN,
SELECT A.name, B.nickname
FROM A
LEFT JOIN
(
SELECT MAX(preferred) AS preferred, id
FROM B
GROUP BY id
)AS B1
ON A.id = B1.id
LEFT JOIN B ON B.preferred = B1.preferred AND B.id = B1.id
If SQLite supported analytic functions then that would provide a fairly clean and convenient solution. No such luck, though. It does simplify the problem that you want either all the preferred nicknames for a given person (of which there will be at most one) or all the non-preferred ones. It is then fairly straightforward to use an inline view to distinguish between those cases and apply a suitable filter:
SELECT p.name, pn.nickname
FROM
person p
JOIN (
SELECT id, MAX(preferred) AS preferred
FROM person_nickname
GROUP BY id
) flag
ON p.id = flag.id
JOIN person_nickname pn
ON pn.id = flag.id AND pn.preferred = flag.preferred

SQL joining two tables with common row

I have 2 tables in sybase
Account_table
Id account_code
1 A
2 B
3 C
Associate_table
id account_code
1 A
1 B
1 C
2 A
2 B
3 A
3 C
I have this sql query
SELECT * FROM account_table account, associate_table assoc
WHERE account.account_code = assoc.account_code
This query will return 7 rows. What I want is to return the rows from associate_table that is only common to the 3 accounts like this:
account id account_code Assoc Id
1 A 1
2 B 1
3 C 1
Can anyone help what kind of join should I do?
SELECT b.id account_id,a.code account_code,a.id assoc_id
FROM associate a,
account b
WHERE a.code = b.code
AND a.id IN (SELECT a.id
FROM associate a,
account b
WHERE a.code = b.code
GROUP BY a.id
HAVING Count(*) = (SELECT Count(*)
FROM account));
NOTE: this query works only if you have unique values in Id and account_code columns in account table. And also, your associate_table should contain unique combination of (id, account,code). i.e., associate table should not contain (1,A) or any pair twice.
Try this
SELECT AC.ID,AC.account_code,ASS.ID
FROM account_table AC INNER JOIN associate_table AS ASS ON AC.account_code = ASS.account_code
OK so far answer is accepted I'll post simpler one:
SELECT *
FROM account_table AS account,
associate_table AS assoc
WHERE account.account_code = assoc.account_code
HAVING (
SELECT
COUNT(*)
FROM associate_table assoc_2
WHERE assoc_2.id = assoc.id
) = 3
here 3 is the number of codes account table has, if it's gonna be dynamic (changing over time),
you can use (SELECT COUNT(*) FROM account_table) instead of exact number. Also I'm sure it will be cached by database engine, so requires less resources

Left Outer Join with one result per match and "priority" of match set by a different field in match SQL Server 2005

I am trying to get a single result (PHONE) per match (CONTACT_ID) in a Left Outer Join. I imagine that there is a way to accomplish this with the preference (or order) being set by another column/field- the phone type (TYPE), but I haven't been able to figure it out. Below is a list of facts to help better explain what I am trying to accomplish and then following is an example Table A and B with the desired result. I've looked at min() and group by, but I don't know how to make those work here. As a side note, after this is working, I will be joining it to more tables to the left of it in a simpler fashion.
The student can have an unlimited number of CONTACT_ID.
A contact does not always have all phone types.
The preferred order of phone types (TYPE) is C,H,W (which, fortunately, happens to be alphabetical)
ignore match and go to the next in priority if PHONE is null
TableA:
STUDENT_ID CONTACT_ID
---------- ----------
X 1
X 2
Y 3
Y 4
TableB:
CONTACT_ID TYPE PHONE
---------- ---- -----
1 H 21
1 C
1 W 44
2 H 78
2 C 92
2 W 11
Desired Result:
STUDENT_ID CONTACT_ID TYPE PHONE
---------- ---------- ---- -----
X 1 H 21
X 2 C 92
Y 3
Y 4
Here is the query that I have that will make a join with all phone matches (minus all of my crazy attempts at getting what I want).
SELECT *
FROM Table TableA T1
LEFT OUTER JOIN TableB T2 ON T1.CONTACT_ID = T2.CONTACT_ID
All help greatly appreciated!
Edited code from Stefan Onofrei's solution:
(results in some duplicate entries)
SELECT
T1.STUDENT_ID,
T1.CONTACT_ID,
T2.PHONE_TYPE,
T3.PHONE
FROM REG_STU_CONTACT T1
INNER JOIN
(SELECT MIN(PHONE_TYPE) AS PHONE_TYPE, CONTACT_ID
FROM REG_CONTACT_PHONE
WHERE PHONE IS NOT NULL
GROUP BY CONTACT_ID) T2 ON T1.CONTACT_ID = T2.CONTACT_ID
INNER JOIN REG_CONTACT_PHONE T3 ON T2.CONTACT_ID = T3.CONTACT_ID AND T2.PHONE_TYPE = T3.PHONE_TYPE
ORDER BY T1.STUDENT_ID
Select A.STUDENT_ID A.CONTACT_ID B.TYPE c.PHONE
from TableA A
inner join
(select MIN(type ) as type, Contact_ID
from Tableb
where phone is not null
group by contactid) B
on A.contactid = b.contactid
inner join Tableb C
on B.contactid = c.conatctid and b.type = c.type