SQL LEFT JOIN help - sql

My scenario: There are 3 tables for storing tv show information; season, episode and episode_translation.
My data: There are 3 seasons, with 3 episodes each one, but there is only translation for one episode.
My objetive: I want to get a list of all the seasons and episodes for a show. If there is a translation available in a specified language, show it, otherwise show null.
My attempt to get serie 1 information in language 1:
SELECT
season_number AS season,number AS episode,name
FROM
season NATURAL JOIN episode
NATURAL LEFT JOIN episode_trans
WHERE
id_serie=1 AND
id_lang=1
ORDER BY
season_number,number
result:
+--------+---------+--------------------------------+
| season | episode | name |
+--------+---------+--------------------------------+
| 3 | 3 | Episode translated into lang 1 |
+--------+---------+--------------------------------+
expected result
+-----------------+--------------------------------+
| season | episode| name |
+-----------------+--------------------------------+
| 1 | 1 | NULL |
| 1 | 2 | NULL |
| 1 | 3 | NULL |
| 2 | 1 | NULL |
| 2 | 2 | NULL |
| 2 | 3 | NULL |
| 3 | 1 | NULL |
| 3 | 2 | NULL |
| 3 | 3 | Episode translated into lang 1 |
+--------+--------+--------------------------------+
Full DB dump
http://pastebin.com/Y8yXNHrH

I tested the following on MySQL 4.1 - it returns your expected output:
SELECT s.season_number AS season,
e.number AS episode,
et.name
FROM SEASON s
JOIN EPISODE e ON e.id_season = s.id_season
LEFT JOIN EPISODE_TRANS et ON et.id_episode = e.id_episode
AND et.id_lang = 1
WHERE s.id_serie = 1
ORDER BY s.season_number, e.number
Generally, when you use ANSI-92 JOIN syntax you need to specify the join criteria in the ON clause. In MySQL, I know that not providing it for INNER JOINs results in a cross join -- a cartesian product.

LEFT JOIN episode_trans
ON episode_trans.id_episode = episode.id_episode
AND episode_trans.id_lang = 1
WHERE id_serie=1

You probably need to move the id_lang = 1 into the LEFT JOIN clause instead of the WHERE clause. Think of it this way... for all of those rows with no translation the LEFT JOIN gives you back NULLs for all of those translation columns. Then in the WHERE clause you are checking to see if that is equal to 1 - which of course evaluates to FALSE.
It would probably be easier if you included your code in the question next time instead of in a link.

Can you try using
LEFT OUTER JOIN
instead of
NATURAL LEFT JOIN

Related

SQL not returning values where null in another table

I'm trying to use the SQL join function to grab information from multiple tables.
My issue is I can't seem to get the desired result.
select a.DRINKER, sum(C.PRICE)
from DRINKERS a
left join ORDERS b on a.DRINKER = b.DRINKER
join SERVES c on b.PUB = c.PUB and d.DRINK = c.DRINK
group by a.DRINKER;
This gives the following results
----------------------
|DRINKER|sum(C.PRICE)|
----------------------
| BOB | 200.10 |
| NED | 172.50 |
| JOE | 270.90 |
| TIM | 80.10 |
----------------------
However I want this to be giving all of the people in a.DRINKER like such:
----------------------
|DRINKER|sum(C.PRICE)|
----------------------
| BOB | 200.10 |
| NED | 172.50 |
| JOE | 270.90 |
| TIM | 80.10 |
| PAT | null |
| ANN | null |
----------------------
Any guidance would be appreciated and if you could also explain the logic behind the changes that would be greatly appreciated as I wanna learn what I should be doing! Thanks in advance!
Even though you got a left join between DRINKERS and ORDERS, the join between ORDERS and SERVES, will filter out any nulls obtained in the first left join.
To fix this you could try by further left joining the tables
select a.DRINKER, sum(C.PRICE)
from DRINKERS a
left join ORDERS b on a.DRINKER = b.DRINKER
left join SERVES c on b.PUB = c.PUB and d.DRINK = c.DRINK
group by a.DRINKER;

SQL Query manipulation

