I can't wrap my head around joins - mysql

So, alright, I have a few tables. My current query runs against a "historical" table. I want to do a join of some kind to get the most recent status from my Current table. These tables share a like column, called "ID"
Here's the structure
ddCurrent
-ID
-Location
-Status
-Time
ddHistorical
-CID (AI field to keep multiple records per site)
-ID
-Location
-Status
-Time
My goal now is to do a simple join to get all the variables from ddHistorical and the current Status from ddCurrent.
I know that they can be joined on ID since both of them have the same items in their ID tables, I just can't figure out which kind of join is appropriate or why?

I'm sure someone may provide a specific link that goes into great detail explaining, but I'll try to summarize it this way. When writing a query, I try to list the tables from the position of what table do I want to get data from and have that as my first table in the "FROM" clause. Then, do "JOIN" criteria to other tables based on relationships (such as IDs). In your example
FROM
ddHistorical ddH
INNER JOIN ddCurrent ddC
on ddH.ID = ddC.ID
In this case, INNER JOIN (same as JOIN) the ddHistorical table is the left table(listed first for my styling consistency and indentation) and ddCurrent is the right table. Notice my ON criteria that joins them together is also left alias.column = right alias table.column -- again, this is just for mental correlation purposes.
an Inner Join (or JOIN) means a record MUST have a match on each side, otherwise it is discarded.
A LEFT JOIN means give me all records in the LEFT table (ddHistorical in this case), regardless of a matching in the right-side table (ddCurrent). Not practical in this example.
A RIGHT JOIN is the reverse... give me all records from the RIGHT-side table REGARDLESS of a matching record in the left side table. Most of the time you will see LEFT-JOINs more frequently than RIGHT-JOINs.
Now, a sample to mentally get the left-join. You work at a car dealership and have a master table of 10 cars that are sold. For a given month, you want to know what IS NOT selling. So, start with the master table of all cars and look at the sales table for what DID sell. If there is NO such sales activity the right-side table will have NULL value
select
M.CarID,
M.CarModel
from
MasterCarsList M
LEFT JOIN CarSales CS
on M.CarID = CS.CarID
AND month( CS.DateSold ) = 4
where
CS.CarID IS NULL
So, my LEFT join is based on a matching car ID -- AND -- the month of sales activity is 4 (April) as I may not care about sales for Jan-Mar -- but would also qualify year too, but this is a simple sample.
If there is no record in the Car Sales table it will have a NULL value for all columns. I just happen to care about the car ID column since that was the join basis. That is why I am including that in the WHERE clause. For all other types of cars that DO have a sale it will have a value.
This is a common approach you will see in querying where someone looking for all regardless of other... Some use a where NOT EXIST ( subselect ), but those perform slower because they test on every record. Having joins is much faster.
Other examples may be you want a list of all employees of a company, and if they had some certification / training to show it... You still want all employees, but LEFT-JOINING to some certification/training table would expose those extra field as needed.
select
Emp.FullName,
Cert.DateCertified
FROM
Employees Emp
Left Join Certifications Cert
on Emp.EmpID = Cert.EmpID
Hopefully these samples help you understand better the relationship for queries, and now to actually provide answer for your needs.
If what you want is a list of all "Current" items and want to look at their historical past, I would use current FIRST. This might be if your current table of things is 50, but historically your table had 420 items. You don't care about the other 360 items, just those that are current and the history of those.
select
ddC.WhateverColumns,
ddH.WhateverHistoricalColumns
from
ddCurrent ddC
JOIN ddHistorical ddH
on ddC.ID = ddH.ID

If there is always a current field then a simple INNER JOIN will do it
SELECT a.CID, a.ID, a.Location, a.Status, a.Time, b.Status
FROM ddHistorical a
INNER JOIN ddCurrent b
ON a.ID = b.ID

An INNER JOIN will omit any ddHistorical rows that don't have a corresponding ID in ddCurrent.
A LEFT JOIN will include all ddHistorical rows, even if they don't have a corresponding ID in ddCurrent, but the ddCurrent values will be null (because they're unknown).
Also note that a LEFT JOIN is just a specific type of outer join. Don't bother with the others yet - 90% or more of what you'll ever do will be INNER or LEFT.
To include only those ddHistorical rows where the ID is in ddCurrent:
SELECT h.CID, h.ID, h.Location, h.Status, c.Status, h.Time
FROM ddHistorical h
INNER JOIN ddCurrent c ON h.ID = c.ID
If you want to include ddHistorical rows even if the ID isn't in ddCurrent:
SELECT h.CID, h.ID, h.Location, h.Status, c.Status, h.Time
FROM ddHistorical h
LEFT JOIN ddCurrent c ON h.ID = c.ID
If all ddHistorical rows happen to match an ID in ddCurrent, note that both queries will return the same result.

