MySQL slow query - sql

SELECT
items.item_id, items.category_id, items.title, items.description, items.quality,
items.type, items.status, items.price, items.posted, items.modified,
zip_code.state_prefix, zip_code.city, books.isbn13, books.isbn10, books.authors,
books.publisher
FROM
(
(
items
LEFT JOIN bookitems ON items.item_id = bookitems.item_id
)
LEFT JOIN books ON books.isbn13 = bookitems.isbn13
)
LEFT JOIN zip_code ON zip_code.zip_code = items.item_zip
WHERE items.rid = $rid`
I am running this query to get the list of a user's items and their location. The zip_code table has over 40k records and this might be the issue. It currently takes up to 15 seconds to return a list of about 20 items! What can I do to make this query more efficient?
UPDATE: The following is the table creation code for the relevant tables. Sorry about the formatting!
CREATE TABLE `bookitems` (
`bookitem_id` int(10) unsigned NOT NULL auto_increment COMMENT 'BookItem ID',
`item_id` int(10) unsigned NOT NULL default '0' COMMENT 'Item ID',
`isbn13` varchar(13) NOT NULL default '' COMMENT 'Book ISBN13',
`modified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Date of Last Modification',
PRIMARY KEY (`bookitem_id`),
UNIQUE KEY `item_id` (`item_id`),
KEY `fk_bookitems_isbn13` (`isbn13`),
CONSTRAINT `fk_bookitems_isbn13` FOREIGN KEY (`isbn13`) REFERENCES `books` (`isbn13`),
CONSTRAINT `fk_bookitems_item_id` FOREIGN KEY (`item_id`) REFERENCES `items` (`item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=latin1;
CREATE TABLE `books` (
`isbn13` varchar(13) NOT NULL default '' COMMENT 'Book ISBN13 (pk)',
`isbn10` varchar(10) NOT NULL default '' COMMENT 'Book ISBN10 (u)',
`title` text NOT NULL COMMENT 'Book Title',
`title_long` text NOT NULL,
`authors` text NOT NULL COMMENT 'Book Authors',
`publisher` text NOT NULL COMMENT 'ISBNdb publisher_text',
PRIMARY KEY (`isbn13`),
UNIQUE KEY `isbn10` (`isbn10`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `items` (
`item_id` int(10) unsigned NOT NULL auto_increment COMMENT 'Item ID',
`rid` int(10) unsigned NOT NULL default '0' COMMENT 'Item Owner User ID',
`category_id` int(10) unsigned NOT NULL default '0' COMMENT 'Item Category ID',
`title` tinytext NOT NULL COMMENT 'Item Title',
`description` text NOT NULL COMMENT 'Item Description',
`quality` enum('0','1','2','3','4','5') NOT NULL default '0' COMMENT 'Item Quality',
`type` enum('forsale','wanted','pending') NOT NULL default 'pending' COMMENT 'Item Status',
`price` int(6) unsigned NOT NULL default '0' COMMENT 'Price',
`posted` datetime NOT NULL default '0000-00-00 00:00:00' COMMENT 'Date of Listing',
`modified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT 'Date of Last Modification',
`status` enum('sold','found','flagged','removed','active','expired') NOT NULL default 'active',
`item_zip` int(5) unsigned zerofill NOT NULL default '00000',
PRIMARY KEY (`item_id`),
KEY `fk_items_rid` (`rid`),
KEY `fk_items_category_id` (`category_id`),
CONSTRAINT `fk_items_category_id` FOREIGN KEY (`category_id`) REFERENCES `categories` (`category_id`),
CONSTRAINT `fk_items_rid` FOREIGN KEY (`rid`) REFERENCES `users` (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`rid` int(10) unsigned NOT NULL auto_increment COMMENT 'User ID',
`fid` int(10) unsigned NOT NULL default '0' COMMENT 'Facebook User ID',
`role_id` int(10) unsigned NOT NULL default '4',
`zip` int(5) unsigned zerofill NOT NULL default '00000' COMMENT 'Zip Code',
`joined` timestamp NOT NULL default CURRENT_TIMESTAMP COMMENT 'INSERT Timestamp',
`email` varchar(255) NOT NULL default '',
`notes` varchar(255) NOT NULL default '',
PRIMARY KEY (`rid`),
UNIQUE KEY `fid` (`fid`),
KEY `fk_users_role` (`role_id`),
CONSTRAINT `fk_users_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1013 DEFAULT CHARSET=latin1;
CREATE TABLE `zip_code` (
`id` int(11) unsigned NOT NULL auto_increment,
`zip_code` varchar(5) character set utf8 collate utf8_bin NOT NULL,
`city` varchar(50) character set utf8 collate utf8_bin default NULL,
`county` varchar(50) character set utf8 collate utf8_bin default NULL,
`state_name` varchar(50) character set utf8 collate utf8_bin default NULL,
`state_prefix` varchar(2) character set utf8 collate utf8_bin default NULL,
`area_code` varchar(3) character set utf8 collate utf8_bin default NULL,
`time_zone` varchar(50) character set utf8 collate utf8_bin default NULL,
`lat` float NOT NULL,
`lon` float NOT NULL,
`search_string` varchar(52) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `zip_code` (`zip_code`)
) ENGINE=MyISAM AUTO_INCREMENT=42625 DEFAULT CHARSET=utf8;

SELECT items.item_id,
items.category_id,
items.title,
items.description,
items.quality,
items.TYPE,
items.status,
items.price,
items.posted,
items.modified,
zip_code.state_prefix,
zip_code.city,
books.isbn13,
books.isbn10,
books.authors,
books.publisher
FROM items
LEFT JOIN
bookitems
ON bookitems.item_id = items.item_id
LEFT JOIN
books
ON books.isbn13 = bookitems.isbn13
LEFT JOIN
zip_code
ON zip_code.zip_code = items.item_zip
WHERE items.rid = $rid
Create the following indexes:
items (rid)
bookitems (item_id)
books (isbn13)
zip_code (zip_code)

Your first option should be to optimise your database.
Is all those 40k rows useful data? Or can you move some old data to a table containing archived data? Are you using proper indexing? The list goes on..

The first question is what indexes you have. Do you have an index on zip_code (zip_code) ? How big are your other tables? Indexes that would obviously help are, as I say, zip_code (zip_code), then items (rid), bookitems (item_id), and books (isbn13).
I'd think you would almost certainly want on index on zip_code, that's likely used in almost any query against that table. You likely also want indexes on isbn13 and bookitems (item_id). I don't know what items (rid) is supposed to be. An index on that would help this query, but might or might not be generally useful.
Besides that, there are no obvious flaws in the query.
BTW, the parentheses around "items left join bookitems ..." are superfluous. Tables are joined left to right by default.

I am not sure about this just by looking at the query, but
It seems you are joining your rows with string data types, that is a bit slower to compare than using integers, like IDs, specially if not indexed.

Related

How to resolve 'Foreign key constraint is incorrectly formed' issue

I am trying to create a database table called wp_tokens with a foreign key relationship to another table called wp_users. However, every time I attempt to run the create table SQL, I get the error "Foreign key constraint is incorrectly formed". I have tried multiple re-edits of the same code, but I just cannot figure out what is going on.
This is the code for wp_users
CREATE TABLE `wp_users` (
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_login` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_pass` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_nicename` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_email` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_url` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_registered` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`user_activation_key` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_status` int(11) NOT NULL DEFAULT '0',
`display_name` varchar(250) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
PRIMARY KEY (`ID`),
KEY `user_login_key` (`user_login`),
KEY `user_nicename` (`user_nicename`),
KEY `user_email` (`user_email`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
This is the SQL for wp_tokens
CREATE TABLE `wp_tokens` (
id mediumint(9) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`token` varchar(255) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES wp_users (`ID`)
)
Any help would be greatly appreciated!
The unsigned is important -- the types need to be identical. Try this:
CREATE TABLE `wp_tokens` (
id mediumint(9) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL,
`token` varchar(255) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES wp_users (`ID`)
)
Here is a rextester.

How to use subqueries in MySQL

I have two tables.
Table user:
CREATE TABLE IF NOT EXISTS `user` (
`user_id` int(20) NOT NULL AUTO_INCREMENT,
`ud_id` varchar(50) NOT NULL,
`name` text NOT NULL,
`password` text NOT NULL,
`email` varchar(200) NOT NULL,
`image` varchar(150) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB
and mycatch:
CREATE TABLE IF NOT EXISTS `mycatch` (
`catch_id` int(11) NOT NULL AUTO_INCREMENT,
`catch_name` text NOT NULL,
`catch_details` text NOT NULL,
`longitude` float(10,6) NOT NULL,
`latitude` float(10,6) NOT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`image` varchar(150) NOT NULL,
`user_id` int(20) NOT NULL,
PRIMARY KEY (`catch_id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB;
ALTER TABLE `mycatch`
ADD CONSTRAINT `mycatch_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE;
My goal is: I want to retrieve longitude and latitude from mycatch against given ud_id (from user) and catch_id (from mycatch) where ud_id = given ud_id and catch_id > given catch_id.
I used the query but fail to retrieve
SELECT ud_id=$ud_id FROM user
WHERE user_id =
(
SELECT user_id,longitude,latitude from mycatch
WHERE catch_id>'$catch_id'
)
The error is:
#1241 - Operand should contain 1 column(s)
First, try not to use subqueries at all, they're very slow in MySQL.
Second, a subquery wouldn't even help here. This is a regular join (no, Mr Singh, not an inner join):
SELECT ud_id FROM user, mycatch
WHERE catch_id>'$catch_id'
AND user.user_id = mycatch.user_id
Select m.longitude,m.latitude from user u left join mycatch m
on u.user_id =m.user_id
where u.ud_id=$ud_id and
m.catch_id >$catch_id

Why is the comma messing up the insert

here is my query
insert into invoices
set Invoice_number = '823N9823',
price = '11,768.00',
code = 'ret_4_business',
created_at = '2010-09-27';
but my price in my db is 11, not 11768
how do i handle money in mysql? the field is a decimal
CREATE TABLE `invoices` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`Invoice_number` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`price` decimal(10,0) DEFAULT NULL,
`code` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Any non-number character will terminate the parsing and save only part of the number. Remove the comma characters from the query.

Cant figure out how to join these tables

Need help with some kind of join here. Cant figure it out.
I want to loop out the forum boards, but I also want to get last_post and last_posterid from posts, and based on last_posterid get username from users.
This is how far I've come yet (:P):
SELECT name, desc, position FROM boards b
INNER JOIN posts p ON ???
INNER JOIN users u ON ???
ORDER BY b.position ASC
Help would be greatly appreciated.
Cheers
CREATE TABLE `posts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`posterid` int(10) unsigned NOT NULL DEFAULT '0',
`subject` varchar(255) DEFAULT NULL,
`message` text,
`posted` int(10) unsigned NOT NULL DEFAULT '0',
`edited` int(10) unsigned DEFAULT NULL,
`edited_by` int(10) unsigned NOT NULL DEFAULT '0',
`icon` varchar(255) DEFAULT NULL,
`topicid` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
CREATE TABLE `boards` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`desc` varchar(255) NOT NULL,
`position` int(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
CREATE TABLE `users` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(200) NOT NULL,
`password` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
CREATE TABLE `topics` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`posterid` int(10) unsigned NOT NULL DEFAULT '0',
`subject` varchar(255) NOT NULL,
`posted` int(10) unsigned NOT NULL DEFAULT '0',
`last_post` int(10) unsigned NOT NULL DEFAULT '0',
`last_poster` int(10) unsigned NOT NULL DEFAULT '0',
`num_views` mediumint(8) unsigned NOT NULL DEFAULT '0',
`num_replies` mediumint(8) unsigned NOT NULL DEFAULT '0',
`closed` tinyint(1) NOT NULL DEFAULT '0',
`sticky` tinyint(1) NOT NULL DEFAULT '0',
`icon` varchar(40) DEFAULT NULL,
`boardid` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=16 DEFAULT CHARSET=utf8
Assuming posts.topicid refers to boards.id, and posts.posterid refers to users.id, try something like
SELECT b.name, d.desc, b.position
FROM boards b
LEFT JOIN posts p
ON b.id = p.topicid
LEFT JOIN users u
ON (p.posterid = u.id) AND (p.posted = (SELECT MAX(sp.posted) FROM posts sp GROUP BY))
ORDER BY
b.position ASC
Another remark: try to name your fields such that it is clear what they refer to, for instance:
CREATE TABLE `foo` (
foo_ID UNSIGNED INTEGER NOT NULL AUTO_INCREMENT,
-- more stuff
PRIMARY KEY (`foo_ID`)
);
This allows you to re-use the field name foo_ID in another table, which makes things very easy when performing a join.
Nothing stands out, so this is mostly a guess:
SELECT b.name, b.desc, b.position
FROM boards AS b
INNER JOIN posts AS p
ON b.id = p.topicid
INNER JOIN users AS u
ON p.posterid = u.id
ORDER BY b.position ASC
SELECT username, message, posts.id
FROM users, posts
WHERE posterid=users.id
GROUP BY username, message, posts.id
HAVING posted=MAX(posted)
This might just do to get each user's last post concisely.
Ok, lack of a schema not withstanding, it looks like you could use nested select. Something like:
DECLARE #posts TABLE
(
postdate datetime,
postid int,
boardid int
)
DECLARE #users TABLE
(
userid int,
username varchar(10)
)
DECLARE #boards TABLE
(
boardid int
)
INSERT INTO #boards(boardid) VALUES (1)
INSERT INTO #users (userid, username) values (1, 'Adam')
insert into #posts (postdate, postid, boardid) values (GETDATE(), 1, 1)
SELECT b.*, p.*
from #boards b
inner join
(
SELECT TOP 1 postdate, postid, boardid, username FROM #posts
inner join #users on userid = postid WHERE boardid = 1
order by postdate desc
) as p on p.boardid = b.boardid
Oviously this is heavily generalised and uses some hard-coded values, but hopefully it should give you an option for implementing it on your own tables.
Of course, I infer no correctness in my implementation of the spec - SQL is not my first language lol
Was going to post this as a comment but it was too verbose and messy.
you'll want to use OUTER JOINs because otherwise boards with no posts in them will not show up
any single query that links those four tables is going to be a beast - consider using VIEWs to simplify things
your initial question and the tables you posted leave things a bit ambiguous: do you already have last_post and last_poster filled out in topics? If so, then your primary targets for the join should be boards and topics, and since you're already storing redundant data (I assume for speed?), why don't you also put in a last_post_time or something, which would simplify things a lot and curb load (no need to join in posts at all)?
mediumints? why on Earth? (If you don't need the date, and you think it'll never wrap, I suppose you can use the hack of trusting that a later post will have a higher ID due to autoincrementing key...)

what is called KEY

CREATE TABLE `ost_staff` (
`staff_id` int(11) unsigned NOT NULL auto_increment,
`group_id` int(10) unsigned NOT NULL default '0',
`dept_id` int(10) unsigned NOT NULL default '0',
`username` varchar(32) collate latin1_german2_ci NOT NULL default '',
`firstname` varchar(32) collate latin1_german2_ci default NULL,
`lastname` varchar(32) collate latin1_german2_ci default NULL,
`passwd` varchar(128) collate latin1_german2_ci default NULL,
`email` varchar(128) collate latin1_german2_ci default NULL,
`phone` varchar(24) collate latin1_german2_ci NOT NULL default '',
`phone_ext` varchar(6) collate latin1_german2_ci default NULL,
`mobile` varchar(24) collate latin1_german2_ci NOT NULL default '',
`signature` varchar(255) collate latin1_german2_ci NOT NULL default '',
`isactive` tinyint(1) NOT NULL default '1',
`isadmin` tinyint(1) NOT NULL default '0',
`isvisible` tinyint(1) unsigned NOT NULL default '1',
`onvacation` tinyint(1) unsigned NOT NULL default '0',
`daylight_saving` tinyint(1) unsigned NOT NULL default '0',
`append_signature` tinyint(1) unsigned NOT NULL default '0',
`change_passwd` tinyint(1) unsigned NOT NULL default '0',
`timezone_offset` float(3,1) NOT NULL default '0.0',
`max_page_size` int(11) NOT NULL default '0',
`created` datetime NOT NULL default '0000-00-00 00:00:00',
`lastlogin` datetime default NULL,
`updated` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`staff_id`),
UNIQUE KEY `username` (`username`),
KEY `dept_id` (`dept_id`),
KEY `issuperuser` (`isadmin`),
KEY `group_id` (`group_id`,`staff_id`)
) ENGINE=MyISAM AUTO_INCREMENT=35 DEFAULT CHARSET=latin1 COLLATE=latin1_german2_ci;
Hi the above query is the osticket open source one,
i know primary key , foreign key , unique but AM NOT SURE WHAT IS THIS
KEY group_id (group_id,staff_id)
Please tell me, this constraints name....
It's a synonym of INDEX
It's a Index combining the two columns group_id and staff_id but is referred to as group_id. Think of it as a Unique Identifier, but MySQL does not enforce that column to be unique. Also, the way the db will check this key will be in the order of group_id and then staff_id, not the other way around, so make sure your queries reflect that (i.e. doing an order for staff_id alone will not be faster than doing an order by group_id and then staff_id).
If you're asking about the **, those mean nothing in MySQL.
Just saw Bozho's comment, that link should be sufficient.