I have three tables :
BookingNode , Booking AirTrip
AirTrip :
+----+------------+
| ID | Name |
+----+------------+
| 0 | One way |
| 1 | Round trip |
| 2 | Circle |
| 3 | Other |
+----+------------+
When ever we make a booking we store the data as :
BookingNode table
+--------+-------------------+------------+----------------------+
| ID | CustomerGivenName | IPAddress | Email |
+--------+-------------------+------------+----------------------+
| 177022 | xfghfh | 2130706473 | mikehussey#gmail.com |
| 177021 | cfggjfj | 2130706473 | mikehussey#gmail.com |
+--------+-------------------+------------+----------------------+
Booking Table :
+--------+---------------+-----------+------------+------------+
| ID | BookingNodeID | AirTripID | AirLineId | Provider |
+--------+---------------+-----------+------------+------------+
| 181251 | 177020 | 1 | 978 | Jet |
| 181252 | 177021 | 0 | 982 | Go |
| 181253 | 177021 | 0 | 978 | Jet |
+--------+---------------+-----------+------------+------------+
If round trip flight is booked and ProviderID is same then a single entry is done in Booking Table with AirTripID value as 1.(Booking ID : 181251 and Provider Jet )
But if providers are different for both the legs then two entries are done in Booking Table with AirTripID for both entries are one(Booking ID : 181252 and 181253 Provider Go,Jet ).In this case BookingNodeID value being same.
Prob : I have to write a query to get different type of Bookings.(Oneway, RoundTrip,Circle).But when I apply join on AirTripID , it is giving me incorrect results.
How can I write my query to give correct results knowing that BookingNodeID is going to be the same for roundtrip (both entries in Booking Table)
Sample Output
+-------------+---------------+-------------------+------------+
| AirTripName | BookingNodeID | CustomerGivenName | IPAddress |
+-------------+---------------+-------------------+------------+
| TwoWay | 177020 | xfghfh | 2130706473 |
| TwoWay | 177021 | cfggjfj | 2130706473 |
+-------------+---------------+-------------------+------------+
Basically, this code might have an error due to my laziness syntom of data entry. But, the logic of the query is, if b.AirTripID is 0, add extra condition which group by Booking. if result return more than 1 row, is actually 2 way. so AirTripType will become 1, otherwise, remain the same as b.AirTripID. You may copy below on and try fix if theres any error. i believe the logic should work based on your expected result.
select
bd.ID,
bd.CustomerGivenName,
case b.AirTripID
when 1 then 1
when 2 then 2
when 3 then 3
when 0 then
case select BookingNodeID
from Booking
where Booking.BookingNodeID = bd.ID group by BookingNodeID having Count(BookingNodeID)
when 1 then 1
else 0 end as AirTripType,
bd.IPAddress
from BookingNode bd
inner join (select BookingNodeID ,AirTripID from Booking group by BookingNodeID ,AirTripID) as b on b.BookingNodeID = bd.ID
where id=177021
Try This
WITH CTE
AS
(
SELECT
SeqNo = ROW_NUMBER() OVER(PARTITION BY BN.ID ORDER BY B.ID),
B.BookingNodeID,
BN.CustomerGivenName,
BN.IPAddress,
AirTripId = A.ID,
AirTripNm = A.Name
FROM Booking B
INNER JOIN AirTrip A
ON A.ID = B.AirTripID
LEFT JOIN BookingNode BN
ON B.BookingNodeID = BN.id
)
SELECT
C1.SeqNo,
AirTripName = CASE WHEN C2.SeqNo IS NOT NULL
THEN 'Round trip'
ELSE C1.AirTripNm END,
C1.BookingNodeID,
C1.CustomerGivenName,
C1.IPAddress
FROM CTE C1
LEFT JOIN CTE C2
ON C1.BookingNodeID = C2.BookingNodeID
AND C2.SeqNo = 2
WHERE c1.SeqNo = 1
SQL Fiddle Link Here
Select distinct bk.bookingnodeid,cst.customername,ipaddress,
case when count(airtripid)over(partition by bookingnodeid order by bookingnodeid)=2 then 'RoundTrip' else name end As AirTripName
from booking bk
inner join airlinetrip at
on bk.airtripid=at.id
inner join customer cst
on cst.id=bk.bookingnodeid

How to display null if join condition is not met

