tags:

views:

174

answers:

3

I have multiple tables that I want to join to get a specific result.

Tables are:

ad_categories, ad_image, ad_data, ad_location

I need a row output for every one of the following rows in the ad_categories table, regardless if there exists data for any of these categories for a specific location when using the WHERE conditional.

mysql> SELECT type FROM ad_categories;
+---------------+
| type          |
+---------------+
| restaurants   |
| fitness       |
| funactivities |
| shopping      |
| homes         |
| men           |
+---------------+

For example:

mysql> SELECT alias, type, sha, originalname FROM ad_categories LEFT JOIN ad_data ON ad_data.cid=ad_categories.id LEFT JOIN ad_image ON ad_image.pid=ad_data.id LEFT JOIN ad_location ON ad_location.id=ad_data.lid GROUP BY type;
+--------+---------------+------------------------------------------+---------------------------------+
| alias  | type          | sha                                      | originalname                    |
+--------+---------------+------------------------------------------+---------------------------------+
| malibu | fitness       | ad8b277202f4ded274274744b3fa28f34e9f1c21 | thai_body_works.jpg             |
| malibu | funactivities | 6a226df8ff827aa020b077e9e0d48e4701ae2fca | rosenthal-the_malibu_estate.jpg |
| NULL   | homes         | NULL                                     | NULL                            |
| NULL   | men           | NULL                                     | NULL                            |
| malibu | restaurants   | 98f357dfa5bdb2eb1d480dc0e8b7156b1eecac31 | moonshadows.jpg                 |
| malibu | shopping      | 1b2ef538691569842b9f9fb6c3816673f334205a | malibu_surf_shack.jpg           |
+--------+---------------+------------------------------------------+---------------------------------+
6 rows in set (0.00 sec)

Lists all the category types I need, but I haven't specified the location which is what I need to do. I want only one row per type, per location specified. If no data exists for the category type for that location, fill in null values for the columns other than type.

mysql> SELECT alias, type, sha, originalname FROM ad_categories LEFT JOIN ad_data ON ad_data.cid=ad_categories.id JOIN ad_image ON ad_image.pid=ad_data.id LEFT JOIN ad_location ON ad_location.id=ad_data.lid WHERE ad_location.alias='malibu' GROUP BY type;
+--------+---------------+------------------------------------------+---------------------------------+
| alias  | type          | sha                                      | originalname                    |
+--------+---------------+------------------------------------------+---------------------------------+
| malibu | fitness       | ad8b277202f4ded274274744b3fa28f34e9f1c21 | thai_body_works.jpg             |
| malibu | funactivities | 6a226df8ff827aa020b077e9e0d48e4701ae2fca | rosenthal-the_malibu_estate.jpg |
| malibu | restaurants   | 98f357dfa5bdb2eb1d480dc0e8b7156b1eecac31 | moonshadows.jpg                 |
| malibu | shopping      | 1b2ef538691569842b9f9fb6c3816673f334205a | malibu_surf_shack.jpg           |
+--------+---------------+------------------------------------------+---------------------------------+
4 rows in set (0.00 sec)

I am not a pro SQL ninja. Can someone correct me if I am building my SQL query incorrectly or educate me with a few clues on how to properly accomplish what I want to do? Thanks.

A: 

You need to use an "outer join".

Adam Butler
+2  A: 

Use:

SELECT alias, type, sha, originalname
  FROM (SELECT alias, 
               type, 
               sha, 
               originalname,
               CASE WHEN @type = type THEN @rownum := @rownum + 1 ELSE @rownum := 1 END AS rank,
               @type := type
          FROM AD_CATEGORIES c 
     LEFT JOIN AD_DATA d ON d.cid = c.id 
     LEFT JOIN AD_LOCATION l ON l.id = d.lid 
                            AND l.alias = ?
     LEFT JOIN AD_IMAGE i ON i.pid = d.id 
          JOIN (SELECT @rownum := 0, @type := '') r
      ORDER BY type, l.id DESC) x
 WHERE x.rank = 1

