Reduce time of MySQL JOIN statement execution - php

I have a notifcation system like in Facebook using the following MySQL statement:
SELECT
n.`id`,n.`content_id`,n.`site_id`,n.`creator_uid`,n.`type`,
nu.`id` AS nuid, nu.`uid` AS nu_uid, nu.`date`,
nr.`id` AS nrid, nr.`uid` AS nr_uid, nr.`is_read`,
u.`gender`
FROM `notification` AS n
LEFT JOIN `notification_user` AS nu ON nu.`nid` = n.`id`
LEFT JOIN `notification_read` AS nr ON nr.`nid` = n.`id`
LEFT JOIN `users` AS u ON u.`id` = nu.`uid`
WHERE
nu.`uid` != '".$_SESSION['uid']."' AND nr.`uid` = '".$_SESSION['uid']."'
OR
(
nu.`uid` = '".$_SESSION['uid']."' AND n.`type` = 'credits'
)
ORDER BY date DESC, nu.`id` DESC
It should only display the notifications for this specific user I'm logged in as. But now I've more than 22500 records on the notification table and I'm always getting an "maximum execution time exceeded" error.
Can I change this query somehow to reduce the time getting the wanted records? Maybe remove the join and execute more queries?
EDIT: Added Table Overview
CREATE TABLE IF NOT EXISTS `notification` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`content_id` int(11) NOT NULL,
`site_id` int(11) NOT NULL,
`creator_uid` int(11) NOT NULL,
`type` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=22759 ;
.
CREATE TABLE IF NOT EXISTS `notification_read` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nid` int(11) NOT NULL,
`uid` int(11) NOT NULL,
`is_read` tinyint(4) NOT NULL,
PRIMARY KEY (`id`),
KEY `nid` (`nid`),
KEY `nid_2` (`nid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=45342 ;
.
CREATE TABLE IF NOT EXISTS `notification_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nid` int(11) NOT NULL,
`uid` int(11) NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=22813 ;

Splitting the statement up in to a pair of SELECTs, and UNIONing the results together:-
(SELECT
n.`id`,n.`content_id`,n.`site_id`,n.`creator_uid`,n.`type`,
nu.`id` AS nuid, nu.`uid` AS nu_uid, nu.`date`,
nr.`id` AS nrid, nr.`uid` AS nr_uid, nr.`is_read`,
u.`gender`
FROM `notification` AS n
INNER JOIN `notification_user` AS nu ON nu.`nid` = n.`id`
LEFT JOIN `notification_read` AS nr ON nr.`nid` = n.`id`
LEFT JOIN `users` AS u ON u.`id` = nu.`uid`
WHERE nu.`uid` = '".$_SESSION['uid']."' AND n.`type` = 'credits')
UNION
(SELECT
n.`id`,n.`content_id`,n.`site_id`,n.`creator_uid`,n.`type`,
nu.`id` AS nuid, nu.`uid` AS nu_uid, nu.`date`,
nr.`id` AS nrid, nr.`uid` AS nr_uid, nr.`is_read`,
u.`gender`
FROM `notification` AS n
LEFT JOIN `notification_user` AS nu ON nu.`nid` = n.`id`
INNER JOIN `notification_read` AS nr ON nr.`nid` = n.`id`
LEFT JOIN `users` AS u ON u.`id` = nu.`uid`
WHERE nu.`uid` != '".$_SESSION['uid']."' AND nr.`uid` = '".$_SESSION['uid']."')
ORDER BY date DESC, nu.`id` DESC
This should allow MySQL to use indexes effectively on each part of the query. The first part of the query requires a notification_user record so you can use an INNER JOIN there, while the 2nd requires a notification_read record so you can use an INNER JOIN there. Both of those should cut the number of rows to process.
Add an index on the uid field on the notification_user table
Add an index on the uid field on the notification_read table

Related

how can i join 3 tables in mysql php

