How can i optimize this mysql query in social table structure? - mysql

Scheme
CREATE TABLE IF NOT EXISTS `content` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
UNIQUE KEY `insert_at` (`insert_at`),
KEY `fk_entity` (`entity_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_comment` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`),
KEY `fk_user` (`user_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_like` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`),
KEY `fk_user` (`user_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_share` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
`share_type` int(2) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `entity_view` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`entity_uid` int(11) NOT NULL,
`user_uid` int(11) NOT NULL,
....
PRIMARY KEY (`uid`),
KEY `fk_entity` (`entity_uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(30) NOT NULL,
....
PRIMARY KEY (`uid`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query 1 - using Left Join [Query took 16.3032 sec]
SELECT c . * , COUNT(DISTINCT ev.uid) AS view_count, COUNT( DISTINCT el.uid ) AS like_count, COUNT( DISTINCT ec.uid ) AS reply_count, COUNT( DISTINCT es.uid ) AS share_count
FROM content AS c
LEFT JOIN entity_view AS ev ON ev.entity_uid = c.entity_uid
LEFT JOIN entity_like AS el ON el.entity_uid = c.entity_uid
LEFT JOIN entity_share AS es ON es.entity_uid = c.entity_uid
LEFT JOIN entity_comment AS ec ON ec.entity_uid = c.entity_uid
GROUP BY c.uid
EDIT - Explain
Query 2 - using Sub query [Query took 0.0069 sec]
SELECT c.*,
(SELECT COUNT(*) FROM entity_view WHERE entity_uid = c.entity_uid) AS view_count ,
(SELECT COUNT(*) FROM entity_like WHERE entity_uid = c.entity_uid) AS like_count ,
(SELECT COUNT(*) FROM entity_comment WHERE entity_uid = c.entity_uid) AS reply_count ,
(SELECT COUNT(*) FROM entity_share WHERE entity_uid = c.entity_uid) AS share_count
FROM content AS c
EDIT - Explain
Result
uid | data of content | view_count | like_count | reply_count | share_count |
-----------------------------------------------------------------------------
1 | ..... | 100 | 10 | 5 | 6 |
-----------------------------------------------------------------------------
2 | ..... | 200 | 20 | 20 | 3 |
-----------------------------------------------------------------------------
3 | ..... | 300 | 10 | 10 | 2 |
-----------------------------------------------------------------------------
Explain
Storage Engine : InnoDB
entity_{action} : Insert occurs when user {action} occurs.(e.g) entity_view is insertion occurs when user sees the content.
Question
How can I optimize more in the above mysql query?
I run the query in two ways and got the results above.
This proved that subquery is much better.
Is there a way to a get better performance than subquery like this table structure? And why is left join so bad?

Related

How to count correctly 2 inner levels count?

I have a structure for a university DB wherein I have three tables: room, students, possessions
CREATE TABLE `rooms` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name` (`name`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=3
;
CREATE TABLE `students` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`ref_room_id` INT(10) NULL DEFAULT NULL,
`student_name` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
PRIMARY KEY (`id`) USING BTREE,
INDEX `FK_students_room` (`ref_room_id`) USING BTREE,
CONSTRAINT `FK_students_room` FOREIGN KEY (`ref_room_id`) REFERENCES `university`.`rooms` (`id`) ON UPDATE CASCADE ON DELETE SET NULL
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=6
;
CREATE TABLE `possessions` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`ref_student_id` INT(10) NOT NULL,
`name` VARCHAR(50) NOT NULL DEFAULT '' COLLATE 'utf8_general_ci',
PRIMARY KEY (`id`) USING BTREE,
INDEX `FK__students` (`ref_student_id`) USING BTREE,
CONSTRAINT `FK__students` FOREIGN KEY (`ref_student_id`) REFERENCES `university`.`students` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=4
;
INSERT INTO rooms (name) VALUES ('a'),('b');
INSERT INTO students (`ref_room_id`, `student_name`) VALUES
(3,1),
(3,2),
(3,3),
(3,4);
INSERT INTO possessions (ref_student_id, name) VALUES
(7,'a')
(7,'aa'),
(7,'aaa'),
(8,'aaaa'),
(9,'aaaaa'),
(6,'aaaaaa'),
(7,'aaaaaaa');
So, in order to present such a table in MySQL, I created the procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `get_data`()
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
SELECT r.id AS room_id, r.name, COUNT(s.id) AS student_num, COUNT(p.id) AS possessions_num
FROM rooms r
INNER JOIN students s ON s.ref_room_id = r.id
INNER JOIN possessions p ON p.ref_student_id = s.id
GROUP BY r.name;
END
but what I get is
First of all, it misses the room and secondly instead of 4 students it shows 7...
What am I doing wrong?
Consider the following (and note that my data set may vary slightly from yours...)
DROP TABLE IF EXISTS rooms;
CREATE TABLE `rooms` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL ,
PRIMARY KEY (`id`)
);
DROP TABLE IF EXISTS students;
CREATE TABLE `students` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`ref_room_id` INT(10) NULL DEFAULT NULL,
`student_name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
);
DROP TABLE IF EXISTS possessions;
CREATE TABLE `possessions` (
`id` INT NOT NULL AUTO_INCREMENT,
`ref_student_id` INT NOT NULL,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO rooms VALUES (1,'a'),(2,'b');
INSERT INTO students VALUES
(11,1,1),
(12,1,2),
(13,1,3),
(14,1,4);
INSERT INTO possessions VALUES
(101,11,'apple'),
(102,11,'banana'),
(103,11,'cherry'),
(104,12,'date'),
(105,15,'elderberry'),
(106,13,'fig'),
(107,14,'huckleberry');
SELECT r.*
, COUNT(DISTINCT s.id) total_students
, COUNT(DISTINCT p.id) total_possessions
FROM rooms r
JOIN students s
ON s.ref_room_id = r.id
JOIN possessions p
ON p.ref_student_id = s.id
GROUP
BY r.id;
+----+------+----------------+-------------------+
| id | name | total_students | total_possessions |
+----+------+----------------+-------------------+
| 1 | a | 4 | 6 |
+----+------+----------------+-------------------+
Or...
SELECT r.*
, COUNT(DISTINCT s.id) total_students
, COUNT(DISTINCT p.id) total_possessions
FROM rooms r
LEFT
JOIN students s
ON s.ref_room_id = r.id
LEFT
JOIN possessions p
ON p.ref_student_id = s.id
GROUP
BY r.id;
+----+------+----------------+-------------------+
| id | name | total_students | total_possessions |
+----+------+----------------+-------------------+
| 1 | a | 4 | 6 |
| 2 | b | 0 | 0 |
+----+------+----------------+-------------------+

How do I improve performance on a DISTINCT select across three joined tables?

I have the following tables in question:
Personas
ImpressionsPersonas [join table - Personas ManyToMany Impressions]
Impressions
My query looks like this, the EXPLAIN results are attached below:
SELECT
DISTINCT (Personas.id),
Personas.parent_id,
Personas.persona,
Personas.subpersonas_count,
Personas.is_subpersona,
Personas.impressions_count,
Personas.created,
Personas.modified
FROM personas as Personas
INNER JOIN
impressions_personas ImpressionsPersonas ON (
Personas.id = ImpressionsPersonas.persona_id
)
inner JOIN impressions Impressions ON (Impressions.id = ImpressionsPersonas.impression_id AND Impressions.timestamp >= "2016-06-01 00:00:00" AND Impressions.timestamp <= "2016-07-31 00:00:00")
EXPLAIN
+----+-------------+---------------------+--------+-----------------------------------------------------------------------+-------------+---------+---------------------------------------------+------+----------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------------------+--------+-----------------------------------------------------------------------+-------------+---------+---------------------------------------------+------+----------+-----------------------+
| 1 | SIMPLE | Personas | ALL | PRIMARY | NULL | NULL | NULL | 159 | 100.00 | Using temporary |
| 1 | SIMPLE | ImpressionsPersonas | ref | impression_idx,persona_idx,comp_imp_persona,comp_imp_pri,comp_per_pri | persona_idx | 8 | gen1_d2go.Personas.id | 396 | 100.00 | Distinct |
| 1 | SIMPLE | Impressions | eq_ref | PRIMARY,timestamp,timestamp_id | PRIMARY | 8 | gen1_d2go.ImpressionsPersonas.impression_id | 1 | 100.00 | Using where; Distinct |
+----+-------------+---------------------+--------+-----------------------------------------------------------------------+-------------+---------+---------------------------------------------+------+----------+-----------------------+
3 rows in set, 1 warning (0.00 sec)
CREATE STATEMENT FOR PERSONAS
CREATE TABLE `personas` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) unsigned DEFAULT NULL,
`persona` varchar(150) NOT NULL,
`subpersonas_count` int(10) unsigned DEFAULT '0',
`is_subpersona` tinyint(1) unsigned DEFAULT '0',
`impressions_count` bigint(20) unsigned DEFAULT '0',
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `lookup` (`parent_id`,`persona`),
KEY `parent_index` (`parent_id`),
KEY `persona` (`persona`),
KEY `persona_a_id` (`id`,`persona`),
CONSTRAINT `self_referential_join_to_self` FOREIGN KEY (`parent_id`) REFERENCES `personas` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1049 DEFAULT CHARSET=utf8;
CREATE STATEMENT FOR IMPRESSIONS_PERSONAS
CREATE TABLE `impressions_personas` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`impression_id` bigint(20) unsigned NOT NULL,
`persona_id` bigint(20) unsigned NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `impression_idx` (`impression_id`),
KEY `persona_idx` (`persona_id`),
KEY `comp_imp_persona` (`impression_id`,`persona_id`),
KEY `comp_imp_pri` (`impression_id`,`id`),
KEY `comp_per_pri` (`persona_id`,`id`),
CONSTRAINT `impression` FOREIGN KEY (`impression_id`) REFERENCES `impressions` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `persona` FOREIGN KEY (`persona_id`) REFERENCES `personas` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=19387839 DEFAULT CHARSET=utf8;
CREATE STATEMENT FOR IMPRESSIONS
CREATE TABLE `impressions` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`device_id` bigint(20) unsigned NOT NULL,
`beacon_id` bigint(20) unsigned NOT NULL,
`zone_id` bigint(20) unsigned NOT NULL,
`application_id` bigint(20) unsigned DEFAULT NULL,
`timestamp` datetime NOT NULL,
`google_place_id` bigint(20) unsigned DEFAULT NULL,
`name` varchar(60) DEFAULT NULL,
`lat` decimal(15,10) DEFAULT NULL,
`lng` decimal(15,10) DEFAULT NULL,
`personas_count` int(10) unsigned DEFAULT '0',
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `device_idx` (`device_id`),
KEY `zone_idx` (`zone_id`),
KEY `beacon_id_idx2` (`beacon_id`),
KEY `timestamp` (`timestamp`),
KEY `appid_fk_idx_idx` (`application_id`),
KEY `comp_lookup` (`device_id`,`beacon_id`,`timestamp`),
KEY `timestamp_id` (`timestamp`,`id`),
CONSTRAINT `appid_fk_idx` FOREIGN KEY (`application_id`) REFERENCES `applications` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `beacon_id` FOREIGN KEY (`beacon_id`) REFERENCES `beacons` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `device2` FOREIGN KEY (`device_id`) REFERENCES `devices` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `zone_FK` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1582724 DEFAULT CHARSET=utf8;
Now - when I run the query without the DISTINCT and using a COUNT(*), it pulls about 17,000,000 records. Running it with DISTINCT yields 112 records. I am not sure why there are so many records showing up when the explain showed only 159 and 396.
Some information about the tables:
The Personas table contains 159 records. The ImpressionsPersonas table contains about 12.6 million, and Impressions contains about 920,000 records.
What we are doing is selecting the Personas table and joining to the Impressions by way of the join table ImpressionsPersonas. There are filters applied to the Impressions table (date in this case).
Note: removing the date filter had a negligible impact on the execution time - which hovers right around 120s. Is there a way to filter these records down to cut down the execution time of this query?
I presume that you want to get the list of persons who have at least 1 impression within a specified time period. To get this, you can use such a correlated sub-query:
SELECT
Personas.id,
Personas.parent_id,
Personas.persona,
Personas.subpersonas_count,
Personas.is_subpersona,
Personas.impressions_count,
Personas.created,
Personas.modified
FROM personas as Personas
WHERE EXISTS(SELECT 1 FROM impressions_personas
LEFT JOIN impressions Impressions ON
Impressions.id = ImpressionsPersonas.impression_id
WHERE Personas.id = ImpressionsPersonas.persona_id
AND Impressions.timestamp >= "2016-06-01 00:00:00"
AND Impressions.timestamp <= "2016-07-31 00:00:00"
)
Create an INDEX on timestamp column of impressions table. And see if improved else try using the created index in the query(forcing index).
UPDATE
Use INDEX in the JOIN
SELECT
DISTINCT (Personas.id),
Personas.parent_id,
Personas.persona,
Personas.subpersonas_count,
Personas.is_subpersona,
Personas.impressions_count,
Personas.created,
Personas.modified
FROM
personas as Personas
INNER JOIN
impressions_personas ImpressionsPersonas ON (
Personas.id = ImpressionsPersonas.persona_id
)
INNER JOIN
impressions Impressions WITH(INDEX(timestamp)) ON
(Impressions.id = ImpressionsPersonas.impression_id AND
Impressions.timestamp >= "2016-06-01 00:00:00" AND
Impressions.timestamp <= "2016-07-31 11:59:59")
At the moment you're first joining three tables with millions of rows and then using DISTINCT to get only a few rows out of it. Better way is to first get only the required IDs and then use those to select the actual result data.
For example:
SELECT column, other FROM personas
WHERE id IN
(SELECT distinct persona_id
FROM impressions_personas
INNER JOIN impressions Impressions
ON Impressions.id = ImpressionsPersonas.impression_id
AND Impressions.timestamp >= "2016-06-01 00:00:00"
AND Impressions.timestamp <= "2016-07-31 00:00:00"))
This way the engine will only handle a single column for the whole procedure until getting the results.

Enhancing performance when applying multiple joins in mysql

I have 3 tables:
Nutritions_facts table which is static table that have all nutrition nutrition_id and nutrition_ename
Products_info table which has products info and the columns are product_id, product_ename, brand
product_nutrition_facts
which is mediator table that links the product with its nutrition facts with their values . The structure is product_id, nutrition_id, nutrition_value .. Each product can have 1 row or more depending on number of nutrition facts it has.
Here is a real testing example
Nutrition_facts table
nutrition_id |nutrition_ename
1 | caloreis
2 | fat
3 | sugar
4 | salt
Products_info table
product_id| product_ename | brand
1 | Nutella Hazelnut Cocoa | Nutella
2 | Nutella Jar | Nutella
product_nutrition_facts table
product_id | nutrition_id | nutrition_value
1 | 1 | 200
1 | 2 | 15
1 | 3 | 2
1 | 4 | 11
2 | 1 | 200
2 | 2 | 15
2 | 3 | 12
2 | 4 | 11
I need to make query that returns me the products' name with value of sugar is less than or equla 15 and salt less than or equal 140
I build a query that return correct values but it takes long time to process. Can someone suggest edits to enhance the performance please..
SELECT DISTINCT p.product_id, p.brand, p.e_name, p.image_low
FROM products_info p
JOIN product_nutrition_facts pn ON p.product_id = pn.product_id
WHERE p.brand = 'AL MARAI'
AND (
(
p.product_id
IN (
SELECT product_id
FROM product_nutrition_facts pn
WHERE pn.nutrition_id =3
AND pn.nutrition_value <=15
)
)
AND (
p.product_id
IN (
SELECT product_id
FROM product_nutrition_facts pn
WHERE pn.nutrition_id =4
AND pn.nutrition_value <=140
)
)
)
AND (
pn.nutrition_id =3
OR pn.nutrition_id =4
)
EDITS
CREATE TABLE `products_info` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`image_low` varchar(400) DEFAULT NULL,
`e_name` varchar(200) DEFAULT NULL,
PRIMARY KEY (`product_id`),
UNIQUE KEY `product_id_UNIQUE` (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=249292 DEFAULT CHARSET=utf8
CREATE TABLE `product_nutrition_facts` (
`prod_nut_id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL,
`nutrition_id` int(11) DEFAULT NULL,
`nutrition_value` varchar(25) DEFAULT NULL,
`unit_id` int(11) DEFAULT NULL,
`parent` int(11) DEFAULT NULL,
`serving_size` varchar(145) DEFAULT NULL,
`serving_size_unit` int(11) DEFAULT NULL,
`no_nutrition_facts` int(11) NOT NULL,
`added_by` varchar(145) DEFAULT NULL,
`last_update` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`inserted_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_by` varchar(150) NOT NULL,
PRIMARY KEY (`prod_nut_id`),
UNIQUE KEY `prod_nut_id_UNIQUE` (`prod_nut_id`),
KEY `nutrition_id_fk_idx` (`nutrition_id`),
KEY `unit_Fk_idx` (`unit_id`),
KEY `unit_fk1_idx` (`serving_size_unit`),
KEY `product_idFK` (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=580809 DEFAULT CHARSET=utf8
CREATE TABLE `nutrition_facts` (
`nutrition_id` int(11) NOT NULL AUTO_INCREMENT,
`nutrition_aname` varchar(145) DEFAULT NULL,
`nutrition_ename` varchar(145) DEFAULT NULL,
`alternative_name` varchar(145) DEFAULT NULL,
`unit` varchar(8) NOT NULL,
`daily_value` int(11) NOT NULL,
`nut_order` int(2) NOT NULL,
`is_child` int(1) NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`nutrition_id`)
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8
Try to add indexes product_nutrition_facts (nutrition_id,nutrition_value,product_id), product_nutrition_facts (product_id,nutrition_id,nutrition_value), products_info (brand) and perfom query
SELECT p.*
FROM products_info p
join product_nutrition_facts pn1 on
pn1.product_id=p.product_id
AND pn1.nutrition_id=3
AND pn1.nutrition_value<=15
join product_nutrition_facts pn2 on
pn2.product_id=p.product_id
AND pn2.nutrition_id=4
AND pn2.nutrition_value<=140
where
p.brand = 'AL MARAI'
IN ( SELECT ... ) -- Does not optimize well; turn into JOIN (as Max suggests)
"Overnormalization" -- Nutrition_facts can be eliminated; simply use the nutrition_names in place of nutrition_id.

unique records in mysql one-to-many join without DISTINCT or GROUP BY

Here's the basic query:
SELECT
some_columns
FROM
d
JOIN
m ON d.id = m.d_id
JOIN
s ON s.id = m.s_id
JOIN
p ON p.id = s.p_id
WHERE
some_criteria
ORDER BY
d.date DESC
LIMIT 25
Table m can contain multiple s_ids per each d_id. Here's a super simple example:
+--------+--------+------+
| id | d_id | s_id |
+--------+--------+------+
| 317354 | 291220 | 642 |
| 317355 | 291220 | 32 |
+--------+--------+------+
2 rows in set (0.00 sec)
Which we want. But, obviously, it's producing duplicate d records in this particular query.
These tables have lots of columns, and I need to edit these down due to the sensitive nature of the data, but here's the basic structure as it pertains to this query:
| d | CREATE TABLE `d` (
`id` bigint(22) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `date` (`date`)
) ENGINE=InnoDB |
| m | CREATE TABLE `m` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`d_id` bigint(20) NOT NULL,
`s_id` bigint(20) NOT NULL,
`is_king` binary(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `d_id` (`d_id`),
KEY `is_king` (`is_king`),
KEY `s_id` (`s_id`)
) ENGINE=InnoDB |
| s | CREATE TABLE `s` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`p_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `p_id` (`p_id`)
) ENGINE=InnoDB |
| p | CREATE TABLE `p` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB |
Now, previously, we had a GROUP BY d.id in place to grab uniques. The data here are now huge, so we can no longer realistically do that. SELECT DISTINCT d.id is even slower.
Any ideas? Everything I come up with creates a problem elsewhere.
Does changing "JOIN m ON d.id = m.d_id" to "LEFT JOIN m ON d.id = m.d_id" accomplish what you're looking for here?
I'm not sure I understand your goal clearly, but "table m contains many rows per each d" immediately has me wondering if you should be using some other type of join to accomplish your ends.

BLOB data returned in MySQL using AES_DECRYPT with ORDER clause

I'm creating a system in which users can store messages via PHP with a MySQL database, and I am using the MySQL AES_ENCRYPT function to encrypt the contents of these messages.
Here is my posts table:
CREATE TABLE IF NOT EXISTS `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) DEFAULT NULL,
`group` int(11) DEFAULT NULL,
`body` varbinary(1000) NOT NULL,
`ip` varchar(45) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`replyto` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `replyto` (`replyto`),
KEY `user` (`user`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
ALTER TABLE `posts`
ADD CONSTRAINT `posts_ibfk_3` FOREIGN KEY (`replyto`) REFERENCES `posts` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
ADD CONSTRAINT `posts_ibfk_4` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
And my users table:
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(45) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`email` varchar(100) NOT NULL,
`name` varchar(100) NOT NULL,
`hash` varchar(128) NOT NULL,
`salt` varchar(32) NOT NULL,
`guid` varchar(36) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
The AES encryption key I'm using for the message body is a SHA-512-hashed concatenation between a prefix and suffix string, and the posting user's GUID in the middle. Thus, I have the following SQL query to select the most recent messages:
SELECT AES_DECRYPT(`posts`.`body`, SHA2(CONCAT('prefix',(`users`.`guid`),'suffix'),512)) AS 'realbody'
FROM `posts`
INNER JOIN `users` ON `posts`.`user` = `users`.`id`
ORDER BY `posts`.`id` DESC
Unfortunately, this does not return the decrypted messages, as you can see in the screenshot:
Instead, I'm getting this BLOB data. However, if I remove the ORDER BY clause from the query:
SELECT AES_DECRYPT(`posts`.`body`, SHA2(CONCAT('prefix',(`users`.`guid`),'suffix'),512)) AS 'realbody'
FROM `posts`
INNER JOIN `users` ON `posts`.`user` = `users`.`id`
Then suddenly, it works:
I really don't know what could be causing this. Does anybody have any ideas?
UPDATED CAST it to CHAR
SELECT `posts`.*, CAST(AES_DECRYPT(`posts`.`body`,SHA2(CONCAT('prefix',`users`.`guid`,'suffix'),512)) AS CHAR) as 'realbody'
FROM `posts` JOIN `users`
ON `posts`.`user` = `users`.`id`
ORDER BY `posts`.`id` DESC
Sample output:
| ID | USER | ... | REALBODY |
---...------------------------
| 2 | 2 | ... | Post 2 |
| 1 | 1 | ... | Post 1 |
Here is SQLFiddle demo