Related

How to select from multiple tables when some of the tables are empty in MySQL

I need to fetch data from 5 tables(all columns of each table) all have FK, which is PK of single table.
But some of the tables may have record may be empty.If data is present on the respective column/table it should return otherwise null/default value
There is one to many and one to one relations on the child tables with parent table.
I have tried so far
- UNION which has concern of same number of columns
- CROSS JOIN not returning any data
- SELECT ALL_COLUMN FROM ALL_TABLE WHERE TABLE.FK=ID Not returning any data
- LEFT JOIN working for 2 tables but not more than that
SELECT A.GENDER, B.BLOCKED_USER FROM t_macroworld_registration AS A
LEFT JOIN t_macroworld_blacklist AS B ON 1=1 WHERE A.ID=15
What are the possible ways I can implement this in a view in MySQL.
Outer join operations are the normative pattern...
SELECT ...
FROM a
LEFT JOIN b ON b.a_id = a.id
LEFT JOIN c ON c.a_id = a.id
LEFT JOIN d ON d.a_id = a.id
WHERE a.id = 15
It's important for the predicates on the outer joined tables to be in the ON clause and not the WHERE clause. If there's any predicate in the WHERE clause requires that a value from one of the outer joined tables be non-NULL, that will negate the "outerness" of the join, making it into an inner join.
The "big rock" problem with this the result when there are more than one matching rows in b, c and d. If there's five rows from b that match, and three rows from c that match, and two rows from b that match, it's going to look like a lot of duplicates. (5x3x2 = 30 rows to be returned, with a lot of duplicated data on those rows.)
Finally I have solved It,
I broke the whole thing into many select query based on FK from each table, so number of additional row returns and mapping has become easy.
Who ever is getting this kind of problem, if it is possible then break it into many select query instead of one.
SELECT
w.id,w.name,a.address,i.name,i.quantity
FROM
warehouse w
LEFT JOIN address AS a ON a.warehouse_id = w.id
LEFT JOIN item AS i ON i.warehouse_id = w.id
LEFT JOIN order AS o ON o.item_id = i.id
WHERE
w.id = 1
GROUP BY 1,2,3,4;
Will give you an overview of your stock and orders for your warehouses. This will also duplicate some results.
Assuming 2 warehouses, 1 address for each, 3 items per warehouse, 2 orders by item = 2 * 3 * 2 = 12 lines
I recommend adding your LEFT JOIN stage by stage and visualizing the result for each stage. You'll quickly understand why lines are multiplying.
Note the usage of foreign keys and ids in the tables to link the tables.
Good luck

MYSQL Use OR in Left Join condition?

I'm not entirely sure that what I'm trying to do is possible. Can you use an OR in the condition of a left join? I start from my users table and then it can either go from week_meal to meal (adding a meal they do not own to their weekly meal plan) or straight to meal (a meal they own). That part appears to be working, but when I include mta.meal_to_add_id in the select, it incorrectly pulls in meals that do NOT meet the criteria in the LEFT JOIN to meal_to_add.
Fiddle with structure: http://sqlfiddle.com/#!2/7bd9c4
SELECT DISTINCT m.*, o.username as owner,i.*, mta.meal_to_add_id, follow_id
FROM webusers wu
LEFT JOIN week_meal wm ON wu.id=wm.user_id
LEFT JOIN meal m ON (wu.id=m.user_id OR wm.meal_id=m.meal_id)
LEFT JOIN webusers o ON m.user_id=o.id
LEFT JOIN meal_to_add mta ON
((wm.user_id = mta.user_id AND wm.meal_id=mta.meal_id)
OR (m.user_id=mta.user_id AND m.meal_id=mta.meal_id))
JOIN ingredient i ON m.meal_id = i.meal_id
LEFT JOIN follow f ON
m.user_id!=wu.id AND
m.user_id=f.followed_webuser_id
AND wu.id=f.followee_webuser_id
WHERE wu.id=5 AND m.meal_id in (138)
ORDER BY m.meal, i.ingredient_id
OUTPUT: It should be just like this only including the field for mta.meal_to_add_id, which in this case should be NULL for all rows (18)
Sample Results
To answer the first part of your question: Yes, you can use an OR clause in a LEFT JOIN.
As for the second part, in plain words, this is what the query seems to say:
Join 'week meals' to users on the user id. Join meals to users on that same user id OR join meals to users on the meal id. Assume now that we have some matching meal/user combinations, where some meal rows are matched on user, and others are matched on the meal id.
Next, join webusers to meals again. Now we have meal rows possibly matching two sets of users. So when mta tries to match meals, it is matching two possible sets of meal/user combinations.
My practice in cases like this is to break up the query into two queries and put the intermediate results in a temp table (using MEMORY engine), then select from that.