I have a table tbl_expenseLimit which I use to get the Limit for a given combination of variables.
tbl_expenselimit
+------+---------+----------+---------+-------+----------+
| SrNo | ExpType | Location | Expense | Limit | empgrade |
+------+---------+----------+---------+-------+----------+
| 1 | ERA | Metro | 1 | 250 | A |
| 2 | ERA | Metro | 2 | 500 | A |
| 3 | ERA | Metro | 3 | 5000 | A |
+------+---------+----------+---------+-------+----------+
If ExpType in my main table is ERA, Location is Metro, Expense is 1 and empgrade is 'A' then Limit would be 250.
If any of the condition does not match, for example: If ExpType is ERA, Expense is 1 ,empgrade is 'A' but Location is Non-Metro then Limit should be NULL . Similarly if all conditions match but empgrade is 'B' (which is not present in tbl_expenselimit table it should return NULL for Limit
select other.srno,VoucherId,other.ExpType, lmt.Limit
from tbl_voucherotherexpense other
left join tbl_TypesOfExpenses expensemst
on other.expense=expensemst.srno
left join tbl_expenseLimit lmt
on (other.Expense=lmt.Expense
and other.ExpType=lmt.ExpType
and (( lmt.location is not null
and other.location=lmt.location) or 1=1))
where voucherid='C0000004' and lmt.empgrade='a'
The join between tbl_voucherotherexpense and tbl_TypesOfExpenses will always find a match. So its just tbl_expenseLimit that needs to be focused on.
The query that I have written is wrong. It does not achieve what I want. Any help is appreciated.
EDIT:
Suppose in my main table ExpType is ERA, Location is Metro, Expense is 2 but empgrade is B then output should be :
+------+-----------+---------+-------+
| srno | VoucherId | ExpType | Limit |
+------+-----------+---------+-------+
| 4 | C0000004 | ERA | NULL |
+------+-----------+---------+-------+
or if
in my main table ExpType is ERA, Location is Metro, empgrade is A but Expense is 12 then output should still be the same as above.
All the conditions that refer to the table you're left joining i.e. lmt should go in the on, which, unlike the where clause, won't remove rows where the condition isn't matched:
select other.srno,VoucherId,other.ExpType, lmt.Limit
from tbl_voucherotherexpense other left join tbl_TypesOfExpenses expensemst on other.expense=expensemst.srno
left join tbl_expenseLimit lmt
on other.Expense=lmt.Expense
and other.ExpType=lmt.ExpType
and other.location=lmt.location
and lmt.empgrade='a'
where voucherid='C0000004'

Use JOIN on multiple columns multiple times

I am trying to figure out the best way to use a JOIN in MSSQL in order to do the following:
I have two tables. One table contains technician IDs and an example of one data set would be as follows:
+--------+---------+---------+---------+---------+
| tagid | techBid | techPid | techFid | techMid |
+--------+---------+---------+---------+---------+
| 1-1001 | 12 | 0 | 11 | 6 |
+--------+---------+---------+---------+---------+
I have another table that stores the names of these technicians:
+------+-----------+
| TTID | SHORTNAME |
+------+-----------+
| 11 | Steven |
| 12 | Mark |
| 6 | Pierce |
+------+-----------+
If the ID of a technician in the first table is 0, there is no technician of that type for that row (types are either B, P, F, or M).
I am trying to come up with a query that will give me a result that contains all of the data from table 1 along with the shortnames from table 2 IF there is a matching ID, so the result would look something like the following:
+--------+---------+---------+---------+---------+----------------+----------------+----------------+----------------+
| tagid | techBid | techPid | techFid | techMid | techBShortName | techPShortName | techFShortName | techMShortName |
+--------+---------+---------+---------+---------+----------------+----------------+----------------+----------------+
| 1-1001 | 12 | 0 | 11 | 6 | Mark | NULL | Steven | Pierce |
+--------+---------+---------+---------+---------+----------------+----------------+----------------+----------------+
I am trying to use a JOIN to do this, but I cannot figure out how to join on multiple columns multiple times to where it would look something like
Select table1.tagid, table1.techBid, table1.techPid, table1.techFid, table1.techMid, table2.shortname
FROM table1
INNER JOIN table2 on //Dont know what to put here
You need to use left joins like this:
Select table1.tagid, table1.techBid, table1.techPid, table1.techFid, table1.techMid,
t2b.shortname, t2p.shortname, t2f.shortname, t2m.shortname,
FROM table1
LEFT JOIN table2 t2b on table1.techBid = t2b.ttid
LEFT JOIN table2 t2p on table1.techPid = t2p.ttid
LEFT JOIN table2 t2f on table1.techFid = t2f.ttid
LEFT JOIN table2 t2m on table1.techMid = t2m.ttid
you just do mutiple left join
select tech.techPid, techPname.SHORTNAME
, tech.techFid, techFname.SHORTNAME
from tech
left join techName as techPname
on tech.techPid = techPname.TTID
left join techName as techFname
on tech.techFid = techFname.TTID

removing rows from a SELECT based on columns in a different table

I'm pretty much looking for a way to filter out rows from a SELECT of one table based on certain values in rows of another table.
I'm experimenting with the example structure below. I've got a table of blog post content (one row per blog post), and another table of metadata about the posts (one row per key-value pair; each row with a column associating it with a blog post; many rows per blog post). I want to pull a row of posts only if there exists no rows in metadata where metadata.pid=posts.pid AND metadata.k='optout'. That is, for the example structure below, I just want to get back the posts.id=1 row.
(Based on what I've tried) JOINs don't end up removing the posts which have some metadata where metadata.k='optout', because the other row of metadata for that pid means it makes it into the results.
mysql> select * from posts;
+-----+-------+--------------+
| pid | title | content |
+-----+-------+--------------+
| 1 | Foo | Some content |
| 2 | Bar | More content |
| 3 | Baz | Something |
+-----+-------+--------------+
3 rows in set (0.00 sec)
mysql> select * from metadata;
+------+-----+--------+-----------+
| mdid | pid | k | v |
+------+-----+--------+-----------+
| 1 | 1 | date | yesterday |
| 2 | 1 | thumb | img.jpg |
| 3 | 2 | date | today |
| 4 | 2 | optout | true |
| 5 | 3 | date | tomorrow |
| 6 | 3 | optout | true |
+------+-----+--------+-----------+
6 rows in set (0.00 sec)
A subquery can give me the inverse of what I want:
mysql> select posts.* from posts where pid = any (select pid from metadata where k = 'optout');
+-----+-------+--------------+
| pid | title | content |
+-----+-------+--------------+
| 2 | Bar | More content |
| 3 | Baz | Something |
+-----+-------+--------------+
2 rows in set (0.00 sec)
...but using pid != any (...) gives me all 3 of the rows in posts, cause every single pid has a metadata row where k!='optout'.
Sounds like you want to do a LEFT JOIN and then check for results in which the value of the joined table is NULL, indicating that no such joined record exists.
For example:
SELECT * FROM posts
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout')
WHERE metadata.mdid IS NULL;
This will select any row from the table posts for which no corresponding metadata row exists with a value of k = 'optout'.
edit: Worth noting that this is a key property of a left join and would not work with a regular join; a left join will always return values from the first table, even if no matching values exist in the joined table(s), allowing you to perform selections based on the absence of those rows.
edit 2: Let's clarify what's happening here with respect to the LEFT JOIN versus the JOIN (which I refer to as an INNER JOIN for clarity but is interchangable in MySQL).
Suppose you run either of these two queries:
SELECT posts.*, metadata.mdid, metadata.k, metadata.v
FROM posts
INNER JOIN metadata ON posts.pid = metadata.pid;
or
SELECT posts.*, metadata.mdid, metadata.k, metadata.v
FROM posts
LEFT JOIN metadata ON posts.pid = metadata.pid;
Both queries produce the following result set:
+-----+-------+--------------+------+-------+-----------+
| pid | title | content | mdid | k | v |
+-----+-------+--------------+------+-------+-----------+
| 1 | Foo | Some content | 1 | date | yesterday |
| 1 | Foo | Some content | 2 | thumb | img.jpg |
+-----+-------+--------------+------+-------+-----------+
Now, let's suppose we modify the query to add the extra criteria for "optout" that was mentioned. First, the INNER JOIN:
SELECT posts.*, metadata.mdid, metadata.k, metadata.v
FROM posts
INNER JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout");
As expected, this returns no results:
Empty set (0.00 sec)
Now, changing that to a LEFT JOIN:
SELECT posts.*, metadata.mdid, metadata.k, metadata.v
FROM posts
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout");
This DOES produce a result set:
+-----+-------+--------------+------+------+------+
| pid | title | content | mdid | k | v |
+-----+-------+--------------+------+------+------+
| 1 | Foo | Some content | NULL | NULL | NULL |
+-----+-------+--------------+------+------+------+
The difference between an INNER JOIN and a LEFT JOIN is that an INNER JOIN will only return a result if rows from BOTH joined tables match. In a LEFT JOIN, matching rows from the first table will ALWAYS be returned, regardless of whether anything is found to join to. In a lot of cases it doesn't matter which one you use, but it's important to choose the right one so as not to get unexpected results down the line.
So in this case, the suggested query of:
SELECT posts.*, metadata.mdid, metadata.k, metadata.v
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout')
WHERE metadata.mdid IS NULL;
Will return the same result set as above:
+-----+-------+--------------+------+------+------+
| pid | title | content | mdid | k | v |
+-----+-------+--------------+------+------+------+
| 1 | Foo | Some content | NULL | NULL | NULL |
+-----+-------+--------------+------+------+------+
Hopefully that clears it up! Joins are a great thing to learn about, having a full understanding of when to use which one is a very good thing.
You can try something like
select p.*
from posts p
where NOT EXISTS (
select pid
from metadata
where k = 'optout'
and pid = p.pid
)