The only thing I don't have is what column in, I'm assuming, AD_LOCATION that you'd specify "malibu"/etc as the location. The ORDER BY is important, to make sure that non-NULL alias/locations are ranked before NULL ones...

Breakdown

This:

JOIN (SELECT @rownum := 0, @type := '') r

...allows you to define & initialize variables without needing to use a separate SET command.

These:

CASE
   WHEN @type = type THEN @rownum := @rownum + 1  -- increments rownum when type matches
   ELSE @rownum := 1 -- resets rownum to 1 when @type != type
END AS rank,
@type := type -- need this to set @type for the next rows comparison

See the stuff after the "-- " for line-by-line commenting.

ORDER BY type, l.id DESC

The ORDER BY is necessary to keep the type values in order, otherwise the rank value won't as we'd like. The second part of the ORDER BY ensures that the location values that aren't NULL come up first, if there are any based on the filtration.

Finally, the outside query only gets the rows whose rank value is 1...

OMG Ponies
The column is ad_location.type
kom
@Kyle Lafkoff: Thx, updated.
OMG Ponies
@OMG Ponies: #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '@type = type FROM AD_CATEGORIES c LEFT JOIN AD_DATA d ON d.cid' at line 7Do you understand the error? I am reading the docs to understand what is going on here in the mean time.
kom
@OMG Ponies: OOPS, the column is named ad_location.alias for the location, sorry about that.
kom
OMG Ponies
@OMG Ponies: #1064 '@rownum := 0, @type := '') r ORDER BY type, l.id DESC) x WHERE x.rank =' at line 13
kom
@Kyle Lafkoff: ...and the `SELECT` before @rownum - see update
OMG Ponies
Can you explain to me what you're doing here? The CASE statement and then the SELECT inside the last JOIN
kom
@Kyle Lafkoff: See update
OMG Ponies
@OMG Ponies, Not getting the result I want. Right now it's returning 10 results (there are 10 row entries in the DB for the location) when: l.alias = 'malibu'. It need only one row per category, for a total of 6 total rows returned from database with the query. If there is nothing that exists for a specific location for one of the 6 categories, still output a row for that category but have the sha and originalname as null. I appreciate your help very much :)
kom
@kom: Is it possible for you to update the question with sample data? I'd like to recreate it in my own environment to see what I'm missing
OMG Ponies
@OMG Ponies: Here's a dump of the database with the tables: http://pastebin.ca/1929399
kom
@kom: Thx, I'll work on it later tonight.
OMG Ponies
@kom: Fixed it, updated the answer. You were right, it did need a LEFT JOIN onto `AD_LOCATION`. The other problem was my typo here: `@type = type` needs to assign the value, so it needs to be `@type := type`. Thank you for posting the data!
OMG Ponies
@OMG Ponies: check this output: http://pastebin.com/0TsLvXZp -- Notice how I change the location alias at the top and it gives the same result yet below when I change the location, it shows a ranking of 7 for brentwood...the first query on the pastebin.com page should return NULL for all categories except the one ad located in brentwood / restaurants, as indicated in the 3rd query output on the page, the row: #| brentwood | restaurants | 4cc3d6691ac5980f3342156d2bc0cffdf1e515ac | zurobata.jpg | 7 | restaurants |
kom
A: 

Left this alone for a while, came back to it. Here's the answer I needed, thanks to a coworker at a new job:

SELECT 
    alias, type, sha, originalname 
FROM 
    ad_categories 
LEFT JOIN 
    ad_location ON ad_location.alias = '?' 
LEFT JOIN 
    ad_data ON ad_data.cid=ad_categories.id AND ad_location.id=ad_data.lid 
LEFT JOIN 
    ad_image ON ad_image.pid=ad_data.id GROUP BY type;

Thanks coworker ;)

kom