I have 3 tables tags,tag_type,user_alert
table structure is as follows:
CREATE TABLE `tags` (
`tag_id` int(11) NOT NULL,
`tag_name` varchar(255) CHARACTER SET utf8 NOT NULL,
`tagtype_id` int(11) NOT NULL,
`status` tinyint(1) NOT NULL,
`is_deleted` tinyint(1) NOT NULL,
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
CREATE TABLE IF NOT EXISTS `tag_types` (
`tagtype_id` int(11) NOT NULL,
`tagtype_name_en` varchar(100) CHARACTER SET utf8 NOT NULL,
`tagtype_name_fr` varchar(100) NOT NULL,
`display_order` int(11) NOT NULL,
`tag_color` varchar(50) CHARACTER SET utf8 NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '1',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
CREATE TABLE IF NOT EXISTS `user_alert` (
`user_alert_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`is_removed` enum('yes','no') DEFAULT 'no',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
I have created query, but it is not working for me.
select t.tag_name,t.tag_id,ty.tag_color,ty.tagtype_name_en as tagtype_name from tags as t INNER JOIN tag_types as ty ON t.tagtype_id=ty.tagtype_id INNER JOIN user_alert as ua ON ua.tag_id != t.tag_id where t.tagtype_id='5' and ua.user_id != '14'
I want to make query such that
I am passing tagtype_id and user_id
when I pass tagtype_id I can get all tag which belongs to that tagtyped_id and don not want to select tags in which user has subscribed.
but I am not able to do it.I know the answer will be simple but don't know how to do it.can anybody please help me?
In JOIN, you must use "=" and dont use "!=".
your condition must apply in where statement NOT in join.
Maybe this is what you are looking for (untested as i don't have this DB)
select t.tag_name,
t.tag_id,
ty.tag_color,
ty.tagtype_name_en as tagtype_name
from tags as t
INNER JOIN user_alert as ua
ON ua.tag_id <> t.tag_id
INNER JOIN tag_types as ty
ON ua.tag_id = ty.tagtype_id
where t.tagtype_id = '5'
and ua.user_id <> '14
Assuming the Table definition you supplied here is accurate; the problem is that you don't have a tagtype_name_en Field in your "tag_types" table and you are trying to access it while it doesn't exist.
// YOUR TABLE DEFINITION/COLUMNS:::
TABLE 1 - tags:
tag_id
tag_name
tagtype_id
TABLE 2 - tag_types
tagtype_id
tag_color
tagtype_name_en
TABLE 3 - user_alert
tag_id
user_id
UPDATE: ADDED GROUP BY CLAUSE
$sql = "SELECT
t.tag_name, t.tag_id,
ty.tag_color,ty.tagtype_name_en AS tagtype_name
FROM tags AS t
LEFT JOIN tag_types AS ty ON t.tagtype_id=ty.tagtype_id
LEFT JOIN user_alert AS ua ON ua.tag_id!=t.tag_id
GROUP BY t.tag_id
WHERE t.tagtype_id='5' AND ua.user_id != '14'";
Why dont you try with this <> not equal to operator instead of !=
SELECT * FROM `tags` AS `t` INNER JOIN `tag_types` AS `ty` ON t.tagtype_id = ty.tagtype_id INNER JOIN `user_alert` AS `ua` ON ua.tag_id <> t.tag_id WHERE t.tagtype_id = '5' AND ua.user_id <> '14'
UPDATED
Try this "LEFT JOIN"
SELECT * FROM `tags` AS `t` INNER JOIN `tag_types` AS `ty` ON t.tagtype_id = ty.tagtype_id LEFT JOIN `user_alert` AS `ua` ON ua.tag_id = t.tag_id WHERE t.tagtype_id = '5' AND ua.user_id <> '14' AND ua.tag_id IS NULL

Display best selling items and associated image from another table

I would like to display the image of 4 best selling items on the index page but the sql I'm using does not work. I think the syntax is correct but doesn't show anything on the page. I have an order_items table and products table.
Products table
CREATE TABLE IF NOT EXISTS `products` (
`id` int(11) NOT NULL,
`date` datetime NOT NULL,
`category` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
`image` varchar(100) CHARACTER SET utf8 NOT NULL,
`description` text NOT NULL,
`author` varchar(100) NOT NULL,
`price` decimal(5,2) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=27 ;
Order_items table
CREATE TABLE IF NOT EXISTS `order_items` (
`id` int(11) NOT NULL,
`order` int(11) NOT NULL,
`product` int(11) NOT NULL,
`price` decimal(8,2) NOT NULL,
`qty` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=68 ;
NOTE: in order_items table the field product corresponds to the ID of the product.
Finally, this is the sql statement I'm using but doesn't show anything.
public function bestSellingItems(){
$sql = "SELECT * FROM products AS p
INNER JOIN order_items AS od ON p.id = od.id
GROUP BY p.id, SELECT SUM(od.qty) AS total
ORDER BY SUM(od.qty) DESC limit 4";
}
I would appreciate any help.
You said it your self, product correspond to id and you used p.id = od.id , also, I didn't understand the part of the second select which I believe should throw an error(no from..) so I adjusted it a little bit - select the sum, alias it and order by that alias.
public function bestSellingItems(){
$sql = "SELECT p.*,od.*,sum(od.qty) as sum_qty FROM products AS p
INNER JOIN order_items AS od ON p.id = od.product
GROUP BY p.id
ORDER BY sum_qty DESC limit 4";
}
your join "INNER JOIN order_items AS od ON p.id = od.id " should be INNER JOIN order_items AS od ON p.id = od.Product

MySQL Select rows where the user has not voted yet (contest app)

I have a photo contest app where users can vote. I would like to select all of the contests where the logged in user has not voted yet.
So I have two tables.
The "contest" table :
CREATE TABLE `contest` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`category_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`desc` text NOT NULL,
`created_date` datetime NOT NULL,
`started_date` datetime NOT NULL,
`nb_user_min` int(11) NOT NULL,
`nb_photo_max` int(11) NOT NULL,
`nb_photo_per_user` int(11) NOT NULL,
`duration` int(11) DEFAULT NULL,
`status` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
The "contest_vote" table :
CREATE TABLE `contest_vote` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`pic_id` int(11) DEFAULT NULL,
`contest_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`date` datetime DEFAULT NULL,
`ip` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
So to be clear, I want to get the number (or the list) of contests where the user has not voted yet. So I have tried with a LEFT JOIN but it doesn't return the good set of result. Here it the query I have until now :
SELECT DISTINCT c.id, c.title, cv.user_id
FROM contest c
LEFT JOIN contest_vote cv
ON cv.contest_id = c.id AND cv.user_id != ?
GROUP BY contest_id
("?" represents the user_id parameter).
Can you help me to solve this?
This is pretty simple do with a subquery. Just grab all contest without where user is voted like this:
SELECT DISTINCT c.id, c.title
FROM contest c
WHERE c.id NOT IN (SELECT DISTINCT cv.contest_id FROM contest_vote cv WHERE cv.user_id = ?)
Try this one :
SELECT DISTINCT c.id, c.title, cv.user_id
FROM contest c
LEFT JOIN contest_vote cv ON cv.contest_id = c.id AND cv.user_id != ? WHERE c.user_id = ?
GROUP BY contest_id

Users ranking table on how many likes/upvotes users had

I'm trying to create a ranking table based on how many likes/upvotes a user had on all his items in total. User in the upvotes table links to id of the user that made the like, but I think you don't need this.
Hopefully by giving these tables everything will get clear.
I think the trick here is to get all the upvotes by each item and merge them together towards a user this item was from to get a total likes for each user and then rank all the users based on this total. Of course doing this will probably be a slow query so I need a very performant way to handle this.
The hard thing is here mainly that the upvotes table doesn't include the user id.
3 tables:
CREATE TABLE `items` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`user_id` int(255) NOT NULL,
`img` varchar(500) NOT NULL,
`message` varchar(200) NOT NULL,
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`active` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=19 DEFAULT CHARSET=latin1;
CREATE TABLE `upvotes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(255) NOT NULL,
`item_id` int(255) NOT NULL,
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`password` binary(60) NOT NULL,
`first_name` varchar(255) NOT NULL,
`last_name` varchar(255) NOT NULL,
`active` int(1) NOT NULL DEFAULT '1',
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
I need a performant query giving me the ranking of each user ranked on how many likes they got on all their items?
I managed to write this:
SELECT #rank := #rank + 1 AS rank, m.*
FROM (SELECT
users.first_name as first_name,
users.last_name as last_name,
count(upvotes.item_id) as total
FROM upvotes
INNER JOIN users
ON users.id = (SELECT items.user_id FROM items WHERE items.id = upvotes.item_id LIMIT 1)
GROUP BY users.id
ORDER BY total DESC
) m, (SELECT #rank := 0) r
But I reckon this will be super slow when the database grows...
You can do a simple join query in order to get the total likes for each item of user and order your results with the resulting count in descending order
SELECT u.*,i.*,COUNT(DISTINCT up.user) `total_user_likes_item`
FROM users u
JOIN items i ON(i.user_id = u.id)
JOIN upvotes up ON(up.item_id = i.id)
GROUP BY u.id,i.id
ORDER BY u.id,i.id,total_user_likes_item DESC
Edit from comments For user total likes you remove i.id from group by as below query
SELECT u.*,COUNT(DISTINCT up.user) `total_user_likes_item`
FROM users u
JOIN items i ON(i.user_id = u.id)
JOIN upvotes up ON(up.item_id = i.id)
GROUP BY u.id
ORDER BY total_user_likes_item DESC
I'll try answer your question:
In table users you can add row sum_upvotes. Every time when someone get one like (vote) you will increment this column by:
UPDATE users
SET sum_upvotes = sum_upvotes + 1
;
Of course, you will insert a column in table upvotes.
Finally, you query to select users and order them by upvotes will look like this
SELECT first_name, last_name
FROM users
ORDER BY sum_upvotes
;
Hope this helps.

Mysql product filters with multiple options

for example I have a list o TVs, and each TV has some attributes like: Brand(Samsung,Sony etc..), Size(80cm, 116 cm etc), SmartTv(yes, no).
I have the following schema:
CREATE TABLE `products` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(150) NOT NULL,
)
CREATE TABLE `attributes` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(20) character set latin1 NOT NULL
)
CREATE TABLE `attributes_entity` (
`product_id` int(11) NOT NULL,
`attribute_id` int(11) NOT NULL,
`value_id` int(11) NOT NULL,
)
CREATE TABLE `attributes_values` (
`id` int(11) NOT NULL auto_increment,
`value` varchar(255) default NULL,
)
If i want all TV from samsung i say like this:
SELECT
`p`.`id`,
`p`.`name`
FROM `attributes_entity` `ae`
INNER JOIN `products` `p` ON `ae`.`product_id`=`p`.`id`
INNER JOIN `attributes` `a` ON `ae`.`attribute_id`=`a`.`id`
INNER JOIN `attributes_values` `av` ON `ae`.`value_id`=`av`.`id`
WHERE (`a`.`name`='samsung' AND `av`.`value`='samsung')
This is great but what if I want: All Samsung TVs that are smartTv:
SELECT
`p`.`id`,
`p`.`name`
FROM `attributes_entity` `ae`
INNER JOIN `products` `p` ON `ae`.`product_id`=`p`.`id`
INNER JOIN `attributes` `a` ON `ae`.`attribute_id`=`a`.`id`
INNER JOIN `attributes_values` `av` ON `ae`.`value_id`=`av`.`id`
WHERE (`a`.`name`='samsung' AND `av`.`value`='samsung')
//imposible query
and (`a`.`name`='smartv' AND `av`.`value`='yes')
How should i fix the query with multiple ANDs?
First idea, off the top of my head - try replacing your joins with an inner query, and count the number of matching attributes:
SELECT `p`.`id`, `p`.`name`
FROM `products` `p`
WHERE `p`.`id` IN (SELECT `ae`.`product_id`
FROM `attributes_entity` `ae`
INNER JOIN `attributes` `a` ON `ae`.`attribute_id`=`a`.`id`
INNER JOIN `attributes_values` `av` ON `ae`.`value_id`=`av`.`id`
WHERE ((`a`.`name`='samsung' AND `av`.`value`='samsung') OR
(`a`.`name`='smartv' AND `av`.`value`='yes'))
HAVING COUNT(*) >= 2 -- number of matching attributes required
);

Categories