MySQL Create new table syntax errors - php

I was trying to create a new table with the following PHP/mysql code snippet:
$query = "
CREATE TABLE IF NOT EXISTS :user (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
username CHAR(24),
summonername VARCHAR(16),
password VARCHAR(16),
region CHAR(3),
lvl INT(2) DEFAULT '0',
maxlvl INT(2),
status VARCHAR(20),
enabled INT(1) DEFAULT '1',
priority INT(1) DEFAULT '0',
note VARCHAR(150)
)
";
$query_params = array (
':user' => $user,
);
If I execute this one, I get some syntax error:
Failed: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''testuser' ( id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, use' at line 1
It is weird because I checked the syntax according to the official documentation (http://dev.mysql.com/doc/refman/5.1/de/create-table.html) and also some user-made examples.
Also, is there a way to set the default encoding of the table to utf8 (not for every single row, but global)?
I would be happy if someone can tell me how to fix the syntax errors there,

You cannot use parameters for metadata. Nor should you be creating tables per user. Either add a field to hold the user, or sanitize the table name yourself.

With reference to my comment above, and by way of example...
CREATE TABLE my_table(i INT(4) ZEROFILL NOT NULL PRIMARY KEY);
INSERT INTO my_table VALUES (1),(9),(10),(99),(100),(999),(1000),(9999),(10000);
SELECT * FROM my_table;
+-------+
| i |
+-------+
| 0001 |
| 0009 |
| 0010 |
| 0099 |
| 0100 |
| 0999 |
| 1000 |
| 9999 |
| 10000 |
+-------+

please try this query in create table
CREATE TABLE IF NOT EXISTS `user` (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
username CHAR(24),
summonername VARCHAR(16),
password VARCHAR(16),
region CHAR(3),
lvl INT(2) DEFAULT '0',
maxlvl INT(2),
status VARCHAR(20),
enabled INT(1) DEFAULT '1',
priority INT(1) DEFAULT '0',
)

Related

how to add multiple user with multiple times

I want to make 1 user and be able to add multiple images for this user. I'm thinking about adding content_group for using GROUP UP. Is this the correct database structure?
USER
|---------------------|------------------|
| id | name |
|---------------------|------------------|
| 1 | TESTNAME |
|---------------------|------------------|
CONTENT
|---------------------|------------------|------------------|
| id . | user_id | picture | content_group
|---------------------|------------------|------------------|
| 1 | 1 | 1 .jpg | xxxxxx
|---------------------|------------------|------------------|
| 2 | 1 | 2 .jpg | yyyyyy
|---------------------|------------------|------------------|
This looks reasonably well. If you are adding 'content groups', you should continue the same pattern though.
I think I would expect your data model to look a little bit more like this. I renamed some fields. This is super subjective, but this is how I'd probably expect the fields to be called.
CREATE TABLE user (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
CREATE TABLE content (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT
user_id INT UNSIGNED NOT NULL,
image_filename VARCHAR(50),
category_id INT UNSIGNED
);
CREATE TABLE content_category (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
You might want to add some foreign keys, but it's ultimately optional. Not everyone uses them in MySQL.
The above data-model assumes that categories are shared. If categories belong to specific users, then that table should also get a user_id field.
Furthermore, if categories are per-user and a category is required to exist for every item in content, it means that the user_id field is not needed in the content table, because you can find it through the categories table.
So here's an alternative where categories are per-user and required:
CREATE TABLE user (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
CREATE TABLE content (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT
image_filename VARCHAR(50),
category_id INT UNSIGNED NOT NULL
);
CREATE TABLE content_category (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
user_id INT UNSIGNED NOT NULL,
name VARCHAR(50)
);

Update/Insert Large table records based on another Table ReferenceID.

I have an Internal Inventory system with the below 3 tables as
a. Stocks - Daily updated from a CSV file.
---------------------------------
| id | MODELNO | Discount | MRP |
---------------------------------
| 1 | MODEL_1 | 40% | 900 |
| 2 | MODEL_A | 20% | 600 |
---------------------------------
Everyday this table is truncated and new stocks data are imported from a CSV file of a merchant.(around 6 Million records)
b. Cloths Master - The master clothes database
----------------------------------------
| ref_id | MODELNO | Name | MRP |
----------------------------------------
| 80 | MODEL_1 |Some Dress | 900 |
| 81 | MODEL_A |Another Dress| 600 |
----------------------------------------
The MODELNO is unique and ref_id the primary key. This table is part of the internal Inventory application (Has around 4.5 Million records)
c. Inventory table - It's part of the internal Applications
-------------------------------------------------
| id | ref_id | Name | MRP | status |
-------------------------------------------------
| 1 | 80 |Some Dress | 900 | ACTIVE |
| 2 | 81 |Another Dress| 600 | INACTIVE |
--------------------------------------------------
This table stores the available inventory for the product, based on the stocks and if the discount if above 40% the product is ACTIVE else by default INACTIVE.
The required functionality is that every day I need to run a script that would loop throught stock table records, and for the MODELNO update the stock on the Inventory table and If the record in Inventory table does not exist then it needs to be added.
What I have tried till now is a PHP script that would.
a. Firstly, set status in Inventory table for all records to INACTIVE.
b. And for each of the records in the stocks table, check if the MODELNO exists in Cloths Master table.
b. If the records exists then get the ref_id, and check if the ref_id exists in the Inventory Table and Update/Insert record accordingly.
The problem is that the script takes more than 8+ Hrs to complete.
Can you a suggest an efficient way, that can be used to implement the above functionality.
Note :
All the inserts and updated to the Inventory table are done using CodeIgniter's batch insert/update function.
I set all the status to INACTIVE, as there may be few products that are not present in the Stock DB.
The question in this case which comes in mind is - why not using a trigger ?
Create a table so_stocks
CREATE TABLE IF NOT EXISTS `so_stocks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`MODELNO` varchar(50) COLLATE uft8_general_ci NOT NULL DEFAULT '0',
`Discount` int(10) DEFAULT '0',
`MRP` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=uft8_general_ci;
INSERT INTO `so_stocks` (`id`, `MODELNO`, `Discount`, `MRP`) VALUES
(1, 'MODEL_1', 40, 900),
(2, 'MODEL_A', 20, 600);
Create a table so_inventory
CREATE TABLE `so_inventory` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`ref_id` INT(11) NOT NULL DEFAULT '0',
`Name` VARCHAR(255) NOT NULL DEFAULT '0' COLLATE 'uft8_general_ci',
`MRP` INT(11) NOT NULL DEFAULT '0',
`status` TINYINT(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
COLLATE='uft8_general_ci'
ENGINE=MyISAM
AUTO_INCREMENT=1
;
And finally a table so_cloths
CREATE TABLE `so_cloths` (
`ref_id` INT(11) NOT NULL AUTO_INCREMENT,
`MODELNO` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'uft8_general_ci',
`Name` VARCHAR(255) NOT NULL DEFAULT '0' COLLATE 'uft8_general_ci',
`MRP` INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`ref_id`)
)
COLLATE='uft8_general_ci'
ENGINE=MyISAM
AUTO_INCREMENT=1
;
And now the trigger
CREATE DEFINER=`root`#`::1` TRIGGER `so_cloths_after_insert` AFTER INSERT ON `so_cloths` FOR EACH ROW BEGIN
INSERT INTO so_inventory (ref_id,Name,MRP,status)
select sc.ref_id,sc.Name, sc.MRP, if (ss.Discount >= 40, 1,0) AS active from so_cloths AS sc
LEFT JOIN so_stocks AS ss ON (sc.MODELNO = ss.MODELNO)
WHERE sc.ref_id = new.ref_id;
END
Everytime you insert something into so_cloths an insert would be made into so_inventory.
Obviously it depends whether you want to insert data after inserting it into so_stocks or into so_cloths - you've to decide it - but the example should give you some insight.
The definer in the trigger statement has to be changed to your settings

"Integrity constraint violation: 1062 Duplicate entry" - but no duplicate rows

I'm converting an app from native mysqli calls to PDO. Running into an error when attempting to insert a row into a table with a foreign key constraint.
Note: this is a simplified test case and should not be copy/pasted into a production environment.
Info PHP 5.3, MySQL 5.4
First, here are the tables:
CREATE TABLE `z_one` (
`customer_id` int(10) unsigned NOT NULL DEFAULT '0',
`name_last` varchar(255) DEFAULT NULL,
`name_first` varchar(255) DEFAULT NULL,
`dateadded` datetime DEFAULT NULL,
PRIMARY KEY (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `z_one` VALUES (1,'Khan','Ghengis','2014-12-17 10:43:01');
CREATE TABLE `z_many` (
`order_id` varchar(15) NOT NULL DEFAULT '',
`customer_id` int(10) unsigned DEFAULT NULL,
`dateadded` datetime DEFAULT NULL,
PRIMARY KEY (`order_id`),
KEY `order_index` (`customer_id`,`order_id`),
CONSTRAINT `z_many_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `z_one` (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Or if you prefer,
mysql> describe z_one;
+-------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| customer_id | int(10) unsigned | NO | PRI | 0 | |
| name_last | varchar(255) | YES | | NULL | |
| name_first | varchar(255) | YES | | NULL | |
| dateadded | datetime | YES | | NULL | |
+-------------+------------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
mysql> describe z_many;
+-------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| order_id | varchar(15) | NO | PRI | | |
| customer_id | int(10) unsigned | YES | MUL | NULL | |
| dateadded | datetime | YES | | NULL | |
+-------------+------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
Next, here is the query:
$order_id = '22BD24';
$customer_id = 1;
try
{
$q = "
INSERT INTO
z_many
(
order_id,
customer_id,
dateadded
)
VALUES
(
:order_id,
:customer_id,
NOW()
)
";
$stmt = $dbx_pdo->prepare($q);
$stmt->bindValue(':order_id', $order_id, PDO::PARAM_STR);
$stmt->bindValue(':customer_id', $customer_id, PDO::PARAM_INT);
$stmt->execute();
} catch(PDOException $err) {
// test case only. do not echo sql errors to end users.
echo $err->getMessage();
}
This results in the following PDO error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'22BD24' for key 'PRIMARY'
The same query works fine when handled by mysqli. Why is PDO rejecting the INSERT with a 'duplicate entry' message when there aren't any duplicates found?
Since not all code is available (from php side) just in case your query is in some sort of loop the quickest (and perhaps partly) solution to this is the following:
$order_id = '22BD24';
$customer_id = 1;
try {
$q = "INSERT INTO `z_many` (`order_id`,`customer_id`,`dateadded`)
VALUES (:order_id,:customer_id,NOW())
ON DUPLICATE KEY UPDATE `dateadded`=NOW()";
$stmt = $dbx_pdo->prepare($q);
$stmt->bindValue(':order_id', $order_id, PDO::PARAM_STR);
$stmt->bindValue(':customer_id', $customer_id, PDO::PARAM_INT);
$stmt->execute();
} catch(PDOException $err) {
// test case only. do not echo sql errors to end users.
echo $err->getMessage();
}
I've copied the SQL schema you provided on my mysql DB and added script code, but with PDO initialization at the start:
$dbx_pdo = new PDO('mysql:host=127.0.0.1;dbname=test12;charset=utf8','root','');
.. and it worked fine, but my setup is php 5.5.9 and mysql 5.6.16
So I think your code executes twice and maybe its inside of a transaction, so you get rollback. Need to know more context
Please REMOVE default for primary key column. Also use the construction
INSERT INTO (`field1`, `field2`, `...`) values (val1, val2, val3);
If you tell what default value is for inserts - some mysql versions can see errors when inserting. Thats why you should use auto-increment or dont use default value at all.
Just a shot in the dark. I use PDO with ORACLE PL/SQL only with bindParam(). And have a look at the forth parameter ,15 if it is a PARAM_STR value. So try this, hope it helps.
$stmt->bindParam(':order_id', $order_id, PDO::PARAM_STR, 15);
$stmt->bindParam(':customer_id', $customer_id, PDO::PARAM_INT);
This 15 descibes the (bufffer-)length from order_id in your table.

MySQL Database Unique Column Names

I have written myself a PHP script to help manage a small squad of 10 members. This script current connects to a database and updates weekly updating any rank and power change. However I want to expand this and in a new table, have all the power gained kept in a separate column instead of adding it all up.
The only way I can think of doing this is by using the below code to create the column and changing the name for each.
ALTER TABLE `power gained` ADD timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER name;
So my question is, can I create columns making sure their name is unique, OR is it possible to set a column name to the timestamp as that would save me a column.
I'd like to also make it clear I am using mysql_ functions as this is on my local system, not vulnerable to attacks and I am not stupid enough to make mistakes.
Use normalisation.
Split the users and the power into two difference tables.
CREATE TABLE `users_power` (
`user_id` smallint(5) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`total_power` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
CREATE TABLE `power` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` smallint(1) NOT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`power_increase` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
DROP TRIGGER IF EXISTS `Update users_power`;
CREATE TRIGGER `Update users_power` AFTER INSERT ON `power` FOR EACH ROW BEGIN
UPDATE `users_power` AS `UP`
SET `UP`.`total_power` = `UP`.`total_power` + NEW.`power_increase`
WHERE `UP`.`user_id` = NEW.`user_id`;
END
And then to look up, do something like;
SELECT `power` AS total_power
FROM users_power
WHERE users_power.`user_id` = 1
And your structure would look something like;
select * from power where user_id = 1;
+----+---------+---------------------+----------------+
| id | user_id | time | power_increase |
+----+---------+---------------------+----------------+
| 1 | 1 | 2014-08-7 17:04:06 | 5 |
| 2 | 1 | 2014-08-15 17:04:31 | 15 |
+----+---------+---------------------+----------------+
2 rows in set
select * from users_power where user_id = 1;
+---------+------+-------------+
| user_id | name | total_power |
+---------+------+-------------+
| 1 | joe | 21 |
+---------+------+-------------+
1 row in set
Edits
Added trigger to automatically update total power when a record is inserted into power
Haven't yet tested this but I think I have found a way I can do this in a way that I understand (which is the most important part).
$now = date('Y-m-d');
mysql_query("ALTER TABLE `power gained` ADD `$now` VARCHAR(22) AFTER name");
// ...
mysql_query("UPDATE `power gained` (`$now`) VALUES($gained) WHERE id = $i");
I'm not looking to extract the data from the database as I can access it just by going to localhost/phpmyadmin and I'm not going to ever need to extract it.

Thoughts on improving these queries (MySQL) ? My production machine is seeing 10 sec times on them (using a sample size of over 1000)

Table definition and queries explained:
item |
CREATE TABLE `item` (
`item_id` int(11) NOT NULL AUTO_INCREMENT,
`item_type_id` int(11) NOT NULL,
`brand_id` int(11) NOT NULL,
`site_id` int(11) NOT NULL,
`seller_id` int(11) NOT NULL,
`title` varchar(175) NOT NULL,
`desc` text NOT NULL,
`url` varchar(767) NOT NULL,
`price` int(11) NOT NULL,
`photo` varchar(255) NOT NULL,
`photo_file` varchar(255) NOT NULL,
`photo_type` varchar(32) NOT NULL,
`has_photo` enum('yes','no','pending') NOT NULL DEFAULT 'pending',
`added_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`normalized_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`location` varchar(128) NOT NULL,
PRIMARY KEY (`item_id`),
KEY `item_type_id` (`item_type_id`),
KEY `brand_id` (`brand_id`),
KEY `site_id` (`site_id`),
KEY `seller_id` (`seller_id`),
KEY `created_at` (`created_at`),
KEY `added_at` (`added_at`),
KEY `normalized_time` (`normalized_time`),
KEY `typephototime` (`item_type_id`,`has_photo`,`normalized_time`),
KEY `brandidphoto` (`brand_id`,`item_type_id`,`has_photo`),
KEY `brandidphoto2` (`brand_id`,`item_type_id`,`has_photo`),
KEY `idphoto` (`item_type_id`,`has_photo`),
KEY `idphototime` (`item_type_id`,`has_photo`,`normalized_time`),
KEY `idphoto2` (`item_type_id`,`has_photo`),
KEY `typepricebrandid` (`item_type_id`,`price`,`brand_id`,`item_id`),
KEY `sellertypephototime` (`seller_id`,`item_type_id`,`has_photo`,`normalized_time`),
KEY `typephoto` (`item_type_id`,`has_photo`)
) ENGINE=MyISAM AUTO_INCREMENT=508885 DEFAULT CHARSET=latin1 |
mysql> explain SELECT item.* FROM item WHERE item.item_type_id = "1" AND item.has_photo = "yes" ORDER BY normalized_time DESC LIMIT 1;
+----+-------------+-------+------+------------------------------------------------------------------------------------+---------------+---------+-------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------------------------------------------------------------------------+---------------+---------+-------------+-------+-------------+
| 1 | SIMPLE | item | ref | item_type_id,typephototime,idphoto,idphototime,idphoto2,typepricebrandid,typephoto | typephototime | 5 | const,const | 69528 | Using where |
+----+-------------+-------+------+------------------------------------------------------------------------------------+---------------+---------+-------------+-------+-------------+
1 row in set (0.02 sec)
mysql> explain SELECT * FROM item WHERE item_type_id = "1" AND (price BETWEEN "25" AND "275") AND brand_id = "10" ORDER BY item_id DESC LIMIT 1;
+----+-------------+-------+-------+------------------------------------------------------------------------------------------------------------------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------------------------------------------------------------------------------------------------------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | item | index | item_type_id,brand_id,typephototime,brandidphoto,brandidphoto2,idphoto,idphototime,idphoto2,typepricebrandid,typephoto | PRIMARY | 4 | NULL | 203 | Using where |
+----+-------------+-------+-------+------------------------------------------------------------------------------------------------------------------------+---------+---------+------+------+-------------+
1 row in set (0.01 sec)
mysql> explain SELECT item.* FROM item WHERE item.brand_id = "10" AND item.item_type_id = "1" AND item.has_photo = "yes" ORDER BY normalized_time DESC LIMIT 1;
+----+-------------+-------+-------+------------------------------------------------------------------------------------------------------------------------+-----------------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------------------------------------------------------------------------------------------------------------+-----------------+---------+------+--------+-------------+
| 1 | SIMPLE | item | index | item_type_id,brand_id,typephototime,brandidphoto,brandidphoto2,idphoto,idphototime,idphoto2,typepricebrandid,typephoto | normalized_time | 8 | NULL | 502397 | Using where |
+----+-------------+-------+-------+------------------------------------------------------------------------------------------------------------------------+-----------------+---------+------+--------+-------------+
1 row in set (2.15 sec)
mysql> explain SELECT COUNT(*) FROM item WHERE item.item_type_id = "1" AND item.has_photo = "yes" ;
+----+-------------+-------+------+------------------------------------------------------------------------------------+-----------+---------+-------------+-------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------------------------------------------------------------------------+-----------+---------+-------------+-------+--------------------------+
| 1 | SIMPLE | item | ref | item_type_id,typephototime,idphoto,idphototime,idphoto2,typepricebrandid,typephoto | typephoto | 5 | const,const | 71135 | Using where; Using index |
+----+-------------+-------+------+------------------------------------------------------------------------------------+-----------+---------+-------------+-------+--------------------------+
1 row in set (0.01 sec)
The following indexes are redundant because they match the left columns of another index. You can almost certainly drop these indexes and save some space and overhead.
KEY `item_type_id` (`item_type_id`), /* redundant */
KEY `brand_id` (`brand_id`), /* redundant */
KEY `seller_id` (`seller_id`), /* redundant */
KEY `idphototime` (`item_type_id`,`has_photo`,`normalized_time`), /* redundant */
KEY `brandidphoto2` (`brand_id`,`item_type_id`,`has_photo`), /* redundant */
KEY `idphoto` (`item_type_id`,`has_photo`), /* redundant */
KEY `idphoto2` (`item_type_id`,`has_photo`), /* redundant */
KEY `typephoto` (`item_type_id`,`has_photo`) /* redundant */
That leaves the following indexes:
KEY `site_id` (`site_id`),
KEY `created_at` (`created_at`),
KEY `added_at` (`added_at`),
KEY `normalized_time` (`normalized_time`),
KEY `brandidphoto` (`brand_id`,`item_type_id`,`has_photo`),
KEY `typephototime` (`item_type_id`,`has_photo`,`normalized_time`),
KEY `typepricebrandid` (`item_type_id`,`price`,`brand_id`,`item_id`),
KEY `sellertypephototime` (`seller_id`,`item_type_id`,`has_photo`,`normalized_time`),
You can also use a tool like pt-duplicate-key-checker to find redundant indexes.
Next consider the storage engine:
) ENGINE=MyISAM AUTO_INCREMENT=508885 DEFAULT CHARSET=latin1;
Almost always, InnoDB is a better choice than MyISAM. Not only for performance, but for data integrity and crash safety. InnoDB has been the default storage engine since 2010, and it's the only storage engine that is actively getting improved. I'd recommend making a copy of this table, changing the storage engine to InnoDB, and compare its performance with respect to your queries.
Next let's consider indexes for the queries:
SELECT item.* FROM `item` WHERE item.item_type_id = "1" AND item.has_photo = "yes"
ORDER BY normalized_time DESC LIMIT 1;
I would choose an index on (item_type_id, has_photo, normalized_time) and that's the index it's currently using, which is typephototime.
One way to optimize this further would be to fetch only the columns in the index. That's when you see "Using index" in the EXPLAIN plan, it can be a huge improvement for performance.
Another important factor is to make sure that your index is cached in memory: increase key_buffer_size if you use MyISAM or innodb_buffer_pool_size if you use InnoDB to be as large as all the indexes you want to remain in memory. Because you don't want to run a query that needs to scan an index larger than your buffers; it causes a lot of swapping.
SELECT * FROM `item` WHERE item_type_id = "1" AND (price BETWEEN "25" AND "275") AND brand_id = "10"
ORDER BY item_id DESC LIMIT 1;
I would choose an index on (item_type_id, brand_id, price), but this query is currently using the PRIMARY index. You should create a new index.
SELECT item.* FROM `item` WHERE item.brand_id = "10" AND item.item_type_id = "1" AND item.has_photo = "yes"
ORDER BY normalized_time DESC LIMIT 1;
I would choose an index on (item_type_id, brand_id, has_photo, normalized_time). You should create a new index.
SELECT COUNT(*) FROM `item` WHERE item.item_type_id = "1" AND item.has_photo = "yes" ;
I would choose an index on (item_type_id, has_photo) and that's the index it's currently using, which is typephoto. It's also getting the "Using index" optimization, so the only other improvement could be to make sure there's enough buffer to hold the index in memory.
It's hard to optimize SELECT COUNT(*) queries because they naturally have to scan a lot of rows.
The other strategy to optimize COUNT(*) is to calculate the counts offline, and store them either in a summary table or in an in-memory cache like memcached so you don't have to recalculate them every time someone loads a page. But that means you have to update the counts every time someone adds or deletes a row in the item table, which could be more costly depending on how frequently that happens.
A few things I would suggest changing:
You don't need all those indexes. You really only need indexes on fields that are accessed a lot, like foreign key fields. Remove all the indexes except ones on ID fields.
You should be storing dates as nulls unless there is actual data.
Stay away from the enum data type, use smallint with flags representing each value. Example, 0 pending, 1 yes, 2 no.
Alongside reducing the size of the database, it makes things much cleaner. Your new table structure would look like so:
CREATE TABLE `item` (
`item_id` int(11) NOT NULL AUTO_INCREMENT,
`item_type_id` int(11) NOT NULL,
`brand_id` int(11) NOT NULL,
`site_id` int(11) NOT NULL,
`seller_id` int(11) NOT NULL,
`title` varchar(175) NOT NULL,
`desc` text NOT NULL,
`url` varchar(767) NOT NULL,
`price` int(11) NOT NULL,
`photo` varchar(255) NOT NULL,
`photo_file` varchar(255) NOT NULL,
`photo_type` varchar(32) NOT NULL,
`has_photo` smallint NOT NULL DEFAULT 0,
`added_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updated_at` datetime NULL DEFAULT NULL,
`created_at` datetime NULL DEFAULT NULL,
`normalized_time` datetime NULL DEFAULT NULL,
`location` varchar(128) NULL,
PRIMARY KEY (`item_id`),
KEY `item_type_id` (`item_type_id`),
KEY `brand_id` (`brand_id`),
KEY `site_id` (`site_id`),
KEY `seller_id` (`seller_id`)
) ENGINE=MyISAM AUTO_INCREMENT=508885 DEFAULT CHARSET=latin1;
I would also suggest using utf8_unicode_ci as the collate, utf8 as the charset, and InnoDB as the engine.
But first off, remove all those keys and try again. Also remove the aliasing on the 3rd query.
SELECT * FROM item WHERE brand_id = "10" AND item_type_id = "1" AND has_photo = "yes" ORDER BY normalized_time DESC LIMIT 1;

Categories