0

I have my current sql statement below working exactly as I would like so far. My question here is do i need some sort of subquery on the LEFT JOIN or how would I go about retrieving any records from the invUpdate table that don't exist in the inventory table as well. Right now i get all of the inventory records (which i want) and if a matching record (based on vin) exists in invUpdate, then the record information in table invUpdate overrides the record information in inventory (on the web page being displayed). I need to be able to include any additional records from invUpdate as well.

SELECT IF(u.vin IS NULL,i.vin,u.vin) AS vin, IF(u.vin IS NULL,i.miles,u.miles) AS miles
, IF(u.vin IS NULL,i.year,u.year) AS year, IF(u.vin IS NULL,i.make,u.make) AS make
, IF(u.vin IS NULL,i.model,u.model) AS model, IF(u.vin IS NULL,i.trim,u.trim) AS trim
, IF(u.vin IS NULL,i.category,u.category) AS category, IF(u.vin IS NULL,i.price,u.price) AS price
, IF(u.vin IS NULL,i.msrp,u.msrp) AS msrp, IF(u.vin IS NULL,i.color,u.color) AS color
, IF(u.vin IS NULL,i.engine,u.engine) AS engine, IF(u.vin IS NULL,i.transmission,u.transmission) AS transmission
,  i.images, i.stock, p.imgURL 
FROM inventory i 
LEFT JOIN invUpdate u ON i.vin=u.vin 
LEFT JOIN photos p ON i.vin=p.vin AND position=0
WHERE i.vin != ''

My initial thought is to add some sort of NOT EXISTS statement such as

WHERE NOT EXISTS (SELECT * 
                  FROM invUpdate u
                  WHERE u.vin = i.vin)

My thought is that this should somehow be a part of the 1st LEFT JOIN ON statement

FROM inventory i LEFT JOIN invUpdate u ON i.vin=u.vin

But I'm having trouble on this subquery. Any thoughts, pointers or help would be greatly appreciated.

UPDATE: After sleeping on this and reading over everyones notes along with some other stack overflow examples I came up with something simple for selecting data from both the inventory and invUpdate table.

(SELECT ...
i.stock, 
IF(p.images IS NULL,i.images,p.images)
...
FROM inventory i 
LEFT JOIN invUpdate u ON i.vin=u.vin)
UNION
(SELECT ...
i.stock, 
IF(p.images IS NULL,i.images,p.images)
...
FROM inventory i 
RIGHT JOIN invUpdate u ON i.vin=u.vin)

The only problem i have with this now is there is a 3rd table called photos. If there is a matching record from photos I need to use that record. They will match based on vin. I tried to union that as well but because it has a different number of fields I could not get that to work. Any ideas? Note I am using the IF statement in the union queries so that if there is a record in the photos table images column that it will be used before anything in the inventory table images column.

animuson
  • 53,861
  • 28
  • 137
  • 147
Macrunning
  • 39
  • 5
  • If you want invUpdate records without corresponding inventory records, your "cleanest" solution is to UNION your current query with a second one based around `FROM invUpdate AS iu LEFT JOIN inventory AS i ON iu.vin = i.vin WHERE i.vin IS NULL` – Uueerdo Jan 18 '16 at 21:24
  • I guess this is where I'm having trouble with the query. – Macrunning Jan 18 '16 at 22:29
  • Sidenote/Style-note: I, and most developers I know, tend to stay away from `RIGHT JOIN` in general, and definitely from mixing `RIGHT` and `LEFT` joins, as it tends to make queries a little harder to follow. It is the SQL equivalent of "speaking like Yoda". – Uueerdo Jan 19 '16 at 21:51
  • Thanks Uueerdo. This actually does exactly what I need though. So if it is speaking like Yoda, then I'm ok with it for this project. Thanks for your help. – Macrunning Jan 19 '16 at 23:58

2 Answers2

0

Could you do a full join ? Then you would get records from both tables regardless of if there is a match.

I don't know mySQL but that's what I would do in SQL Server.

