I want to display orders item that have the collect_id = 2
And I want to display all the fields related to each order_item as columns with values.
These are the tables and the result :
+-------------------------------+
| order_item |
+-------------------------------+
| oi_id oi_price oi_collect_id |
| 1 100 2 |
| 2 30 2 |
| 3 55 3 |
| 4 70 4 |
| 5 220 2 |
| 6 300 4 |
+-------------------------------+
+-----------------------------------+
| field_value |
+-----------------------------------+
| v_value v_fk_field_id oi_fk_id |
| Peter 1 1 |
| Lagaf 2 1 |
| Football 3 1 |
| Male 4 1 |
| 12345678 5 1 |
| Frank 1 2 |
| Loran 2 2 |
| Tennis 3 2 |
| Male 4 2 |
| 11223658 5 2 |
| Nathali 1 5 |
| Waton 2 5 |
| Reading 3 5 |
+-----------------------------------+
oi_fk_id : foreign key ref(order_item.oi_id)
v_fk_field_id : foreign key ref(field.f_id)
+--------------------+
| field |
+--------------------+
| f_id f_label |
| 1 surname |
| 2 name |
| 3 hobbies |
| 4 sex |
| 5 phone |
+--------------------+
+-----------------------------------------------------------------------------+
| Result |
+-----------------------------------------------------------------------------+
| oi_id oi_price oi_collect_id surname name hobbies sex phone |
| 1 100 2 Peter Lagaf Football Male 12345678 |
| 2 30 2 Frank Loran Tennis Male 11223658 |
| 5 220 2 Nathali Waton Reading null null |
+-----------------------------------------------------------------------------+
Important : The table field does not contain only these 5 fields (name, surname, hobbies, sex, phone), but it can contain many others, that the developper may not know, same thing for the correspondant value on the table 'field_value'.
PS : I didn't make field labels as columns in a table because they are dynamic and not limited, and in the front end application, the user can add new fields as he want.
You can take advantage of dynamic pivoting to get the results:
SELECT GROUP_CONCAT(t.line)
FROM (
SELECT CONCAT('MAX(IF(t.l=''', f.f_label, ''',t.v,NULL)) AS ', f.f_label) AS line
FROM field f
) AS t
INTO #dynamic;
SELECT CONCAT('SELECT t.oi_id, t.oi_price, t.oi_collect_id,',
#dynamic,
' FROM ( SELECT oi.*, f.f_label AS l, fv.v_value AS v FROM order_item oi JOIN field_value fv ON fv.oi_fk_id = oi.oi_id JOIN field f ON f.f_id = fv.v_fk_field_id WHERE oi.oi_collect_id = 2 ) AS t GROUP BY t.oi_id;')
INTO #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
However it is limited by GROUP_CONCAT function:
The result is truncated to the maximum length that is given by the group_concat_max_len system variable, which has a default value of 1024. The value can be set higher, although the effective maximum length of the return value is constrained by the value of max_allowed_packet.
EDIT - Why MAX function is required?
To solve the given problem, we are creating a dynamic query, based on the content of the field table.
For the given exemplary data, the query without use of MAX function would be:
SELECT t.oi_id,
t.oi_price,
t.oi_collect_id,
IF(t.l='surname', t.v, NULL) AS surname,
IF(t.l='name', t.v, NULL) AS name,
IF(t.l='hobbies', t.v, NULL) AS hobbies,
IF(t.l='sex', t.v, NULL) AS sex,
IF(t.l='phone', t.v, NULL) AS phone
FROM (
SELECT oi.*,
f.f_label AS l,
fv.v_value as V
FROM order_item oi
JOIN field_value fv
ON fv.oi_fk_id = oi.oi_id
JOIN field f
ON f.f_id = fv.v_fk_field_id
WHERE oi.oi_collect_id = 2
) AS t;
Which wouldd result in:
+-------+----------+---------------+---------+-------+----------+------+----------+
| oi_id | oi_price | oi_collect_id | surname | name | hobbies | sex | phone |
+-------+----------+---------------+---------+-------+----------+------+----------+
| 1 | 100 | 2 | Peter | NULL | NULL | NULL | NULL |
| 2 | 30 | 2 | Frank | NULL | NULL | NULL | NULL |
| 5 | 220 | 2 | Nathali | NULL | NULL | NULL | NULL |
| 1 | 100 | 2 | NULL | Lagaf | NULL | NULL | NULL |
| 2 | 30 | 2 | NULL | Loran | NULL | NULL | NULL |
| 5 | 220 | 2 | NULL | Waton | NULL | NULL | NULL |
| 1 | 100 | 2 | NULL | NULL | Football | NULL | NULL |
| 2 | 30 | 2 | NULL | NULL | Tennis | NULL | NULL |
| 5 | 220 | 2 | NULL | NULL | Reading | NULL | NULL |
| 1 | 100 | 2 | NULL | NULL | NULL | Male | NULL |
| 2 | 30 | 2 | NULL | NULL | NULL | Male | NULL |
| 1 | 100 | 2 | NULL | NULL | NULL | NULL | 12345678 |
| 2 | 30 | 2 | NULL | NULL | NULL | NULL | 11223658 |
+-------+----------+---------------+---------+-------+----------+------+----------+
This is an intermediate result, where each row consists of value for one field and NULL for the others. The MAX function together with a GROUP BY clause is used to combine multiple rows concerning one order item in such a way, that it chooses non null values. It could be replaced by MIN function, which will also favor existing values over null.
Related
Prior Information Notice
I have 3 tables:
types
+----+-------------+-----------------------+------------+------------+
| id | category_id | name | created_at | updated_at |
+----+-------------+-----------------------+------------+------------+
| 1 | 1 | T-Shirts | NULL | NULL |
+----+-------------+-----------------------+------------+------------+
prototypes
+----+-----------------------------------------+------------+------------+
| id | name | created_at | updated_at |
+----+-----------------------------------------+------------+------------+
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL |
+----+-----------------------------------------+------------+------------+
filters
+----+-------------+---------------------+-------+------------+------------+
| id | name | value | extra | created_at | updated_at |
+----+-------------+---------------------+-------+------------+------------+
| 1 | gender | male | NULL | NULL | NULL |
| 2 | gender | female | NULL | NULL | NULL |
| 3 | age_group | adult | NULL | NULL | NULL |
| 4 | age_group | child | NULL | NULL | NULL |
| 5 | age_group | baby | NULL | NULL | NULL |
+----+-------------+---------------------+-------+------------+------------+
They are related one another through n-m relationship, so there are respective junction tables types_prototypes, types_filters, prototypes_filters as well. For more details please check also out my dump file.
Problem itself
I'm trying to set up filtering system (with Laravel), so I need to query all Prototypes that are related to all given Filters (logical AND). Until now I have managed to get the, as long as use chose only one Filter:
select * from `prototypes`
inner join `types_prototypes` on `prototypes`.`id` = `types_prototypes`.`prototype_id`
inner join `prototypes_filters` on `prototypes`.`id` = `prototypes_filters`.`prototype_id`
inner join `filters` on `prototypes_filters`.`filter_id` = `filters`.`id`
where `types_prototypes`.`type_id` = ? and `filter_id` = ? group by `prototypes`.`id`
The problem itself consists in the fact that this query is inapplicable, as soon as we have several filters that should be valid simultaneously:
...
where `types_prototypes`.`type_id` = ? and `filter_id` = ? and `filter_id` = ? group by `prototypes`.`id`
I know, where ... and doesn't work, because I have due to join only one column filter_id that can contain only one single value at the same time (what actually groupBy() takes care of). So in this sense I have a new one row for the relation of the same Prototype with another Filter, e.g.:
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| id | name | created_at | updated_at | type_id | prototype_id | prototype_id | filter_id | id | name | value | extra | created_at | updated_at |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL | 1 | 1 | 1 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL | 1 | 1 | 1 | 3 | 3 | age_group | adult | NULL | NULL | NULL |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
I have already tried several different methods, including where 'filter_id' in(?,?), where FIND_IN_SET('filter_id', '?,?') and even restructured my database in accord with EAV-pattern (when the filters is divided into filter_names and filter_values). But every time I obtain only entries that fulfill one requirement of the whole set (equals logical OR), for instance (here we have prototypes for adults and men, but not only for adult men):
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| id | name | created_at | updated_at | type_id | prototype_id | prototype_id | filter_id | id | name | value | extra | created_at | updated_at |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL | 1 | 1 | 1 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 2 | American Apparel Womans T-Shirt | NULL | NULL | 1 | 2 | 2 | 3 | 3 | age_group | adult | NULL | NULL | NULL |
| 3 | Gildan Adult Cotton T-shirt | NULL | NULL | 1 | 3 | 3 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 4 | American Apparel Mens T-Shirt | NULL | NULL | 1 | 4 | 4 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 5 | American Apparel Kids T-Shirt | NULL | NULL | 1 | 5 | 5 | 1 | 1 | gender | male | NULL | NULL | NULL |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
I'm almost desperate, does anybody have a clue?
Thanks you in advice for your help and sorry for so much text, I just wanted to describe all circumstances.
You have to join with the filters table repeatedly for each criterion.
select * from prototypes AS p
inner join types_prototypes AS tp1 on p.id = tp1.prototype_id
inner join prototypes_filters AS pf1 on p.id = pf1.prototype_id
inner join filters AS f1 on pf1.filter_id = f1.id
inner join types_prototypes AS tp2 on p.id = tp2.prototype_id
inner join prototypes_filters AS pf2 on p.id = pf2.prototype_id
inner join filters AS f2 on pf2.filter_id = f2.id
where tp1.type_id = ? and f1.filter_id = ?
AND tp2.type_id = ? and f2.filter_id = ?
group by prototypes.id
I have a table structured like this:
ID NAME SURNAME EXTRA TYPE
______________________________________________
1 MARIO ROSSI RED 10
2 MARCO VERDI YELLOW 10
3 GIANNI BLU TEACHER 20
4 LUCA BLU STUDENT 20
5 LUCA ROSSI GREEN 10
6 MARIA GIALLA 10/08/05 30
7 MARTA ROSA 11/01/79 30
8 FRANCO NERO BARMAN 20
9 MARY NERI 05/09/88 30
10 MAX BLU 06/08/98 30
Now I need to order the query by name, surname and (extra[DESC] where type=30), so with this 3rd condition I mean that I would arrange all extra data where type is 30 in descending order:
....
6 MARIA GIALLA 10/08/05 30
10 MAX BLU 06/08/98 30
9 MARY NERI 05/09/88 30
7 MARTA ROSA 11/01/79 30
....
So I've tried this:
select * from my_table order by name, surname, if(my_table.type=30, extra desc, extra asc)
but it gives me an Sql Error.
Thanks for your support.
Thanks to other programmers, and in according to my data, I solved in this way: ORDER BY name, surname, if(TYPE=30, STR_TO_DATE(EXTRA,'%d/%m/%Y %H.%i'), null) DESC
Try this query :-
ORDER BY
IF(type='30', extra, 0) DESC,
IF(type !='30', extra, 0) ASC
More usefully, consider the following:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,NAME VARCHAR(12) NOT NULL
,SURNAME VARCHAR(12) NOT NULL
,EXTRA VARCHAR(12) NOT NULL
,TYPE INT NOT NULL
);
INSERT INTO my_table VALUES
( 1,'MARIO','ROSSI','RED','10'),
( 2,'MARCO','VERDI','YELLOW','10'),
( 3,'GIANNI','BLU','TEACHER','20'),
( 4,'LUCA','BLU','STUDENT','20'),
( 5,'LUCA','ROSSI','GREEN','10'),
( 6,'MARIA','GIALLA','2005/08/10','30'),
( 7,'MARTA','ROSA','1979/01/11','30'),
( 8,'FRANCO','NERO','BARMAN','20'),
( 9,'MARY','NERI','1988/09/05','30'),
(10,'MAX','BLU','1998/08/06','30'),
(11,'MARIO','ROSSI','PLUMBER','20'),
(12,'MARCO','VERDI','TAILOR','20'),
(13,'GIANNI','BLU','YELLOW','10'),
(14,'LUCA','BLU','BLUE','10'),
(15,'LUCA','ROSSI','BAKER','20'),
(16,'MARIO','ROSSI','2004/08/10','30'),
(17,'MARCO','VERDI','1978/01/11','30'),
(18,'FRANCO','NERO','RED','10'),
(19,'FRANCO','NERO','1987/09/05','30'),
(20,'MARIA','GIALLA','1995/08/06','30');
SELECT name
, surname
, MAX(CASE WHEN type = 10 THEN extra END) colour
, MAX(CASE WHEN type = 20 THEN extra END) occupation
, MAX(CASE WHEN type = 30 THEN extra END) date
FROM my_table
GROUP
BY name
, surname
ORDER
BY name
, surname
, extra
, type
, date DESC;
+--------+---------+--------+------------+------------+
| name | surname | colour | occupation | date |
+--------+---------+--------+------------+------------+
| FRANCO | NERO | RED | BARMAN | 1987/09/05 |
| GIANNI | BLU | YELLOW | TEACHER | NULL |
| LUCA | BLU | BLUE | STUDENT | NULL |
| LUCA | ROSSI | GREEN | BAKER | NULL |
| MARCO | VERDI | YELLOW | TAILOR | 1978/01/11 |
| MARIA | GIALLA | NULL | NULL | 2005/08/10 |
| MARIO | ROSSI | RED | PLUMBER | 2004/08/10 |
| MARTA | ROSA | NULL | NULL | 1979/01/11 |
| MARY | NERI | NULL | NULL | 1988/09/05 |
| MAX | BLU | NULL | NULL | 1998/08/06 |
+--------+---------+--------+------------+------------+
You can join a result like this (or actually a much simpler alternative - but I'll stick with this for now) back onto the original table to order the results as you'd like. I've added in the STR_TO_DATE function just for reference. Obviously it's unnecessary here, and very slightly different from how it would need to be in your version...
SELECT x.*
, y.date
FROM my_table x
LEFT
JOIN
( SELECT name
, surname
, MAX(CASE WHEN type = 30 THEN extra END) date
FROM my_table
GROUP
BY name
, surname
) y
ON y.name = x.name
AND y.surname = x.surname
ORDER
BY x.name
, x.surname
, STR_TO_DATE(y.date,'%Y/%m/%d') DESC;
+----+--------+---------+------------+------+------------+
| ID | NAME | SURNAME | EXTRA | TYPE | date |
+----+--------+---------+------------+------+------------+
| 18 | FRANCO | NERO | RED | 10 | 1987/09/05 |
| 19 | FRANCO | NERO | 1987/09/05 | 30 | 1987/09/05 |
| 8 | FRANCO | NERO | BARMAN | 20 | 1987/09/05 |
| 13 | GIANNI | BLU | YELLOW | 10 | NULL |
| 3 | GIANNI | BLU | TEACHER | 20 | NULL |
| 14 | LUCA | BLU | BLUE | 10 | NULL |
| 4 | LUCA | BLU | STUDENT | 20 | NULL |
| 15 | LUCA | ROSSI | BAKER | 20 | NULL |
| 5 | LUCA | ROSSI | GREEN | 10 | NULL |
| 17 | MARCO | VERDI | 1978/01/11 | 30 | 1978/01/11 |
| 2 | MARCO | VERDI | YELLOW | 10 | 1978/01/11 |
| 12 | MARCO | VERDI | TAILOR | 20 | 1978/01/11 |
| 6 | MARIA | GIALLA | 2005/08/10 | 30 | 2005/08/10 |
| 20 | MARIA | GIALLA | 1995/08/06 | 30 | 2005/08/10 |
| 11 | MARIO | ROSSI | PLUMBER | 20 | 2004/08/10 |
| 16 | MARIO | ROSSI | 2004/08/10 | 30 | 2004/08/10 |
| 1 | MARIO | ROSSI | RED | 10 | 2004/08/10 |
| 7 | MARTA | ROSA | 1979/01/11 | 30 | 1979/01/11 |
| 9 | MARY | NERI | 1988/09/05 | 30 | 1988/09/05 |
| 10 | MAX | BLU | 1998/08/06 | 30 | 1998/08/06 |
+----+--------+---------+------------+------+------------+
It should be like this:
SELECT *
FROM Tabletest
order by
CASE WHEN type <> 30 THEN 1 ELSE 2 END,
CASE WHEN type <> 30 THEN Name END,
CASE WHEN type <> 30 THEN Surname END,
CASE WHEN type = 30 THEN STR_TO_DATE(extra,'%m/%d/%Y') ELSE 0 END DESC
this will order the results as:
I am trying to calculate the number of filled columns per row. For example the the column Perc is the value I wish to calculate.
id Col1 Col2 Col3 Perc
1 NULL 1 2 75% #3 out of 4
2 2 NULL NULL 50% #2 out of 4
3 NULL NUll NULL 25% #1 out of 4
4 a Yes No 100% #4 out of 4
I read count number of Null variables per row mysql, wich offers a possible solution. However in my case the column names and number change frequently. So i would like a more robust query which does not have to be altered every time the names or column count changes.
Consider the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL
,item_id INT NOT NULL
,value VARCHAR(12) NULL
,PRIMARY KEY(id,item_id)
);
INSERT INTO my_table VALUES
(1,1,NULL),
(1,2,'1'),
(1,3,'2'),
(2,1,'2'),
(2,2,NULL),
(2,3,NULL),
(3,1,NULL),
(3,2,NULL),
(3,3,NULL),
(4,1,'a'),
(4,2,'Yes'),
(4,3,'No');
SELECT * FROM my_table;
+----+---------+-------+
| id | item_id | value |
+----+---------+-------+
| 1 | 1 | NULL |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 2 | 1 | 2 |
| 2 | 2 | NULL |
| 2 | 3 | NULL |
| 3 | 1 | NULL |
| 3 | 2 | NULL |
| 3 | 3 | NULL |
| 4 | 1 | a |
| 4 | 2 | Yes |
| 4 | 3 | No |
+----+---------+-------+
SELECT x.id
, ROUND((COUNT(value)+1)/(COUNT(*)+1)*100,2) pct
FROM my_table x
GROUP
BY id;
+----+--------+
| id | pct |
+----+--------+
| 1 | 75.00 |
| 2 | 50.00 |
| 3 | 25.00 |
| 4 | 100.00 |
+----+--------+
I have a query:
select
count(*), paymentOptionId
from
payments
where
id in (select min(reportDate), id
from payments
where userId in (select distinct userId
from payments
where paymentOptionId in (46,47,48,49,50,51,52,53,54,55,56))
group by userId)
group by
paymentOptionId;
The problem place is "select min(reportDate), id", this query must return 1 column result, but I can't realize how to do it while I need to group min.
The data set looks like
+----+--------+--------+-----------+---------------------+--------+----------+-----------------+
| id | userId | amount | userLevel | reportDate | buffId | bankQuot | paymentOptionId |
+----+--------+--------+-----------+---------------------+--------+----------+-----------------+
| 9 | 12012 | 5 | 5 | 2014-02-10 23:07:57 | NULL | NULL | 2 |
| 10 | 12191 | 5 | 6 | 2014-02-10 23:52:12 | NULL | NULL | 2 |
| 11 | 12295 | 5 | 6 | 2014-02-11 00:12:04 | NULL | NULL | 2 |
| 12 | 12295 | 5 | 6 | 2014-02-11 00:12:42 | NULL | NULL | 2 |
| 13 | 12256 | 5 | 6 | 2014-02-11 00:26:25 | NULL | NULL | 2 |
| 14 | 12256 | 5 | 6 | 2014-02-11 00:26:35 | NULL | NULL | 2 |
| 16 | 12510 | 5 | 5 | 2014-02-11 00:42:58 | NULL | NULL | 2 |
| 17 | 12510 | 5 | 5 | 2014-02-11 00:43:08 | NULL | NULL | 2 |
| 18 | 12510 | 18 | 5 | 2014-02-11 00:45:16 | NULL | NULL | 3 |
| 19 | 12510 | 5 | 6 | 2014-02-11 01:00:10 | NULL | NULL | 2 |
+----+--------+--------+-----------+---------------------+--------+----------+-----------------+
select count(*), paymentOptionId
from
(select userId, min(reportdate), paymentOptionId
from payments as t1
group by userId, paymentOptionId) as t2
group by paymentOptionId
Fiddle
It first gets the minimum report date (so the first entry) for every user, for every type (so there are two records for a user who has 2 types) and then counts them grouping by type (aka paymentOptionId).
By the way, you can of course cut the attributes chosen in select in from clause, they are only there so you can copy-paste it and see the results it is giving step by step.
You seem to want to report on various payment options and their counts for the earliest ReportDate for each user.
If so, here is an alternative approach
select p.paymentOptionId, count(*)
from payments p
where paymentOptionId in (46,47,48,49,50,51,52,53,54,55,56) and
not exists (select 1
from payments p2
where p2.userId = p.userId and
p2.ReportDate < p.ReportDate
)
group by paymentOptionId;
This isn't exactly the same as your query, because this will only report on the list of payment types, whereas you might want the first payment type for anyone who has ever had one of these types. I'm not sure which you want, though.
I want to make an anouncement system. But i messed the database design part.
The system will have unlimited categories and their items so every category will has strange properties
for example car has a model, trade, manufacture date, color, gear type. . . and more
another example Real Estate has a door count, gardened, dublex triplex type, how many bath, how many kitchen
another one can be electronic, it has megapixel, gigabyte, warrantie, resolution, dimensions, shipment . . .
so how i can collect these datas together maybe some objects will have similar properties for example most of items must have a manufacter date so how i can use the datas ? How can be the schema ?
Sercan Virlan
Mind you, this is a very vague question, so I am trying my best to answer :)
Note: There are many better ways to do this, and I could question WHY you are doing it this way, but the below method should accomplish what you are looking for!
Here are 3 tables:
CREATE TABLE `object` (
`id` INTEGER NOT NULL AUTO_INCREMENT ,
`category` INTEGER NOT NULL ,
`name` VARCHAR(100) NOT NULL ,
PRIMARY KEY (`id`)
);
CREATE TABLE `properties` (
`id` INTEGER NOT NULL AUTO_INCREMENT ,
`objectid` INTEGER NOT NULL ,
`property_key` VARCHAR(100) NOT NULL ,
`property_value` MEDIUMTEXT NOT NULL ,
PRIMARY KEY (`id`)
);
CREATE TABLE `category` (
`id` INTEGER NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(100) NOT NULL ,
PRIMARY KEY (`id`)
);
Now lets add some pretend data into them:
INSERT INTO category VALUES ("","CAR");
INSERT INTO category VALUES ("","REALESTATE");
INSERT INTO category VALUES ("","ELECTRONIC");
INSERT INTO object VALUES ("",1,"98 CAMERO");
INSERT INTO object VALUES ("",1,"06 PRIUS");
INSERT INTO object VALUES ("",2,"A House Someplace");
INSERT INTO object VALUES ("",3,"iPod Touch");
INSERT INTO object VALUES ("",3,"iPhone");
INSERT INTO object VALUES ("",3,"Zune");
INSERT INTO properties VALUES ("",1,"color","red");
INSERT INTO properties VALUES ("",1,"doors","4");
INSERT INTO properties VALUES ("",1,"engine","v6");
INSERT INTO properties VALUES ("",2,"engine","electric");
INSERT INTO properties VALUES ("",2,"doors","4");
INSERT INTO properties VALUES ("",2,"color","blue");
INSERT INTO properties VALUES ("",6,"video-playback","true");
INSERT INTO properties VALUES ("",4,"video-playback","true");
INSERT INTO properties VALUES ("",5,"video-playback","true");
INSERT INTO properties VALUES ("",6,"manufacturer","microsoft");
INSERT INTO properties VALUES ("",5,"manufacturer","apple");
INSERT INTO properties VALUES ("",4,"manufacturer","apple");
Now, here is what our data should look like.
mysql> select * from object;
+----+----------+-------------------+
| id | category | name |
+----+----------+-------------------+
| 1 | 1 | 98 CAMERO |
| 2 | 1 | 06 PRIUS |
| 3 | 2 | A House Someplace |
| 4 | 3 | iPod Touch |
| 5 | 3 | iPhone |
| 6 | 3 | Zune |
+----+----------+-------------------+
6 rows in set (0.00 sec)
mysql> select * from category;
+----+-------------+
| id | name |
+----+-------------+
| 1 | CAR |
| 2 | REALESTATE |
| 3 | ELECTRONICS |
+----+-------------+
3 rows in set (0.00 sec)
mysql> select * from properties;
+----+----------+----------------+----------------+
| id | objectid | property_key | property_value |
+----+----------+----------------+----------------+
| 1 | 1 | color | red |
| 2 | 1 | doors | 4 |
| 3 | 1 | engine | v6 |
| 4 | 2 | engine | electric |
| 5 | 2 | doors | 4 |
| 6 | 2 | color | blue |
| 7 | 6 | video-playback | true |
| 8 | 4 | video-playback | true |
| 9 | 5 | video-playback | true |
| 10 | 6 | manufacturer | microsoft |
| 11 | 5 | manufacturer | apple |
| 12 | 4 | manufacturer | apple |
+----+----------+----------------+----------------+
12 rows in set (0.00 sec)
You can add an unlimited number of categories, an unlimited number of properties, and an unlimited number of objects all into these tables.
Now to join them ALL together:
mysql> SELECT * FROM object
-> LEFT JOIN category ON object.category = category.id
-> LEFT JOIN properties ON properties.objectid = object.id;
+----+----------+-------------------+------+-------------+------+----------+----------------+----------------+
| id | category | name | id | name | id | objectid | property_key | property_value |
+----+----------+-------------------+------+-------------+------+----------+----------------+----------------+
| 1 | 1 | 98 CAMERO | 1 | CAR | 1 | 1 | color | red |
| 1 | 1 | 98 CAMERO | 1 | CAR | 2 | 1 | doors | 4 |
| 1 | 1 | 98 CAMERO | 1 | CAR | 3 | 1 | engine | v6 |
| 2 | 1 | 06 PRIUS | 1 | CAR | 4 | 2 | engine | electric |
| 2 | 1 | 06 PRIUS | 1 | CAR | 5 | 2 | doors | 4 |
| 2 | 1 | 06 PRIUS | 1 | CAR | 6 | 2 | color | blue |
| 3 | 2 | A House Someplace | 2 | REALESTATE | NULL | NULL | NULL | NULL |
| 4 | 3 | iPod Touch | 3 | ELECTRONICS | 8 | 4 | video-playback | true |
| 4 | 3 | iPod Touch | 3 | ELECTRONICS | 12 | 4 | manufacturer | apple |
| 5 | 3 | iPhone | 3 | ELECTRONICS | 9 | 5 | video-playback | true |
| 5 | 3 | iPhone | 3 | ELECTRONICS | 11 | 5 | manufacturer | apple |
| 6 | 3 | Zune | 3 | ELECTRONICS | 7 | 6 | video-playback | true |
| 6 | 3 | Zune | 3 | ELECTRONICS | 10 | 6 | manufacturer | microsoft |
+----+----------+-------------------+------+-------------+------+----------+----------------+----------------+
13 rows in set (0.00 sec)
I hope this is what you are looking for, its an infinitely scalable solution, though if the database becomes incredibly taxed with a heavy workload, it could slow down, just due to its design.