MySQL JOIN Three Tables Using Row Values of A Table - mysql

This got complicated really quickly and I'm beginning to question the database design.
The basic concept of the application is:
User accounts
Features
Access levels
So, users have different access levels for each of the features. Fairly basic and common application I would think.
Schema:
CREATE TABLE `user_accounts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
`user_fname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_lname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_group` varchar(32) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Default',
PRIMARY KEY (`id`),
UNIQUE KEY `user_login` (`user_login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
INSERT INTO `user_accounts` VALUES(1, 'email#example.com', 'secret', 'Example', 'Name', 'Admin');
INSERT INTO `user_accounts` VALUES(2, 'john#example.com', 'secret', 'John', 'Doe', 'Trainer');
INSERT INTO `user_accounts` VALUES(3, 'jane#example.com', 'secret', 'Jane', 'Doe', 'Default');
CREATE TABLE `user_access_meta` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `user_access_meta` VALUES(1, 'type_1');
INSERT INTO `user_access_meta` VALUES(2, 'type_2');
INSERT INTO `user_access_meta` VALUES(3, 'type_3');
INSERT INTO `user_access_meta` VALUES(4, 'type_4');
INSERT INTO `user_access_meta` VALUES(5, 'type_5');
INSERT INTO `user_access_meta` VALUES(6, 'type_6');
CREATE TABLE `user_access_levels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`level` int(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_login_2` (`user_login`,`type`),
KEY `user_login` (`user_login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
INSERT INTO `user_access_levels` VALUES(1, 'email#example.com', 'type_1', 1);
INSERT INTO `user_access_levels` VALUES(2, 'email#example.com', 'type_2', 1);
INSERT INTO `user_access_levels` VALUES(3, 'email#example.com', 'type_3', 0);
INSERT INTO `user_access_levels` VALUES(4, 'email#example.com', 'type_5', 2);
INSERT INTO `user_access_levels` VALUES(5, 'john#example.com', 'type_2', 1);
INSERT INTO `user_access_levels` VALUES(6, 'john#example.com', 'type_3', 1);
INSERT INTO `user_access_levels` VALUES(7, 'john#example.com', 'type_5', 3);
INSERT INTO `user_access_levels` VALUES(8, 'jane#example.com', 'type_4', 1);
These tables actually have a lot more fields and have foreign key constraints between them, but I've striped them down for this example. They are also used individually for other purposes.
I've successfully been able to join all three tables together for a single user with this:
SELECT
ua.`user_fname`,
uam.`type`,
ual.`level`
FROM `user_access_meta` uam
LEFT JOIN `user_access_levels` ual
ON ual.`user_login` = 'email#example.com'
AND uam.`type` = ual.`type`
JOIN `user_accounts` ua
ON ua.`user_login` = 'email#example.com';
Output:
| USER_FNAME | TYPE | LEVEL |
--------------------------------
| Example | type_1 | 1 |
| Example | type_2 | 1 |
| Example | type_3 | 0 |
| Example | type_4 | (null) |
| Example | type_5 | 2 |
| Example | type_6 | (null) |
Even this isn't ideal, but It's all I could come up with and it serves it's purpose.
Now, what I need to do is select all users including their access levels. It would look something like this:
| USER_FNAME | type_1 | type_2 | type_3 | type_4 | type_5 | type_6 |
--------------------------------------------------------------------------
| Example | 1 | 1 | 0 | (null) | 2 | (null) |
| John | (null) | 1 | 1 | (null) | 3 | (null) |
| Jane | (null) | (null) | (null) | 1 | (null) | (null) |
I feel this may not have been the best design, but the reason I went with this design is so that I can easily add and remove features or even temporarily disable them individually.
Should the design be rethought? Is it even possible to get the results I'm looking for with this design?
I've put this up on SQL Fiddle. http://sqlfiddle.com/#!2/bb313/2/0

I have a few suggestions on both your table design and then how to get the data in the format that you want.
First on the database design, the change I would advise is in the table user_access_levels. Alter you table to the following:
CREATE TABLE `user_access_levels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`type_id` int(11) NOT NULL,
`level` int(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id_2` (`user_id`,`type_id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
There is no need to store the user_login and type in this table when you can just store the user_id and the type_id. Use both of these as foreign keys to their respective tables.
Then to get the data in format that you want. MySQL does not have a PIVOT function so you will want to use a CASE statement with an aggregate function.
select ua.user_fname,
MIN(CASE WHEN uam.type = 'type_1' THEN ual.level END) type_1,
MIN(CASE WHEN uam.type = 'type_2' THEN ual.level END) type_2,
MIN(CASE WHEN uam.type = 'type_3' THEN ual.level END) type_3,
MIN(CASE WHEN uam.type = 'type_4' THEN ual.level END) type_4,
MIN(CASE WHEN uam.type = 'type_5' THEN ual.level END) type_5,
MIN(CASE WHEN uam.type = 'type_6' THEN ual.level END) type_6
FROM user_accounts ua
LEFT JOIN user_access_levels ual
ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
ON ual.type_id = uam.id
group by ua.user_fname
See a SQL Fiddle with a Demo
This version will work if you know ahead of time the type columns that you want to get the values for. But if it is unknown, then you can use prepared statements to generate this dynamically.
Here is a version of the query using prepared statements:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MIN(case when type = ''',
type,
''' then level end) AS ',
replace(type, ' ', '')
)
) INTO #sql
FROM user_access_meta;
SET #sql = CONCAT('SELECT ua.user_fname, ', #sql, ' FROM user_accounts ua
LEFT JOIN user_access_levels ual
ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
ON ual.type_id = uam.id
group by ua.user_fname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See a SQL Fiddle with Demo