Jeff B
  • 535
  • 1
  • 6
  • 15
  • Does a full join not give me duplicate records though? (which id don't want) – Macrunning Jan 18 '16 at 21:29
  • A full join will take all of the records that match then add anything from the left that didn't match and add anything from the right that didn't match. Take a look at this: http://www.sql-join.com/ it may help. – Jeff B Jan 19 '16 at 13:28
  • There is no full join in mysql – Macrunning Jan 19 '16 at 19:53
0

"how would I go about retrieving any records from the invUpdate table that don't exist in the inventory table as well."

To recap, for tables you have:

INV (inventory)
UPD (invupdate)

Consider that any given "vin" may exist in...

1) just INV
2) both INV & UPD
3) just UPD

The query you posted pretty much covers 1 & 2.

Edit @Macrunning wrote:

> hello jgreve. 
> Yes I understand it covers 1 & 2.
> Yes I need 3 included on this.
> After the initial WHERE i.vin!='' statement,
> there is more based on user selection.

> These additional are concatenated using php.
> For example:
> if(isset($_GET['year'])){ $query_Recordset1.=" AND i.year='$myYear' "; }
>
> Therefor placing the union all after would impede on this.
> I thought there would be a cleaner solution by placing a
> subquery in the first LEFT JOIN statement.

Hmm... this is getting more complex. :-)

r.e. adding a subquery in the original question's first LEFT JOIN:

It seems like that will lead into a rats nest of complex logic on your WHERE clauses.

Consider this (u2 & u3 are my attempt to tie back to the 3 situations):

select i.vin  as i_vin
     , u2.vin as u2_vin
     , u3.vin as u3_vin
from INVENTORY i
left join INVUPDATE u2 on u2.vin = i.vin
left join INVUPDATE u3 on u3.vin = i.vin

    Source tables          : Result set
+-----------+-----------+  : +-------+--------+--------+
| INVENTORY | INVUPDATE |  : | i_vin | u2_vin | u3_vin |
+-----------+-----------+  : +-------+--------+--------+
|    100    |   200     |  : | 100   | null   | null   |
|    200    |   300     |  : | 200   | 200    | 200    |
+-----------+-----------+  : +-------+--------+--------+

Note that #300 never gets a chance to appear. I don't think you can do this by combining LEFT JOINs.

You might be able to modify your "LEFT JOIN" to read from a sub select instead of directly specifying the table, e.g.:

select i.vin  as i_vin
 , u2.vin as u2_vin
from INVENTORY i
left join (select usub.vin ....  from INVUPDATE something somthing) u

But without running some experiments it is hard for me to predict what that will yield for result sets (writing sub-selects is hard for me to do without building it up incrementally against an actual database).

I imagine you're going to have to limit #3 by all your PHP filters as well. This is what I suspect will get ugly.

If MySQL supported a "full outer join" I would suggest possibly doing something like this: (see "No full joins on MySQL" part of Full Outer Join in MySQL )

-- hypothetical example, doesn't actually run in MySQL
select i.vin as i_vin
     , u.vin as u_vin
from INVENTORY i
full outer join INVUPDATE u on u.vin = i.vin

    Source tables          : Result set
+-----------+-----------+  : +-------+-------+
| INVENTORY | INVUPDATE |  : | i_vin | u_vin |
+-----------+-----------+  : +-------+-------+
|    100    |   200     |  : | 100   | null  |
|    200    |   300     |  : | 200   | 200   |
+-----------+-----------+  : | null  | 300   |
                           : +-------+-------+

However you would still need to filter by YEAR, MODEL, and whatever else your PHP front end suppoerts.

And when you have columns involved from various table qualifiers (e.g. i.year vs. u.year), the logic for the WHERE clause seems ovelry complex (e.g. makes my head hurt).

So... what about the following approach?

The UNION approach looks more complex at first.

But I suspect it will simplify your filtering logic.

Because MySQL evaluates each part of the union independently, we don't shouldn't need to worry so much about table qualifiers.

Which means you should be able to write "generic" filters... Suppose we have the following PHP variables for filters:

$filter_YEAR = "\nAND year = '1995'"
$filter_YEAR = "\nAND model = 'XYZ'"

The goal is to generate SQL like this (start here for testing):

select '1) just INV' as record_source
     , vin
     , miles
     ...etc...
from FROM inventory i
where not exists( select 1 from invupdate u where u.vin=i.vin  )
AND year = '1995'
AND model = 'XYZ'
-- In this part the "where NOT EXISTS" makes this "1) just INV"
-- ===============================================================
-- I'm avoiding @Uueerdo's neat trick with left-join on INVUPDATE
-- and filtering nulls in this part's WHERE clause because I don't
-- want to generate a   $filter_YEAR_INV = "AND i.year = '1995'"
--  and another one for $filter_YEAR_UPD = "AND u.year = '1995'"
--  (note the different qualifiers).
-- ===============================================================
union all
select '2) both INV and UPD' as record_source
     , vin
     , miles
     ...etc...
from FROM invupdate u
-- the "where EXISTS" makes this "2) both INV and UPD"
where exists( select 1 from inventory i where u.vin=i.vin  )
AND year = '1995'
AND model = 'XYZ'
union all
select '3) just UPD' as record_source
     , vin
     , miles
from FROM invupdate u
-- the "where NOT EXISTS" makes this "3) just UPD".
where not exists( select 1 from inventory i where u.vin=i.vin  )
AND year = '1995'
AND model = 'XYZ'
order by record_source, vin

Since I don't have much (any) flight time in PHP I don't know if the following will actually compile, but I hope you get the idea.

// obligatory: see  "little bobby tables" https://xkcd.com/327/
// Sanitizing the input filters is beyond the scope of this answer.
$filter_YEAR = "";
if(isset($_GET['year'])){ $filter_YEAR ="\n AND year='$myYear' "; }
$filter_YMODEL = "";
if(isset($_GET['model'])){ $filter_MODEL ="\n AND year='$myModel' "; }
$filter_COLOR = "";
// At this point your $filter_WHATEVER variables either have an AND-clause
// that will work for all of the following.
// Or they are empty, in which case they wont change your resultant SQL.

$query_Recordset1 = "select '1) just INV' as record_source"
               .= "\n     , vin"
               .= "\n     , miles"
               ...etc...
               .= "\nfrom FROM inventory i"
               .= "\nwhere not exists( select 1 from invupdate u where u.vin=i.vin  )"
               .= $filter_YEAR$ . filter_YMODEL . $filter_COLOR;

// add SQL to get 2) both INV and UPD
$query_Recordset1 .= "union all"
               .=   "select '2) both INV and UPD' as record_source
               .= "\n     , vin
               .= "\n     , miles"
               ...etc...
               .= "\nfrom FROM invupdate u
               .= "\nwhere exists( select 1 from inventory i where u.vin=i.vin  )"
               .= $filter_YEAR$ . filter_YMODEL . $filter_COLOR;

// add SQL to get 3) just UPD
$query_Recordset1 .= .= "union all"
               .=   "select '3) just UPD' as record_source
               .= "\n     , vin
               .= "\n     , miles"
               ...etc...
               .= "\nfrom FROM invupdate u
               .= "\nwhere not exists( select 1 from inventory i where u.vin=i.vin  )"
               .= $filter_YEAR$ . filter_YMODEL . $filter_COLOR
               .= "order by record_source, vin"

Original answer

(Edited to fix problem in SQL; remains here for completeness, but not so useful in light of the additional "dynamic filtering" requirements based on the PHP front end.)

(question: I just thought of this, will you ever have 2+ UPD records for a a given VIN ? Solving that is a little different, you might want to take use just th emost recent update record )

For #3 you could include with a FULL OUTER JOIN, but that will be really messy... your IF() expressions will be a headache to code and maintain.

Seems cleaner to me to do as @UUeerdo suggested in their comment.

This example adds a "sourceTables" column to help you trace down which table(s) a given record came from.

