views:

64

answers:

4

I am building a database similar to the one described here where I have products of different type, each type with its own attributes.

I report a short version for convenience

product_type
============
product_type_id INT
product_type_name VARCHAR

product
=======
product_id INT
product_name VARCHAR
product_type_id INT -> Foreign key to product_type.product_type_id
... (common attributes to all product) 

magazine
========
magazine_id INT
title VARCHAR
product_id INT -> Foreign key to product.product_id
... (magazine-specific attributes)

web_site
========
web_site_id INT
name VARCHAR
product_id INT -> Foreign key to product.product_id
... (web-site specific attributes)

This way I do not need to make a huge table with a column for each attribute of different product types (most of which will then be NULL)

How do I SELECT a product by product.product_id and see all its attributes? Do I have to make a query first to know what type of product I am dealing with and then, through some logic, make another query to JOIN the right tables? Or is there a way to join everything together? (if, when I retrieve the information about a product_id there are a lot of NULL, it would be fine at this point).

Thank you

A: 

Why not make an AttributeDefinition table and a ProductAttribute table? Something along the lines of:

AttributeDefinition
    Id
    Description

ProductAttribute
    AttributeDefinitionId
    ProductId
    Value

Then, no matter which product you are dealing with, you know that you can get all the attributes by simply querying the ProductAttribute table. And you don't have to add a new specific table each time the need for a new product with custom attributes arises.

klausbyskov
+2  A: 

Nice design. Nice job avoiding the Entity Attribute Value trap.

You will simply do a join, as you suggested, but I don't see a need for two queries. I don't even think the product_type table is required.

SELECT * FROM product p
LEFT JOIN magazine m
ON m.product_id = p.product_id
LEFT JOIN web_site w
ON w.product_id = p.product_id

In the above query, for a magazine, m.product_id IS NOT NULL and for a web_site, w.product_id IS NOT NULL.

Magazines only:

SELECT * FROM product p
JOIN magazine m
ON m.product_id = p.product_id

Websites only:

SELECT * FROM product p
JOIN web_site w
ON w.product_id = p.product_id

Your big question was about getting the column names? You're probably coding these, or you use reflection to get them. Most database access layers provide reflection.

Marcus Adams
+1  A: 

You could do it all in one query, a few columns would stay empty:

SELECT
  t.product_type_name,
  t.product_type_id 
  p.product_id,
  p.product_name,
  p.[common attributes to all products...],
  m.*,
  w.*
FROM
  product p
  INNER JOIN product_type t ON t.product_type_id = p.product_type_id
  LEFT  JOIN magazine     m ON m.product_id      = p.product_id
  LEFT  JOIN web_site     w ON w.product_id      = p.product_id
WHERE
  p.product_id = ?

Use product_type_id in your app to determine which columns of the result set are interesting to you in any particular case.

As far as performance goes, this should run pretty quickly (foreign keys, indexes); and it produces a consistent result set for any product type.

I would recommend against using .* and for explicitly listing every column name, this is more portable, more maintainable and less error-prone.

Tomalak
Plus, using w.* and m.* will re-include the common columns you might have already included.
BradC
Thank you! It works.I am pretty new to SQL and I was puzzled by the existence of LEFT JOIN. Now I understand!However the right way is probably this: LEFT JOIN magazine m ON m.product_id = p.product_id LEFT JOIN web_site w ON w.product_id = p.product_idSince product_id are unique in the product table, they are also unique in all the subtables, so there is no need for a magazine_id or a web_site_id.Similarly, I don't see a need for the product_type table. I can just add an attribute in the product table
Stefano
@Stefano: 1) I joined against the `product_type` because it was there. 2) You are correct regarding the join, I used the wrong column name. That's corrected now. 3) Why do you have *two* attributes that uniquely identify a magazine (`magazine_id` and `product_id`, obviously)? This setup makes no sense to me. `product_id` is unique across product types, so there is no need for yet another unique id within each product type.
Tomalak
@Tomalak. Agreed. I copied the design as it was in the original question. Only later I saw there was too many columns and tables
Stefano
A: 

Big nasty union all, with explicit columns for each type, NULL if they don't apply (vastly simplified):

SELECT ID, ProductType, m.Name as MagazineName, m.Pages as MagazinePages, 
                        NULL as WebSiteName, NULL as WebSiteURL
         FROM Magazines m
UNION ALL
SELECT ID, ProductType, NULL as MagazineName, NULL as MagazinePages, 
                        w.Name as WebSiteName, w.URL as WebSiteURL
         FROM WebSites w

Will product an output like:

ID   Type       MagazineName   MagazinePages   WebSiteName   WebSiteURL
1    Magazine   Time           100             NULL          NULL
2    Magazine   Newsweek       80              NULL          NULL
3    Website    NULL           NULL            Yahoo         www.yahoo.com
4    Website    NULL           NULL            Google        www.google.com
BradC