Query three tables with inner join - sql

I'm working on a sql database with three tables.
PageIds
Links
Ranks
I want to query the database with a page name and find all links for the given page name, furthermore I want to get Links sorted by a Score.
The PageName used for the query either exists as PageA or as PageB, and if PageA then I want PageBs PageName and PageBs PageScore - and vice versa.
I know I have to use inner joins, but how should I handle 'or' in inner join and how do get PageId in PageIds, to query other table and then PageName in PageIds.
This what I have tried, but it take quite some time and then I will not get the opposite PageName
select * from PageIds
inner join Links
on Links.PageAId = PageIds.PageId or Links.PageBId = PageIds.PageId
inner join Ranks
on Ranks.PageId = Links.PageBId
where PageIds.PageName = #searchternm
PageIds
Get PageId for PageName matching #searchterm
+--------+----------+
| PageId | PageName |
+--------+----------+
| 1234 | NameA |
| 4321 | NameB |
| 3321 | NameC |
+--------+----------+
Links
if previously found PageId = PageAId,
then get PageBId and PageName for PageBId,
else get PageAId and PageName for PageAId
+---------+---------+
| PageAId | PageBId |
+---------+---------+
| 1234 | 3321 |
| 4321 | 3321 |
| 1234 | 4321 |
+---------+---------+
Ranks
if previously found PageId = PageAId,
then get score for PageBId,
else get score for PageAId
+--------+-----------+
| PageId | PageScore |
+--------+-----------+
| 1234 | 1 |
| 3321 | 4 |
| 4321 | 2 |
+--------+-----------+
If input is NameB then the output should be:
PageName, Score
NodeC, 4
NodeA, 1
Thanks for your help.

If I understand your requirements correctly you'll want to join the PageIDs table twice, once as PageAIDs and once as PageBIDs.
Then have one query pick up pageA details where pageB matches, then union it with a reversed query for pageB details where pageA matches.
SELECT PageA.PageName, RanksA.Score FROM Links
INNER JOIN PageIDs [PageA] on Links.PageAID = PageA.PageID
INNER JOIN PageIDs [PageB] on Links.PageBID = PageB.PageID
INNER JOIN Ranks [RanksA] on Links.PageAID = RanksA.PageID
WHERE PageB.PageName = #PageName
UNION
SELECT PageB.PageName, RanksB.Score FROM Links
INNER JOIN PageIDs [PageA] on Links.PageAID = PageA.PageID
INNER JOIN PageIDs [PageB] on Links.PageBID = PageB.PageID
INNER JOIN Ranks [RanksB] on Links.PageBID = RanksB.PageID
WHERE PageA.PageName = #PageName

Related

How to join multiple columns in one table to another lookup table?

I am unable to figure out how to join a couple of tables together when multiple columns in one table refer to another table.
For example, I have a "document_statuses" table:
document_statuses table:
+-----------+-------------+
| status_id | status_name |
+-----------+-------------+
| 1 | RECEIVED |
| 2 | MISSING |
| 3 | NOT_NEEDED |
+-----------+-------------+
Now in another table, I am tracking the status of multiple documents:
filings table:
+-----------+-------------+----------------+----------------+----------------+
| filing_id | filing_name | doc1_status_id | doc2_status_id | doc3_status_id |
+-----------+-------------+----------------+----------------+----------------+
| 1 | John | 1 | 3 | 2 |
| 2 | Mikaela | 2 | 3 | 2 |
| 3 | Sam | 1 | 2 | 1 |
+-----------+-------------+----------------+----------------+----------------+
How would I write a query that pulls the status_name in for each column and produce the following result:
+-------------+-------------+-------------+------------+
| Filing Name | Doc1 Status | Doc2 Status | Doc3Status |
+-------------+-------------+-------------+------------+
| John | RECEIVED | NOT_NEEDED | MISSING |
| Mikaela | MISSING | NOT_NEEDED | MISSING |
| Sam | RECEIVED | MISSING | RECEIVED |
+-------------+-------------+-------------+------------+
I'm aware of how to do this when looking up a single field from document_statuses per row, but not multiple. If I only had one column in documents that referred to document_statuses, I'd do a simple JOIN:
SELECT filing_name, status_name
FROM documents d
LEFT JOIN document_statuses ds ON d.doc1_status = ds.status_id
But how do I do that when I need more than one?
You will need to join the statuses table multiple times and then alias the columns in the select clause to be formatted to be what you want. Please note that you have to alias the tables in the join clause so that you can reference the columns in the select clause of the statement.
SELECT filing_name
, ds1.status_name AS 'Doc1 Status'
, ds2.status_name AS 'Doc2 Status'
, ds3.status_name AS 'Doc3 Status'
FROM documents d
LEFT JOIN document_statuses ds1 ON d.doc1_status = ds1.status_id
LEFT JOIN document_statuses ds2 ON d.doc2_status = ds2.status_id
LEFT JOIN document_statuses ds3 ON d.doc3_status = ds3.status_id
You'll need to do three times joins with same table using different aliases for table.
SELECT filing_name, ds.status_name, ds1.status_name,
ds2.status_name FROM documents d
LEFT JOIN document_statuses ds ON d.doc1_status
LEFT JOIN document_statuses ds1 ON d.doc1_status
LEFT JOIN document_statuses ds2 ON d.doc1_status
You can do this:
select t1.filing_name, t2.status_name as doc1_status, t3.status_name as doc2_status, t4.status_name as doc3_status
from filings_table t1
inner join statuses_table t2 on t1.doc1_status_id = t2.status_id
inner join statuses_table t3 on t1.doc2_status_id = t3.status_id
inner join statuses_table t4 on t1.doc3_status_id = t4.status_id