While I'm not familiar with the specifics of MySQL, it seems to me you are describing a pretty fundamental example of a pivot table query. What you're looking for seems reasonable to me, so I don't think based on what you've shown here I'd get too concerned about revisiting the data model. You may find putting the "level" back with the "type" table, based on the ol' saw "Normalize til hit hurts, denormalize til it works :)"
Just my $0.02. Good luck.

I typically use a BIGINT column and use bit masking to set the values.
For example level1 = 2, level2=4, level3=8, level4=16, etc..
Give someone level1 and level2 access:
update user set access_level = 2 & 4
does someone have level2 access?
select 1 from user where access_level | 2 AND user_id = ?

Related

Count clause -> incorrect count value

We have an issue using a counting combination with inner/left join that we cannot figure out how to solve.
We would appreciate any help on the matter!
We have 4 tables in the example:
1: providers: Including 2 providers
2: providers_categories: Including 2 categories. 1 provider can be in multiple categories (this seems to be causing the issue)
3: connections_providers: connecting the providers to the categories
4: reviews_providers: currently we have included 1 rating per provider
Goal: to output the review count from the table reviews_providers.
Issue: Provider 2 is included in 2 categories. The review count is doubled: 1 count for each provider category: A total of 2 reviews are printed even though only 1 entry exists.
Thank you!
Code:
SELECT prov.id, prov.title, prov_cat.title AS category, AVG(reviews.rating) AS rating, COUNT(reviews.rating) AS count
FROM connections_providers_categories conn
INNER JOIN providers_categories prov_cat
ON prov_cat.id = conn.category_id
LEFT JOIN reviews_providers reviews
ON reviews.provider_id = conn.provider_id
INNER JOIN providers prov
ON prov.id = conn.provider_id
GROUP BY prov.id
ORDER BY prov.title ASC
CREATE TABLE `connections_providers_categories` (
`provider_id` int(4) UNSIGNED NOT NULL,
`category_id` int(4) UNSIGNED NOT NULL
) ENGINE=MyISAM DEFAULT;
INSERT INTO `connections_providers_categories` (`provider_id`, `category_id`) VALUES
(1, 1),
(2, 1),
(2, 2);
CREATE TABLE `providers` (
`id` int(4) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT;
INSERT INTO `providers` (`id`, `title`) VALUES
(1, 'Provider 1'),
(2, 'Provider 2');
CREATE TABLE `providers_categories` (
`id` int(4) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` varchar(60) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT;
INSERT INTO `providers_categories` (`id`, `title`) VALUES
(1, 'Category 1'),
(2, 'Category 2');
CREATE TABLE `reviews_providers` (
`id` int(4) UNSIGNED NOT NULL AUTO_INCREMENT,
`provider_id` int(4) UNSIGNED NOT NULL,
`rating` enum('1','2','3','4','5') DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT;
INSERT INTO `reviews_providers` (`id`, `provider_id`, `rating`) VALUES
(1, 2, '5'),
(2, 1, '3');
Our question might resemble the following question, but we do not find the answer / see that it is the same case even thought both questions include multiple counts: count is multiplied after adding left join
It seems we might need a subquery, but we are not sure how to do this.
Any suggestions?
Thanks!
you can use subquery top get your result
SELECT prov.id, prov.title, GROUP_CONCAT(prov_cat.title) AS category, reviews.rating , reviews.count
FROM connections_providers_categories conn
INNER JOIN providers_categories prov_cat
ON prov_cat.id = conn.category_id
LEFT JOIN (SELECT provider_id, AVG(rating) AS rating, COUNT(provider_id) AS count FROM reviews_providers GROUP BY provider_id) reviews
ON reviews.provider_id = conn.provider_id
INNER JOIN providers prov
ON prov.id = conn.provider_id
GROUP BY prov.id,prov.title
ORDER BY prov.title ASC
id | title | category | rating | count
-: | :--------- | :-------------------- | -----: | ----:
1 | Provider 1 | Category 1 | 3 | 1
2 | Provider 2 | Category 2,Category 1 | 5 | 1
db<>fiddle here

MYSQL full join 4 tables using a reference table

I need some help building a SQL to fetch something like a "FULL OUTER JOIN" over four tables. I have this structure and cannot really modify much on it, cause its a already in use database:
-- ----------------------------
-- Table structure for article
-- ----------------------------
CREATE TABLE `article` (
`ID` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`Name` varchar(255) NULL DEFAULT NULL,
PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3;
INSERT INTO `article` VALUES (1, 'Coffeemaker');
INSERT INTO `article` VALUES (2, 'Toaster');
-- ----------------------------
-- Table structure for language
-- ----------------------------
CREATE TABLE `language` (
`ID` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`Name` varchar(255) NULL DEFAULT NULL,
PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3;
INSERT INTO `language` VALUES (1, 'German');
INSERT INTO `language` VALUES (2, 'English');
-- ----------------------------
-- Table structure for property
-- ----------------------------
CREATE TABLE `property` (
`ID` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`Name` varchar(255) NULL DEFAULT NULL,
PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3;
INSERT INTO `property` VALUES (1, 'DescriptionText');
INSERT INTO `property` VALUES (2, 'EAN-Code');
-- ----------------------------
-- Table structure for data
-- ----------------------------
CREATE TABLE `data` (
`ArticleID` int(10) UNSIGNED NOT NULL,
`PropertyID` int(10) UNSIGNED NOT NULL,
`LanguageID` int(10) UNSIGNED NOT NULL,
`Value` varchar(255) NULL DEFAULT NULL,
PRIMARY KEY (`ArticleID`, `PropertyID`, `LanguageID`) USING BTREE,
CONSTRAINT `FK_ArticleID` FOREIGN KEY (`ArticleID`) REFERENCES `article` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `FK_LanguageID` FOREIGN KEY (`LanguageID`) REFERENCES `language` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `FK_PropertyID` FOREIGN KEY (`PropertyID`) REFERENCES `property` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB;
INSERT INTO `data` VALUES (1, 1, 1, 'Eine Kaffemaschine');
INSERT INTO `data` VALUES (2, 1, 2, 'A toaster');
SQL: http://sqlfiddle.com/#!9/91dc8/1
What i want to get is a new VIEW which contains a join over all entity-tables showing a row for all articles, all properties and all languages but using the already existing data if available or null if not.
Is it possible? How would the SQL look like?
You seem to be after this...
SELECT a.id articleid
, p.id propertyid
, l.id languageid
, d.value
FROM article a
CROSS -- optional keyword
JOIN property p
CROSS -- optional keyword
JOIN language l
LEFT -- not optional
JOIN data d
ON d.articleid = a.id
AND d.propertyid = p.id
AND d.languageid = l.id;
+-----------+------------+------------+--------------------+
| articleid | propertyid | languageid | value |
+-----------+------------+------------+--------------------+
| 1 | 1 | 1 | Eine Kaffemaschine |
| 2 | 1 | 1 | NULL |
| 1 | 2 | 1 | NULL |
| 2 | 2 | 1 | NULL |
| 1 | 1 | 2 | NULL |
| 2 | 1 | 2 | A toaster |
| 1 | 2 | 2 | NULL |
| 2 | 2 | 2 | NULL |
+-----------+------------+------------+--------------------+
If you want all articles, you don't want a "full join". You want a left join that starts with the articles table. Further, you don't even need that, because all your articles have values in data.
But the question does specify all articles, so:
SELECT a.*, p.*, l.*, d.*
FROM article a LEFT JOIN
data d
ON d.ArticleID = a.ID LEFT JOIN
property p
ON d.PropertyID = p.id LEFT JOIN
language l
ON d.LanguageID = l.id;
I'm not sure what you mean by all articles and all languages (I missed the second part when I first read the question). If you want all combinations then:
SELECT a.*, p.*, l.*, d.*
FROM article a CROSS JOIN
languages l LEFT JOIN
data d
ON d.ArticleID = a.ID AND
d.LanguageID = l.ID LEFT JOIN
property p
ON d.PropertyID = p.id ;

MySQL Bug LIMIT in JOIN-Query change values in records

in the following scenario:
CREATE TABLE `table1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`text1` varchar(29) NOT NULL,
`flag` bit(1) DEFAULT NULL,
`reference` int(11) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE ,
UNIQUE KEY `idx_text` (`text1`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `table1` (id, text1, flag, reference) VALUES
(31486, 'YWXH-D6N4-XXH6', 0, NULL),
(31487, 'CBH0-UJBC-MFTO', 0, NULL),
(31488, 'FRQM-E6MW-6VFE', 1, 1657),
(31489, 'LZOS-EYDT-1BBF', 0, NULL),
(31490, 'D1XQ-YKAX-XQRC', 0, NULL);
CREATE TABLE `table2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value1` int(11) NOT NULL,
`value2` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `value1` (`value1`),
KEY `value2` (`value2`)
) ENGINE=MyISAM AUTO_INCREMENT=20068 DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED;
INSERT INTO table2 (id, value1, value2) VALUES
(1657, 1891, 1748);
-- the tables are shortened from "real" tables, i used SHOW CREATE <table> to create this script.
are the results of the following queries different.
here mysql returns for the record with id 31488 and 31490 the wrong value for the bit-field:
Query 1:
SELECT m.id, m.text1, m.flag, m.reference
FROM table1 AS m LEFT JOIN table2 AS v ON v.id = m.reference
GROUP BY m.text1 ORDER BY m.text1 DESC LIMIT 0, 5;
returns the correct result:
id | text1 | flag | reference
31487 | CBH0-UJBC-MFTO | 0 | NULL
31490 | D1XQ-YKAX-XQRC | 0 | NULL
31488 | FRQM-E6MW-6VFE | 1 | 1657
31489 | LZOS-EYDT-1BBF | 0 | NULL
31486 | YWXH-D6N4-XXH6 | 0 | NULL
while Query 2
SELECT m.id, m.text1, m.flag, m.reference
FROM table1 AS m LEFT JOIN table2 AS v ON v.id = m.reference
GROUP BY m.text1 ORDER BY m.text1 DESC LIMIT 0, 4;
returns this:
id | text1 | flag | reference
31487 | CBH0-UJBC-MFTO | 0 | NULL
31490 | D1XQ-YKAX-XQRC | 1 | NULL
31488 | FRQM-E6MW-6VFE | 0 | 1657
31489 | LZOS-EYDT-1BBF | 0 | NULL
so here is my question:
Im using Joomla CMS, and in the code of the component i can change the whole query except the LIMIT-part.
Joomla add the limit part to the query because of the pagination.
Is there a way to change the query that it works with the LIMIT-command?
oh, my MySQL-Version on Server is 5.1.61 (but this bug still exists on my client v5.5.16)
Your a) not inserting the data correctly - see the BIT data type docs and b) not selecting the data correctly - see the docs on the Bit-Field Literals
You need to insert using the following syntax
INSERT INTO `table1` (id, text1, flag, reference) VALUES
(31486, 'YWXH-D6N4-XXH6', b'0', NULL),
(31487, 'CBH0-UJBC-MFTO', b'0', NULL),
(31488, 'FRQM-E6MW-6VFE', b'1', 1657),
(31489, 'LZOS-EYDT-1BBF', b'0', NULL),
(31490, 'D1XQ-YKAX-XQRC', b'0', NULL);
Then select like this :
SELECT m.id, m.text1, bin(m.flag), m.reference
FROM table1 AS m LEFT JOIN table2 AS v ON v.id = m.reference
GROUP BY m.text1 ORDER BY m.text1 DESC LIMIT 0, 4;
Then it all works as expected