How to exclude records which don't match a LEFT JOIN from a SELECT in MySQL?

I'm trying to get a product search to work properly in MySQL 5.0.88.
Basic setup: I have table A with products
A.id
A.ean
A.styleNo
A.price
A.seller (ID e.g. 123, 456)
And a table B with pricelists
B.name
B.ean
B.alt_price
B.seller
Sellers can define optional pricelists, which are matched to the user doing the search. My search more or less looks like this:
SELECT A.styleNo, A.price, B.price
FROM arts A
LEFT JOIN pricelists B ON
B.seller = A.seller
AND B.ean = A.ean
AND B.alt_price != 0
WHERE...
// pricelists
AND ( B.name = "some_name" AND B.seller = "123" ) OR ( next applicable seller ) ...
So after the LEFT JOIN, I'm including all items from a pricelist by name and seller.
This works ok as it selects both the regular price (A) and alt_price (B) and I can check for existing alt_price when displaying the results to show the correct price to the user.
However if the seller does not give an alt-price to all of his products, I'm now displaying the product with the price from A, when in fact I DON'T want to display products from this seller which do not have a pricelist entry at all (think regional assortment).
So if user X has a pricelist "abc" and seller 123 has 500 products, 200 of which are on the pricelist "abc", I only want to display the 200 products and not 500 with 200 in the correct price.
I tried to use B.alt_price != 0 in the LEFT JOIN, but this doesn't help, because all items on there have a price.
Question
Is there a way to do this in the actual search or do I have to do it in the results loop, which I'm not really keen on doing.
SELECT
b.styleNo, b.price, a.alt_price
FROM
pricelists a
INNER JOIN
arts b ON a.seller = b.seller AND a.ean = b.ean
WHERE
a.alt_price <> 0 AND
a.name = 'name' AND
a.seller = 123
What the INNER JOIN is doing here is returning the row only if the seller and ean fields match in both tables, so it will only retrieve the products which are on the pricelist filtered through on the WHERE.
A LEFT JOIN on the other hand, will return all rows regardless of whether or not there's a match in the other table. If there is a match, the corresponding values in the second table are shown, but if there isn't, the values will be NULL from the second table, while still retaining the row data from the first table.
So if we instead did FROM arts a LEFT JOIN pricelists b ON ..., we would get all rows from the products table regardless of whether there's a match in the pricelist table. If a pricelist didn't match up, the product still shows, but with the pricelist data containing NULL values.
Note that the table on the left side of the LEFT JOIN has its row data retained regardless of matches in the table on the right side of the LEFT JOIN... hence "LEFT".
You might want to take a look at Jeff Atwood's visual explanation of joins to understand how the different JOIN types work.
Also understand that WHERE is evaluated after joins, so the conditional filtering you specify in WHERE will apply after the joins have taken place. So if you wanted all rows only where table2's rows didn't match table1's rows in a LEFT JOIN, you would specify WHERE table2.fieldname IS NULL.
It sounds like you want an inner join instead of an outer join. An inner join only returns those combinations where the join condition succeeds. A left join will always return at least one row for each row in the left table.
To get only those products with an alt price, do something like:
SELECT A.styleNo, A.price, B.price
FROM arts A
INNER JOIN pricelists B ON
B.seller = A.seller
AND B.ean = A.ean
AND B.alt_price is not null
WHERE...
Alternately, you can add AND B.alt_price is not null to the where clause of your current query, though that's likely to be less efficient unless your db's query optimizer takes over.
Ok. Finally found the solution.
The problem is, the search will contain multiple sellers, some of whome use pricelists and some who don't.
If I do an INNER JOIN on pricelists B, I will not get the products of sellers who don't use pricelists as they will not be have any entry in B.
If I do a LEFT JOIN on pricelists B, all entries will have either NULL or pricelist B values, so when I'm searching for a seller who uses pricelists for some of his products, I will always get his full range, regardless of alt_price specified
If I try to filter these unwanted records (seller uses pricelists, exclude products which are not on it), by adding B.alt_price != 0 to the WHERE clause, I'm also excluding all products from sellers not using pricelists.
I solved it like this:
- I'm having to construct this line anyway:
LEFT JOIN pricelists B ON
// dynamic construct depending on number of sellers using pricelists matched to user
B.seller = A.seller
AND B.ean = A.ean
AND B.alt_price != 0
So I created another variable, that includes all seller IDs who use pricelists and are applicable to this user. Looks like this:
123,456,789...
I add this to the WHERE clause:
AND ( IF( A.seller IN ( 123,456,789... ), B.alt_price IS NOT NULL,1 ) )
This way, I'm checking
(a) if the record is from a pricelist seller applicable to the user, and
(b) if that's the case, the records must not have a NULL value in the b.alt_price, which records not being on the pricelist will have, since sql adds NULL to all records not on the pricelist B when LEFT JOINING.
That was difficult...