Postgres complicated full outer join keeping nulls from the "on" column

I have written a PostgresSQL query that is relatively performant at scale and gives me the dataset I want back, but I am wondering if it is the simplest/best way to write the query. It seems like there should be a simpler join operation that satisfies the conditions I need.
EDIT: I do need this to be performant on large tables. In the example given below, pets is 150 million rows, food is roughly 100k rows. My solution at the bottom clocks in at about 0.6ms. Both tables have an index on id and user_id. The food table also includes an index on pet_id.
I have two tables that are related in my system that have one guaranteed shared attribute - the user_id. Here is an example that in essence shows my problem:
Pets
+------+-------+---------+
| id | type | user_id |
+------+-------+---------+
| 1234 | dog | 1 |
| 1235 | cat | 1 |
| 1236 | gecko | 1 |
+------+-------+---------+
Food
+------+-----------+---------+--------+
| id | name | user_id | pet_id |
+------+-----------+---------+--------+
| 4321 | hamburger | 1 | NULL |
| 4322 | dog food | 1 | 1234 |
| 4323 | cat food | 1 | 1235 |
+------+-----------+---------+--------+
Desired Results
+------+------+
| p.id | f.id |
+------+------+
| NULL | 4321 | --no pet, hamburger
| 1234 | 4322 | --dog, dog food
| 1235 | 4323 | --cat, cat food
| 1236 | NULL | --gecko, no food
+------+------+
Now with an example to refer to, I'll make sure it's clear what the result is. The result contains all rows from both sides that belong to my user_id (imagine that the table could contain thousands of other rows that don't belong to user_id 1). I want these result rows to include exactly ONE copy of each row matched to the other table.
An example of a full outer join that I tried to make this work:
SELECT p.id, f.id
FROM pets p FULL OUTER JOIN food f ON p.user_id = f.user_id
WHERE p.user_id = 1;
There's a bit of a problem in this query because
It excludes NULLs from the left side of the query. I need those.
Because the user_id is essentially the constant here, I end up with plenty of duplicates because it matches on user_id. Every row from the left gets matched to every row from the right. Not what I need. I need a one-to-one match.
I could fix #1 by including an OR in the WHERE filter:
SELECT p.id, f.id
FROM pets p FULL OUTER JOIN food f ON p.user_id = f.user_id
WHERE p.user_id = 1 OR f.user_id = 1;
For reasons I'm not completely sure of, it makes the query take a very long time. In our system, both tables have an index on user_id, so it isn't the lack of an index.
To solve my issue, I landed on the following query (really two combined):
SELECT p.id, f.id
FROM pets p LEFT JOIN food f
ON p.id = f.pet_id AND f.user_id = 1
WHERE p.user_id = 1
UNION
SELECT p.id, f.id FROM pets p RIGHT JOIN food f
ON p.id = f.pet_id
WHERE f.user_id = 1 AND p.id IS NULL;
So my question is this: Is there a simpler way to execute this as a single query?
SQL DEMO
SELECT p.id, f.id
FROM pets p
FULL OUTER JOIN food f
ON p.user_id = f.user_id
AND p.id = f.pet_id
AND p.user_id = 1;
OUTPUT
| id | id |
|--------|--------|
| 1234 | 4322 |
| 1235 | 4323 |
| 1236 | (null) |
| (null) | 4321 |
NOTE:
You should add a composite index on (user_id, pet_id) for both tables.
You're just overthinking this a bit. You want to join on P.ID = F.PET_ID:
SELECT P.ID, F.ID
FROM PETS P
FULL OUTER JOIN FOOD F ON P.ID = F.PET_ID
AND P.USER_ID = F.USER_ID
AND P.USER_ID = 1 --optional
ORDER BY P.ID

Joining 2 tables with sql

I am trying to join these 2 tables based on the left table and matching the row values. The right table does not necessarily have all ITEMNUMs that are in the left table. And not every VNDITNUM is going to have an ITEMNUM that is in the left hand table.
LEFT TABLE (559 rows)
--Filtered based on column IVCOGSIX = 137
SELECT
ITEMNMBR
FROM dbo.IV00101
WHERE IVCOGSIX = 137
+---------+
| ITEMNUM |
+---------+
| Item 1 |
| Item 2 |
| Item 3 |
| Item 4 |
+---------+
RIGHT TABLE (553 rows)
--Filtered based on column VENDORID = 90505EP
SELECT
ITEMNMBR,
VNDITNUM
FROM dbo.IV00103
WHERE VENDORID = '90505EP'
+---------+-------------+
| ITEMNUM | VNDITNUM |
+---------+-------------+
| Item 1 | VendorNum 1 |
| Item 2 | VendorNum 2 |
| Item 4 | VendorNum 4 |
| Item X | VendorNum 5 |
+---------+-------------+
The output that I am trying to get is this -
JOINED TABLE (559 rows)
+---------+-------------+
| ITEMNUM | VNDITNUM |
+---------+-------------+
| Item 1 | VendorNum 1 |
| Item 2 | VendorNum 2 |
| Item 3 | NULL |
| Item 4 | VendorNum 4 |
+---------+-------------+
However, my query keeps giving me only 548 rows, and I'm not necessarily sure what rows it is leaving out.
Here is my current query -
SELECT
cogs.ITEMNMBR,
vin.VNDITNUM
FROM METRO.dbo.IV00101 cogs
LEFT JOIN METRO.dbo.IV00103 vin
on vin.ITEMNMBR = cogs.ITEMNMBR
WHERE
cogs.IVCOGSIX = 137
AND vin.VENDORID = '90505EP'
How can I join these 2 tables to get the 559 rows of the left table and any matching items from the right table?
Your join predicate vin.VENDORID = '90505EP' has effectively turned your left join into an inner join.
Try this instead.
SELECT
cogs.ITEMNMBR,
vin.VNDITNUM
FROM METRO.dbo.IV00101 cogs
LEFT JOIN METRO.dbo.IV00103 vin
on vin.ITEMNMBR = cogs.ITEMNMBR
AND vin.VENDORID = '90505EP'
WHERE
cogs.IVCOGSIX = 137
To explain what happened, essentially SQL Server will join the tables together first, and then run the where clause on the results (it's a bit more complicated then that, but this is the general way it works). The WHERE clause as you originally ran it will remove anything that doesn't have vin.VENDORID = '90505EP', essentially stripping out the nulls from the result set. and returning the results of an inner join.
#SeanLange's answer works around this by shifting the filter on VENDORID into the join clause, essentially saying "filter dbo.IV00103 where VENDORID = '90505EP and then left outer join on that result"
I call (and have seen it called this before) as an Accidental Inner Join.
You need a Left Outer Join, so the Left table will output the full content:
SELECT
cogs.ITEMNMBR,
vin.VNDITNUM
FROM METRO.dbo.IV00101 cogs
LEFT OUTER JOIN METRO.dbo.IV00103 vin
on vin.ITEMNMBR = cogs.ITEMNMBR
WHERE
cogs.IVCOGSIX = 137
AND vin.VENDORID = '90505EP'

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

SQL LEFT JOIN help

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