How to generate a dynamic sequence table in MySQL?

I'm trying to generate a sequence table in MySQL, so that I can get unique ids from last_insert_id.
The problem is that I need multiple sequences dynamically.
At the first, I created a table:
CREATE TABLE `sequence` (
`label` char(30) CHARACTER SET latin1 NOT NULL,
`id` mediumint(9) NOT NULL DEFAULT '0',
PRIMARY KEY (`label`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
And then tried to get the number, using example from http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_last-insert-id
UPDATE sequence SET id = LAST_INSERT_ID(id + 1) WHERE label = 'test';
SELECT LAST_INSERT_ID();
After a while I realized that I also need to generate rows for new labels safely.
So I changed this schema into:
CREATE TABLE `sequence` (
`label` char(30) CHARACTER SET latin1 NOT NULL,
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`label`,`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
And I simply gave up using WHERE clause to update its id.
INSERT INTO sequence (label) values ( ? )
SELECT LAST_INSERT_ID()
Is this a proper way? I want to know if there is a better solution.
The MyISAM engine will do it for you -
Table definition:
CREATE TABLE `sequence` (
`label` char(30) CHARACTER SET latin1 NOT NULL,
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`label`,`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Populate table:
INSERT INTO sequence VALUES ('a', NULL); -- add some 'a' labels
INSERT INTO sequence VALUES ('a', NULL);
INSERT INTO sequence VALUES ('a', NULL);
INSERT INTO sequence VALUES ('b', NULL); -- add another labels 'b'
INSERT INTO sequence VALUES ('b', NULL);
INSERT INTO sequence VALUES ('a', NULL); -- add some 'a' labels
INSERT INTO sequence VALUES ('a', NULL);
Show result:
SELECT * FROM sequence;
+-------+----+
| label | id |
+-------+----+
| a | 1 |
| a | 2 |
| a | 3 |
| a | 4 |
| a | 5 |
| a | 6 |
| b | 1 |
| b | 2 |
+-------+----+

MySQL filter query with relation

I'm having the following problem with 2 MySQL tables that have a relation:
I can easily query table 1 (address) when I want a full list or filter the result by name or email or such. But now I need to query table 1 and filter it based on the relational content of table 2 (interests). So, I need to find a row (usually many rows) in table 1 only if a (or more) conditions are met in table 2.
Here are the tables:
CREATE TABLE IF NOT EXISTS `address` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`email` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`countryCode` char(2) COLLATE utf8_unicode_ci DEFAULT NULL,
`languageCode` char(2) COLLATE utf8_unicode_ci DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `emailUnique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `address` (`id`, `name`, `email`, `countryCode`, `languageCode`, `timestamp`) VALUES
(1, '', 'dummy#test.com', 'BE', 'nl', '2010-07-16 14:07:00'),
(2, '', 'test#somewhere.com', 'BE', 'fr', '2010-07-16 14:10:25');
CREATE TABLE IF NOT EXISTS `interests` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`address_id` int(11) unsigned NOT NULL,
`cat` char(2) COLLATE utf8_unicode_ci NOT NULL,
`subcat` char(2) COLLATE utf8_unicode_ci NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `address_id` (`address_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `interests` (`id`, `address_id`, `cat`, `subcat`, `timestamp`) VALUES
(1, 1, 'aa', 'xx', '2010-07-16 14:07:00'),
(2, 1, 'aa', 'yy', '2010-07-16 14:07:00'),
(3, 2, 'aa', 'xx', '2010-07-16 14:07:00'),
(4, 2, 'bb', 'zz', '2010-07-16 14:07:00')
(5, 2, 'aa', 'yy', '2010-07-16 14:07:00');
ALTER TABLE `interests`
ADD CONSTRAINT `interests_ibfk_1` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
For example, I need to find the address(es) that has (have) as interest cat=aa and subcat=xx. Or, another example, I need the address(es) with as interest both cat=aa and subcat=xx AND cat=aa and subcat=yy. Specially the latter is important and one has to keep in mind that both the address and the interest tables will be long lists and that the amount of cat/subcat combinations will vary. I'm working with reference queries through Zend_Db_Table (findDependentRowset) at the moment but that solution is way to slow for address lists numbering 100s and even 1000s of hits.
Thank you for your help.
SELECT a.name FROM address a
INNER JOIN interests i ON (a.id = i.address_id)
WHERE i.cat = "aa" AND i.subcat IN ('xx', 'yy')
I added another row in your interests table, to demonstrate a different result set between the two examples:
INSERT INTO interests VALUES (6, 2, 'aa', 'vv', '2010-07-16 14:07:00');
Then you may want to try using correlated subqueries as follows:
SELECT *
FROM address a
WHERE EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'xx'));
Result:
+----+------+--------------------+-------------+--------------+---------------------+
| id | name | email | countryCode | languageCode | timestamp |
+----+------+--------------------+-------------+--------------+---------------------+
| 1 | | dummy#test.com | BE | nl | 2010-07-16 14:07:00 |
| 2 | | test#somewhere.com | BE | fr | 2010-07-16 14:10:25 |
+----+------+--------------------+-------------+--------------+---------------------+
2 rows in set (0.00 sec)
For the second example, we're testing for the new row added previously in order not to have the same result as above:
SELECT *
FROM address a
WHERE EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'xx')) AND
EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'vv'));
Result:
+----+------+--------------------+-------------+--------------+---------------------+
| id | name | email | countryCode | languageCode | timestamp |
+----+------+--------------------+-------------+--------------+---------------------+
| 2 | | test#somewhere.com | BE | fr | 2010-07-16 14:10:25 |
+----+------+--------------------+-------------+--------------+---------------------+
1 row in set (0.00 sec)
Using correlated subqueries is easy and straightforward. However keep in mind that it might not be the best in terms of performance, because the correlated subqueries will be executed once for each address in the outer query.