MySQL subquery - Find only first record in a LEFT JOIN

I'm trying to display a list of member records, and I have a few tables I'm using to display what I need.
That's the easy part. The part I need help with is with a table that has many records to each member record: Login history
I want to display only the first row for each member record, that exists in the Login History table. Alternatively, I may want to flip flop and display the last record in the Login History table, as well.
here's what I've got so far:
SELECT m.memberid, m.membername, m.gender, mp.phone
FROM tbl_members m,
tbl_members_phones mp,
tbl_members_addresses ma
WHERE m.defaultphoneid = mp.phoneid
AND m.defaultaddressid = ma.addressid
So that returns what's expected.
The 2 columns from tbl_members_login_history I'd like to add to the returned result are: mh.loggedtime, mh.ipaddy
I know adding the tbl_members_login_history as a LEFT JOIN would return duplicates, so I'm thinking there must be a Subquery necessity here, in order to return just the 1st record for that memberid that exists in tbl_members_login_history.
What I'm worried about is if no record in the history table exists, I still want to display that member info, but leave the history columns as NULL.
Would this be a subquery incident? and if so, how does one add that type of LIMIT?
This is the greatest-n-per-group problem, which is asked frequently on Stack Overflow.
Here's how I would solve it in your scenario:
SELECT m.memberid, m.membername, m.gender, mp.phone, mh.loggedtime, mh.ipaddy
FROM tbl_members m
INNER JOIN tbl_members_phones mp ON m.defaultphoneid = mp.phoneid
INNER JOIN tbl_members_addresses ma ON m.defaultaddressid = ma.addressid
LEFT OUTER JOIN tbl_members_login_history mh ON m.memberid = mh.memberid
LEFT OUTER JOIN tbl_members_login_history mh2 ON m.memberid = mh2.memberid
AND mh.pk < mh2.pk
WHERE mh2.pk IS NULL;
That is, we want mh to be the most recent row in tbl_member_login_history for the given memberid. So we search for another row mh2 that is even more recent. If none more recent than the mh row is found, then mh2.* will be NULL, so mh must be the most recent.
I'm assuming this table has a primary key column that contains increasing values. For this example, I assume the column name is pk.
Using LEFT OUTER JOIN for both references to the login history table means that the m row will be reported even if there is no matching row.
add like this
LEFT OUTER JOIN (SELECT member_id, MAX(LAST_LOGIN_DATE) from tbl_members_login_history) Last_Login ON Last_Login.memberid = m.memberid
PS. LAST_LOGIN_DATE is pseudo column, you can try your restictive column

MySQL - How to JOIN with an unexisting index?

I have a table in my DB called ORDERS and it looks like this:
ID_section (int), ID_price (int), ID_city (int), ID_company (int)
And I want to use the JOIN method to set names to the ID's.
What I would do is:
SELECT * FROM ORDERS
JOIN sections ON sections.id=orders.ID_section
JOIN prices ON prices.id=orders.ID_price
JOIN cities on cities.id=orders.ID_cities
JOIN companies ON companies.id=orders.ID_company
But the point is, that in ORDERS table can be inserted value of 0 and it means - all the sections/prices/cities/companies, but when I run my query, only the values, that their ID exist in the other table show up.
Any ideas? Thanks.
If I understand you question correctly and having, for example, ID_section = 0 means that the order belongs to all the section then the following query should do the trick.
SELECT * FROM ORDERS
JOIN sections ON sections.id=orders.ID_section OR orders.ID_section = 0
JOIN prices ON prices.id=orders.ID_price OR orders.ID_price = 0
JOIN cities on cities.id=orders.ID_cities OR orders.ID_cities = 0
JOIN companies ON companies.id=orders.ID_company OR orders.ID_company = 0
If, on the other hand, you want to retrieve all orders regardless if they have sections, prices, etc. associated, then it is sufficient to put LEFT JOIN where you have JOIN. (But this situation does not result from your question! I only added it because people seem to understand that.)
Use LEFT JOIN instead of JOIN (which is a shorthand for INNER JOIN)
Experiment with LEFT and RIGHT OUTER JOIN s and you'll be able to figure out what you need to do. Basically a LEFT or RIGHT OUTER JOIN will insert NULLS into columns where no data exists but still do the joins.
There are various resources on the web that explain this.