SELECT IF(u.vin IS NULL
          , '1) Just INV'
          , '2) Both INV & UPD' )          AS sourceTables
     , IF(u.vin IS NULL, i.miles, u.miles) AS miles
     , ...etc...
FROM inventory i 
LEFT JOIN invUpdate u ON i.vin=u.vin 
LEFT JOIN photos p ON i.vin=p.vin AND position=0
WHERE i.vin != ''
union all
SELECT '3) Just UPD' AS sourceTables
     , u.miles       AS miles
     , ...etc...
from invUpdate u
-- edit: fixed "where" before LEFT JOIN error.
-- Also modified as per @Uueerdo's insightful comment.
LEFT JOIN inventory AS i ON u.vin = i.vin
LEFT JOIN photos p ON u.vin=p.vin AND position=0
WHERE u.vin != ''
  AND i.vin IS NULL – Uueerdo 3 hours ago

Trivia point: note in the last LEFT JOIN we're joining "photos" on u.vin, not i.vin as above. Same for the WHERE u.vin != '' (I'm assuming you have a good reason for blocking empty vin #'s).

jgreve
  • 1,225
  • 12
  • 17
  • hello jgreve. Yes I understand it covers 1 & 2. Yes I need 3 included on this. After the initial WHERE i.vin!='' statement, there is more based on user selection. These additional are concatenated using php. For example: if(isset($_GET['year'])){ $query_Recordset1.=" AND i.year='$myYear' "; } Therefor placing the union all after would impede on this. I thought there would be a cleaner solution by placing a subquery in the first LEFT JOIN statement. – Macrunning Jan 18 '16 at 22:40
  • You cannot have a WHERE before a LEFT JOIN (the second query in the union); you're better off doing `... u LEFT JOIN inventory AS i ON u.vin = i.vin LEFT JOIN photos ... WHERE i.vin IS NULL` – Uueerdo Jan 18 '16 at 23:13
  • @Uueerdo - thanks, I swear I proof read these things (argh! :-) ). Also r.e. WHERE i.vin IS NULL, cool - I haven't used that before but it makes good sense. – jgreve Jan 19 '16 at 01:01
  • I tried the sql statement you recommend but it does not work. I get an sql error in my statement message. Is it not possible to place a subquery inside of the first Left Join statement to get both (2 & 3)? Also, invUpdate will contain only 1 record for the vin. – Macrunning Jan 19 '16 at 01:14
  • @Macrunning nope, not possible; by definition LEFT JOIN takes everything on the left and only the rows on the right that match according to the join condition. If you were to try to "game" the condition, the best you could do is match every unmatched invUpdate row with **every** inventory row, and even then would have to be using the query we are suggesting to UNION with as your subquery. Why so set on having this be a "single query" (_technically, a UNION is a single query._)? – Uueerdo Jan 19 '16 at 01:28
  • not set on anything. I don't know everything so that is why I am here asking questions. Union statement i finally came up with gave me over 400 records. i really just need the 116 as mentioned earlier. Maybe I'm not asking the question correctly. I have 3 tables 2 of which have the same columns and names. table invUpdate (any information on a record that matches with the same vin should override the inventory table record result). But I also need any record from invUpdate that isnt in the inventory table. (I also need any matching record from photos based on matching vin) – Macrunning Jan 19 '16 at 01:37
  • sorry, @Macrunning - my sql post is broken - I'm trying to edit up a better version that addresses the filtering you want to do. – jgreve Jan 19 '16 at 01:53
  • no worries jgreve. I don't think this is a simple thing. For me, my thought process is that I should be able to do something simple like LEFT JOIN invUpdate u ON i.vin=u.vin _AND u.vin!=i.vin_ but that's not working. – Macrunning Jan 19 '16 at 02:04
  • I'm getting close - some deeper SQL theory would help you work out why it isn't doing what you want. If you have a little extra money I will recommend book "SQL For Smarties" very much, it does a pretty solid job of explaining how to "think" like an sql engine. – jgreve Jan 19 '16 at 02:16