Insert trigger subtract value from another table - php

I am a newbie at sql. I made a php website that user can make item purchases. And now I want to make a trigger for when a receipt is created, it subtracts the quantity in the item stock table with the quantity sold in the receipt.
create table if not exists Store.Receipt (
ReceiptID varchar(10) NOT NULL DEFAULT '0',
ItemID varchar(10) NOT NULL,
PriceSold int NOT NULL,
QtySold int NOT NULL,
RDate DATETIME NOT NULL,
BranchID varchar(10) NOT NULL,
EmployeeID varchar(10) NOT NULL,
MemberID varchar(10) DEFAULT "NotMember",
primary key (ReceiptID, ItemID));
alter table Store.Receipt
add constraint FK_Receipt_Member foreign key (MemberID)
references Store.MemberShip (MemberID),
add constraint FK_Receipt_Employee foreign key (EmployeeID)
references Store.Employee (EmployeeID),
add constraint FK_Receipt_Branch foreign key (BranchID)
references Store.Branch (BranchID)
on delete restrict
on update cascade;
The sql script above is for the receipt table. The id for this table is auto generated using autoincrement. One ReceiptID can contain many items. So I am using a composite of ReceiptID and ItemID sold as the primary key. And here is the Script for the table.
create table if not exists Store.Item (
ItemID varchar(10) NOT NULL DEFAULT '0',
ItemName varchar(45) NOT NULL,
UnitPrice int NOT NULL,
QtyStock int NOT NULL,
SupplierID varchar(10) NOT NULL,
primary key (ItemID));
alter table Store.Item
add constraint FK_Supplier foreign key (SupplierID)
references Store.Supplier (SupplierID)
on delete restrict
on update cascade;
I have been trying to make the trigger on my own, but I am still quite unfimiliar with sql triggers. Here is what I came up with.
DELIMITER $$
CREATE TRIGGER update_stock_trigger
AFTER INSERT ON Store.Receipt
FOR EACH ROW
BEGIN
DECLARE
#qty numeric
#itemid varchar
#itemid = SELECT ItemID FROM Receipt WHERE ReceiptID = INSERTED.ItemID;
#qty = SELECT QtySold FROM Receipt WHERE ReceiptID = INSERTED.QtySold;
UPDATE Item SET QtyStock = QtyStock - #qty WHERE ItemID = #itemid;
END$$
DELIMITER ;
Thank you for your help in advance.

I don't think the trigger needs to be quite so complicated as you can access the values required directly by prefixing the column name with NEW
create trigger `update_stock_trigger`
after insert on `receipt`
for each row
begin
update `item` set `qtystock` = `qtystock` - new.qtysold where `itemid` = new.itemid;
end
Using a slightly simplified schema ( remove FK dependency upon Supplier table )
mysql> describe item;
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| ItemID | varchar(10) | NO | PRI | 0 | |
| ItemName | varchar(45) | NO | | NULL | |
| UnitPrice | int(11) | NO | | NULL | |
| QtyStock | int(11) | NO | | NULL | |
| SupplierID | varchar(10) | NO | | NULL | |
+------------+-------------+------+-----+---------+-------+
mysql> describe receipt;
+------------+-------------+------+-----+-----------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+-----------+-------+
| ReceiptID | varchar(10) | NO | PRI | 0 | |
| ItemID | varchar(10) | NO | PRI | NULL | |
| PriceSold | int(11) | NO | | NULL | |
| QtySold | int(11) | NO | | NULL | |
| RDate | datetime | NO | | NULL | |
| BranchID | varchar(10) | NO | | NULL | |
| EmployeeID | varchar(10) | NO | | NULL | |
| MemberID | varchar(10) | YES | | NotMember | |
+------------+-------------+------+-----+-----------+-------+
And populated a single Product in the item table
mysql> select * from item;
+--------+----------+-----------+----------+------------+
| ItemID | ItemName | UnitPrice | QtyStock | SupplierID |
+--------+----------+-----------+----------+------------+
| 1 | bob | 23 | 100 | 404-blah |
+--------+----------+-----------+----------+------------+
And an initial record in the receipt table
mysql> select * from receipt;
+-----------+--------+-----------+---------+---------------------+----------+------------+-----------+
| ReceiptID | ItemID | PriceSold | QtySold | RDate | BranchID | EmployeeID | MemberID |
+-----------+--------+-----------+---------+---------------------+----------+------------+-----------+
| 1 | 1 | 43 | 17 | 2020-01-30 09:28:29 | Dundee | Senga-X | NotMember |
+-----------+--------+-----------+---------+---------------------+----------+------------+-----------+
and a subsequent second record ( showing used sql )
INSERT INTO `Store`.`receipt` (`ReceiptID`, `ItemID`, `PriceSold`, `QtySold`, `RDate`, `BranchID`, `EmployeeID`)
VALUES ('2', '1', '35', '10', '2020-01-30 09:32:00', 'Glasgow', 'Senga-Y');
The trigger has updated the item table accordingly
mysql> select * from item;
+--------+----------+-----------+----------+------------+
| ItemID | ItemName | UnitPrice | QtyStock | SupplierID |
+--------+----------+-----------+----------+------------+
| 1 | bob | 23 | 73 | 404-blah |
+--------+----------+-----------+----------+------------+

Related

how can i get data from table with relation many to many in yii2?

I have three tables
----------
mysql> show columns from employee;
+-----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| firstname | varchar(30) | NO | | NULL | |
| lastname | varchar(30) | YES | | NULL | |
| position | tinyint(1) | NO | | 0 | |
| email | varchar(50) | NO | | NULL | |
+-----------+------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> show columns from groups;
+---------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name_of_group | varchar(50) | NO | | NULL | |
+---------------+------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> show columns from groups_of_employee;
+-------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| employee_id | int(10) unsigned | NO | MUL | NULL | |
| group_id | int(10) unsigned | NO | MUL | NULL | |
+-------------+------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
and some code
class employee
public function getGroupsOfEmployee()
{
return $this->hasMany(GroupsOfEmployee::className(), ['id' =>
'group_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getGroups()
{
return $this->hasMany(Groups::className(), ['employee_id' => 'id'])
->via('groupsOfEmployee');
//->viaTable('groups_of_employee', ['group_id' => 'id']);
}
class groups
public function getGroupsOfEmployee()
{
return $this->hasMany(GroupsOfEmployee::className(),
['employee_id' => 'id']);
}
for example i get
$model = Employee::findOne(1);
var_dump($model->getGroups());
but i don't see any way how to get name of grooup from table called groups
If the relation is based on getGroup
you should use
$model = Employee::findOne(1);
var_dump($model->groups);
var_dump($model->groupOfEmployee);
and accessing to the value
var_dump($model->groups->id);
var_dump($model->groupOfEmployee->id);

mysql query - not scaling well, slow with 15k records

I have a large SQL query (mysql) used to populate a table of records in a CRM system.
This has been working well and quite fast with around 4000 records. Now reaching 15,500 it's running painfully slow. Taking around 70 seconds to return data.
I have tried adding some indexes, but with limited success. Any suggestions?
The query is:
SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS
s.*,
ca2.address_postcode,
ca2.resident_status
FROM
(
SELECT
a.id,
c.name_first,
c.name_last,
a.created as app_created,
a.edited,
a.comments as custcomment,
c.name_company,
a.loan_amount,
a.product_type,
ap.packager as placed_with,
a.introducer_id,
a.status,
c.contact_number,
c.email,
a.database,
bd.legal_structure,
(
SELECT
ca1.id
FROM
cl_customer_address AS ca1
WHERE
ca1.customer_id = a.customer_id AND
ca1.deleted = "0000-00-00 00:00:00"
ORDER BY
ca1.created DESC
LIMIT
1
) AS address_id
FROM
cl_application AS a
LEFT JOIN
cl_application_business_details as bd on bd.id = a.id
LEFT JOIN
cl_customer AS c ON c.id = a.customer_id AND c.deleted = c.deleted
LEFT JOIN
cl_application_packager AS ap ON ap.application_id = a.id AND ap.status != "Declined" AND ap.deleted = "0000-00-00 00:00:00"
WHERE
(a.deleted = "0000-00-00 00:00:00")
GROUP BY
a.id
) AS s
LEFT JOIN
cl_customer_address AS ca2 ON ca2.id = s.address_id AND ca2.deleted = ca2.deleted
WHERE
(ca2.deleted = ca2.deleted OR ca2.deleted IS NULL)
ORDER BY
app_created DESC
LIMIT
0, 100;
Time: 70.382
Rows: 100
The table descriptions are:
CREATE TABLE cl_application (
id int(11) NOT NULL AUTO_INCREMENT,
customer_id int(11) NOT NULL,
product_type tinytext COLLATE utf8_unicode_ci,
introducer_id int(11) NOT NULL,
loan_amount double NOT NULL,
loan_purpose tinytext COLLATE utf8_unicode_ci NOT NULL,
comments text COLLATE utf8_unicode_ci NOT NULL,
security_value double NOT NULL,
property_address_1 tinytext COLLATE utf8_unicode_ci NOT NULL,
property_address_2 tinytext COLLATE utf8_unicode_ci NOT NULL,
property_town_city tinytext COLLATE utf8_unicode_ci NOT NULL,
property_postcode varchar(8) COLLATE utf8_unicode_ci NOT NULL,
property_country tinytext COLLATE utf8_unicode_ci NOT NULL,
application_source tinytext COLLATE utf8_unicode_ci NOT NULL,
`status` enum('WK - Working Lead','APP - Application Taken','ISS - Pack Issued','HOT - Head Of Terms Sent','APU - Application underway','OFI - Offer Issued','DIP - Deal in Progress','DUS - Declined Unsecured/Trying Secured','DUG - Declined Unsecured/Trying Guarantor','COM - Completed Awaiting Payment','PAC - Paid and Completed','TD - Turned Down','DUP - Duplicate application') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'WK - Working Lead',
multi_stage_status text COLLATE utf8_unicode_ci NOT NULL,
owner_id int(11) NOT NULL,
token tinytext COLLATE utf8_unicode_ci NOT NULL,
cache_keyword text COLLATE utf8_unicode_ci NOT NULL,
old_id int(11) NOT NULL,
placed_with tinytext COLLATE utf8_unicode_ci NOT NULL,
submitted datetime NOT NULL,
`database` enum('LV','TD','CD','UL') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'LV',
snoozed datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
created datetime NOT NULL,
edited datetime NOT NULL,
deleted datetime NOT NULL,
PRIMARY KEY (id),
FULLTEXT KEY cache_keyword (cache_keyword)
) ;
CREATE TABLE cl_application_packager (
id int(11) NOT NULL AUTO_INCREMENT,
application_id int(11) NOT NULL,
packager tinytext COLLATE utf8_unicode_ci,
packager_id int(11) DEFAULT NULL,
amount double DEFAULT NULL,
`status` enum('In Progress','Declined','Accepted','Completed') COLLATE utf8_unicode_ci DEFAULT 'In Progress',
commision double DEFAULT NULL,
created datetime NOT NULL,
edited datetime NOT NULL,
deleted datetime NOT NULL,
PRIMARY KEY (id),
KEY application_id (deleted)
) ;
CREATE TABLE cl_application_business_details (
id int(11) NOT NULL AUTO_INCREMENT,
company_number tinytext COLLATE utf8_unicode_ci,
legal_structure enum('Ltd','LLP','Partnership','Sole Trader') COLLATE utf8_unicode_ci DEFAULT NULL,
incorporated date DEFAULT NULL,
created datetime NOT NULL,
edited datetime NOT NULL,
deleted datetime NOT NULL,
PRIMARY KEY (id)
) ;
CREATE TABLE cl_customer (
id int(11) NOT NULL AUTO_INCREMENT,
title enum('Mr','Mrs','Ms','Miss') COLLATE utf8_unicode_ci NOT NULL,
name_first tinytext COLLATE utf8_unicode_ci NOT NULL,
name_last tinytext COLLATE utf8_unicode_ci NOT NULL,
dob date NOT NULL,
marital_status tinytext COLLATE utf8_unicode_ci NOT NULL,
name_company tinytext COLLATE utf8_unicode_ci NOT NULL,
email tinytext COLLATE utf8_unicode_ci NOT NULL,
alt_email tinytext COLLATE utf8_unicode_ci,
contact_number tinytext COLLATE utf8_unicode_ci NOT NULL,
home_phone_number tinytext COLLATE utf8_unicode_ci NOT NULL,
work_phone_number tinytext COLLATE utf8_unicode_ci NOT NULL,
created datetime NOT NULL,
edited datetime NOT NULL,
deleted datetime NOT NULL,
PRIMARY KEY (id)
) ;
CREATE TABLE cl_customer_address (
id int(11) NOT NULL AUTO_INCREMENT,
customer_id int(11) NOT NULL,
application_id int(11) NOT NULL,
house_name tinytext COLLATE utf8_unicode_ci NOT NULL,
house_number tinytext COLLATE utf8_unicode_ci NOT NULL,
address_line_1 tinytext COLLATE utf8_unicode_ci NOT NULL,
address_line_2 tinytext COLLATE utf8_unicode_ci NOT NULL,
address_town tinytext COLLATE utf8_unicode_ci NOT NULL,
address_postcode tinytext COLLATE utf8_unicode_ci NOT NULL,
moved_in date NOT NULL,
vacated date NOT NULL DEFAULT '0000-00-00',
ptcabs tinytext COLLATE utf8_unicode_ci NOT NULL,
resident_status tinytext COLLATE utf8_unicode_ci NOT NULL,
created datetime NOT NULL,
edited datetime NOT NULL,
deleted datetime NOT NULL,
PRIMARY KEY (id),
KEY customer_id (customer_id,deleted)
) ;
mysql> describe cl_application
-> ;
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| customer_id | int(11) | NO | | NULL | |
| product_type | tinytext | YES | | NULL | |
| introducer_id | int(11) | NO | | NULL | |
| loan_amount | double | NO | | NULL | |
| loan_purpose | tinytext | NO | | NULL | |
| comments | text | NO | | NULL | |
| security_value | double | NO | | NULL | |
| property_address_1 | tinytext | NO | | NULL | |
| property_address_2 | tinytext | NO | | NULL | |
| property_town_city | tinytext | NO | | NULL | |
| property_postcode | varchar(8) | NO | | NULL | |
| property_country | tinytext | NO | | NULL | |
| application_source | tinytext | NO | | NULL | |
| status | enum('WK - Working Lead','APP - Application Taken','ISS - Pack Issued','HOT - Head Of Terms Sent','APU - Application underway','OFI - Offer Issued','DIP - Deal in Progress','DUS - Declined Unsecured/Trying Secured','DUG - Declined Unsecured/Trying Guarantor','COM - Completed Awaiting Payment','PAC - Paid and Completed','TD - Turned Down','DUP - Duplicate application') | NO | | WK - Working Lead | |
| multi_stage_status | text | NO | | NULL | |
| owner_id | int(11) | NO | | NULL | |
| token | tinytext | NO | | NULL | |
| cache_keyword | text | NO | MUL | NULL | |
| old_id | int(11) | NO | | NULL | |
| placed_with | tinytext | NO | | NULL | |
| submitted | datetime | NO | | NULL | |
| database | enum('LV','TD','CD','UL') | NO | | LV | |
| snoozed | datetime | NO | | 0000-00-00 00:00:00 | |
| created | datetime | NO | | NULL | |
| edited | datetime | NO | | NULL | |
| deleted | datetime | NO | | NULL | |
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+-----+---------------------+----------------+
27 rows in set (0.00 sec)
--
mysql> describe cl_application_business_details;
+-----------------+-----------------------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+-----------------------------------------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| company_number | tinytext | YES | | NULL | |
| legal_structure | enum('Ltd','LLP','Partnership','Sole Trader') | YES | | NULL | |
| incorporated | date | YES | | NULL | |
| created | datetime | NO | | NULL | |
| edited | datetime | NO | | NULL | |
| deleted | datetime | NO | | NULL | |
+-----------------+-----------------------------------------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
--
mysql> describe cl_customer;
+-------------------+------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | enum('Mr','Mrs','Ms','Miss') | NO | | NULL | |
| name_first | tinytext | NO | | NULL | |
| name_last | tinytext | NO | | NULL | |
| dob | date | NO | | NULL | |
| marital_status | tinytext | NO | | NULL | |
| name_company | tinytext | NO | | NULL | |
| email | tinytext | NO | | NULL | |
| alt_email | tinytext | YES | | NULL | |
| contact_number | tinytext | NO | | NULL | |
| home_phone_number | tinytext | NO | | NULL | |
| work_phone_number | tinytext | NO | | NULL | |
| created | datetime | NO | | NULL | |
| edited | datetime | NO | | NULL | |
| deleted | datetime | NO | | NULL | |
+-------------------+------------------------------+------+-----+---------+----------------+
15 rows in set (0.00 sec)
--
mysql> describe cl_application_packager;
+----------------+-------------------------------------------------------+------+-----+-------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------------------------------------------------+------+-----+-------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| application_id | int(11) | NO | | NULL | |
| packager | tinytext | YES | | NULL | |
| packager_id | int(11) | YES | | NULL | |
| amount | double | YES | | NULL | |
| status | enum('In Progress','Declined','Accepted','Completed') | YES | | In Progress | |
| commision | double | YES | | NULL | |
| created | datetime | NO | | NULL | |
| edited | datetime | NO | | NULL | |
| deleted | datetime | NO | MUL | NULL | |
+----------------+-------------------------------------------------------+------+-----+-------------+----------------+
10 rows in set (0.00 sec)
mysql> describe cl_customer_address;
+------------------+----------+------+-----+------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+----------+------+-----+------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| customer_id | int(11) | NO | MUL | NULL | |
| application_id | int(11) | NO | | NULL | |
| house_name | tinytext | NO | | NULL | |
| house_number | tinytext | NO | | NULL | |
| address_line_1 | tinytext | NO | | NULL | |
| address_line_2 | tinytext | NO | | NULL | |
| address_town | tinytext | NO | | NULL | |
| address_postcode | tinytext | NO | | NULL | |
| moved_in | date | NO | | NULL | |
| vacated | date | NO | | 0000-00-00 | |
| ptcabs | tinytext | NO | | NULL | |
| resident_status | tinytext | NO | | NULL | |
| created | datetime | NO | | NULL | |
| edited | datetime | NO | | NULL | |
| deleted | datetime | NO | | NULL | |
+------------------+----------+------+-----+------------+----------------+
16 rows in set (0.00 sec)
Explain Output:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY <derived2> ALL 178913936 Using filesort
1 PRIMARY ca2 eq_ref PRIMARY PRIMARY 4 s.address_id 1 Using where
2 DERIVED a ALL PRIMARY, cache_keyword 14494 Using where; Using temporary; Using filesort
2 DERIVED bd eq_ref PRIMARY PRIMARY 4 s-choiceloans.a.id 1
2 DERIVED c eq_ref PRIMARY PRIMARY 4 s-choiceloans.a.customer_id 1 Using where
2 DERIVED ap ALL 12344 Using where; Using join buffer (Block Nested Loop)
3 DEPENDENT SUBQUERY ca1 ref customer_id customer_id 9 func,const 1 Using where; Using filesort
After changing the query based on the answer's follow up:
https://jsbin.com/bonarucisi/2/edit?html,output
The biggest suspect is the inline view (or derived table, in the MySQL venacular.) MySQL is going to materialize the derived table, and then the outer query will run against that.
The other suspect is the correlated subquery in the SELECT list. That's going to be executed for every row returned by the outer query.
Also, the ORDER BY app_created is going to require a Using filesort operation (unless you're running a more recent version of MySQL that might build an index on the derived table.)
There's some odd predicates, e.g. c.deleted = c.deleted
That's going to be true for every row that has a non-NULL value in the deleted column. That's equivalent to c.deleted IS NOT NULL.
And for this: ca2.deleted = ca2.deleted OR ca2.deleted IS NULL, that's always going to be true.
Neither of those should have much impact on performance however.
Run EXPLAIN to see the execution plan.
Absent that, here's a first cut at some recommendations:
Add a covering index on c1_application_packager
CREATE INDEX c1_application_packager_IX2 ON c1_application_packager
(application_id, deleted, status, packager)
;
Add a covering index on cl_customer_address
CREATE INDEX c1_customer_address_IX2 ON c1_customer_address
(customer_id, deleted, created, id, address_postcode, resident_status)
;
And re-write the query to eliminate the derived table. Replace the join to ca2 with the same correlated subquery you used to return the id from c1_customer_address...
SELECT a.id
, c.name_first
, c.name_last
, a.created AS app_created
, a.edited
, a.comments AS custcomment
, c.name_company
, a.loan_amount
, a.product_type
, ap.packager AS placed_with
, a.introducer_id
, a.status
, c.contact_number
, c.email
, a.database
, bd.legal_structure
, ( SELECT ca1.id
FROM cl_customer_address ca1
WHERE ca1.customer_id = a.customer_id
AND ca1.deleted = '0000-00-00 00:00:00'
ORDER BY ca1.customer_id, ca1.deleted, ca1.created DESC
LIMIT 1
) AS address_id
, ( SELECT ca2.address_postcode
FROM cl_customer_address ca2
WHERE ca2.customer_id = a.customer_id
AND ca2.deleted = '0000-00-00 00:00:00'
ORDER BY ca2.customer_id, ca2.deleted, ca2.created DESC
LIMIT 1
) AS address_postcode
, ( SELECT ca3.resident_status
FROM cl_customer_address ca3
WHERE ca3.customer_id = a.customer_id
AND ca3.deleted = '0000-00-00 00:00:00'
ORDER BY ca3.customer_id, ca3.deleted, ca3.created DESC
LIMIT 1
) AS resident_status
FROM cl_application a
LEFT
JOIN cl_application_business_details bd
ON bd.id = a.id
LEFT
JOIN cl_customer c
ON c.id = a.customer_id
AND c.deleted IS NOT NULL
LEFT
JOIN cl_application_packager ap
ON ap.application_id = a.id
AND ap.status != 'Declined'
AND ap.deleted = '0000-00-00 00:00:00'
WHERE a.deleted = '0000-00-00 00:00:00'
GROUP BY a.created, a.id
ORDER BY a.created, a.id
Those correlated subqueries in the SELECT list are going to be executed for every row returned by the query, so that's going to be expensive.
Now we want to see if we can get an index on c1_application table that will help us avoid a Using filesort operation (to satisfy the ORDER BY and the GROUP BY clauses.)
CREATE INDEX c1_application_IX2 ON c1_application
(deleted, created, id)
;
The query relies on a MySQL-specific extension to GROUP BY behavior, not throwing an error due to non-aggregates in the SELECT list which don't appear in the GROUP BY. If there is more than one "matching" row from c1_customer or c1_application_packager, which of the rows is returned out of the GROUP BY operation is indeterminate.
No guarantee that these changes will have a positive impact on performance. (Performance could be a whole lot worse.)
Again, run EXPLAIN to see the execution plan, and adjust from there.
The next big hump is those correlated subqueries. For a shot at decent performance, it's imperative that a suitable index is available. (A proposal for a suitable covering index already specified above.)
The next cut would be eliminate the correlated subqueries from the SELECT list, If the id column from c1_customer_address was being returned as a means to get the address_postcode and resident_status, that first correlated subquery could be eliminated.
FOLLOWUP
Removing the correlated subqueries... adding an inline view lc to get the latest created date from c1_customer_address (for each customer_id), and another join to c1_customer_address to retrieve the rows with that latest created date (for each customer).
The lc inline view introduces a "derived table", which can be expensive for large sets, but this may be faster than using correlated subqueries.
SELECT a.id
, c.name_first
, c.name_last
, a.created AS app_created
, a.edited
, a.comments AS custcomment
, c.name_company
, a.loan_amount
, a.product_type
, ap.packager AS placed_with
, a.introducer_id
, a.status
, c.contact_number
, c.email
, a.database
, bd.legal_structure
, ca.id AS address_id
, ca.address_postcode
, ca.resident_status
FROM cl_application a
LEFT
JOIN cl_application_business_details bd
ON bd.id = a.id
LEFT
JOIN cl_customer c
ON c.id = a.customer_id
AND c.deleted IS NOT NULL
LEFT
JOIN cl_application_packager ap
ON ap.application_id = a.id
AND ap.status != 'Declined'
AND ap.deleted = '0000-00-00 00:00:00'
LEFT
JOIN ( SELECT ca1.customer_id
, MAX(ca1.created) AS latest_created
FROM cl_customer_address ca1
WHERE ca1.deleted = '0000-00-00 00:00:00'
GROUP BY ca1.customer_id
) lc
ON lc.customer_id = a.customer_id
LEFT
JOIN cl_customer_address ca
ON ca.customer_id = lc.customer_id
AND ca.created = lc.latest_created
AND ca.deleted = '0000-00-00 00:00:00'
WHERE a.deleted = '0000-00-00 00:00:00'
GROUP BY a.created, a.id
ORDER BY a.created, a.id

MySQL: Key/value store queries optimization

I am seeking a help with setting up correct indexes (i've tried too many and now i am a bit lost), correct MySQL engine (MyIsam, InnoDB...) and help with my queries (JOINs, ...). Also I have headache when I am thinking that these queries should return count(*).
My times were more than 5 - 10 seconds per query without counting, but I am not sure if I can get better times for this big database.
I am trying to optimize this MySQL tables:
Items (~600k rows):
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| type | varchar(255) | NO | PRI | NULL | |
+-------+------------------+------+-----+---------+----------------+
Items_Relationships (~1M rows):
+-------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| lft_item_id | int(11) unsigned | NO | PRI | NULL | |
| rgt_item_id | int(11) unsigned | NO | PRI | NULL | |
| rel_type | varchar(255) | NO | PRI | NULL | |
+-------------+------------------+------+-----+---------+-------+
Items_Values (~4M rows):
+---------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| item_id | int(11) unsigned | NO | PRI | 0 | |
| name | varchar(255) | YES | MUL | NULL | |
| value | longtext | YES | | NULL | |
| lang | varchar(2) | YES | | NULL | |
+---------+------------------+------+-----+---------+----------------+
I am running basically these common queries:
1. query - All items with value "status" > 1:
SELECT `company`.`id` AS `id`,
`company`.`type` AS `type`
FROM `items` AS `company`
INNER JOIN `items_values` AS `value_name` ON (`company`.`id` = `value_name`.`item_id`)
WHERE `company`.`type` = 'company'
AND `value_name`.`name` = 'status'
AND CONVERT(`value_name`.`value`, SIGNED) > 1
GROUP BY `company`.`id`
ORDER BY `company`.`id` DESC
LIMIT 0, 30
2. query - All items with some values in relationship with other items:
SELECT `company`.`id` AS `id`,
`company`.`type` AS `type`
FROM `items` AS `company`
INNER JOIN `items_values` AS `value_status` ON (`value_status`.`item_id` = `company`.`id`)
INNER JOIN `items_relationships` AS `companies_categories` ON (`companies_categories`.`lft_item_id` = `company`.`id`)
INNER JOIN `items_values` AS `category_rgt` ON (`category_rgt`.`item_id` = `companies_categories`.`rgt_item_id`)
WHERE `company`.`type` = 'company'
AND `company`.`type` = 'company'
AND `value_status`.`name` = 'status'
AND CONVERT(`value_status`.`value`, SIGNED) >= 1
AND `category_rgt`.`name` = 'rgt'
AND (CONVERT(category_rgt.value, UNSIGNED) BETWEEN 2805 AND 4222)
AND `companies_categories`.`rel_type` = 'company_category'
GROUP BY `company`.`id`
ORDER BY `company`.`id` DESC LIMIT 10
OFFSET 0
Thx in advance!
If you ask about indexes, so you have almost everything you need already indexed.
I have just question about
| name | varchar(255) | YES | MUL | NULL | |
So I would prefer to set it as well as primary key.
I have only one suggestion about table structures.
If you have mixed string and numbers in your
| value | longtext | YES | | NULL | |
create another column int_value SIGNED or unsigned is even better.
And you should set that column as index as well (as soon as you need that column used as a filter and/or search criteria)
And fill that field on insert/update where it is applicable.
This modification will increase performance of query where you should not use CAST and/or CONVERT for millions of records like you do here:
AND CONVERT(`value_status`.`value`, SIGNED) >= 1
AND (CONVERT(category_rgt.value, UNSIGNED) BETWEEN 2805 AND 4222)
So I have no more comments about structure.
But I would ask you to try my query, just like an experiment if it faster than yours. Unfortunately I have no possibility to debug with any data. If you provide some sqlfiddle that will help a lot.
SELECT `company`.`id` AS `id`,
`company`.`type` AS `type`
FROM `items` AS `company`
INNER JOIN (
SELECT
item_id,
FROM items_values
WHERE name = 'status'
AND CONVERT(value, SIGNED) >= 1
) AS value_status
ON value_status.item_id = company.id
INNER JOIN
(
SELECT
lft_item_id
FROM
items_relationships
INNER JOIN (
SELECT
item_id
FROM
items_values
WHERE name = 'rgt'
AND (CONVERT(value, UNSIGNED) BETWEEN 2805 AND 4222)
) AS category_rgt
ON category_rgt.item_id = items_relationships.rgt_item_id
WHERE items_relationships.rel_type = 'company_category'
) as companies_categories
ON (`companies_categories`.`lft_item_id` = `company`.`id`)
WHERE `company`.`type` = 'company'
GROUP BY `company`.`id`
ORDER BY `company`.`id` DESC
LIMIT 10

Sub-query doesn't work

Here's my schema:
Table "Questoes";
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| id_quest | int(11) | NO | | NULL | |
| questao | varchar(255) | NO | | NULL | |
| nivel | int(11) | NO | | NULL | |
| tipo | varchar(255) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
Table "Respostas";
+----------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| id_quest | int(11) | NO | | NULL | |
| resposta | varchar(255) | NO | | NULL | |
| r_valido | enum('0','1') | NO | | NULL | |
+----------+---------------+------+-----+---------+----------------+
My query is:
SELECT q.questao, r.resposta
FROM questoes q, respostas r
WHERE q.id_quest IN (19,20,21)
AND q.id_quest=r.id_quest
AND r.r_valido = ( SELECT resposta FROM respostas WHERE r_valido= 1 )
What I need is the field questao from table Questoes and the field resposta from table respostas where field r_valido = 1.
The field resposta have 4 results, and only one is valid, in other words, where the field r_valido = 1.
Your query should look like this:
SELECT q.questao, r.resposta FROM questoes AS q JOIN respostas AS r ON r.id_quest = q.id_quest WHERE q.id_quest IN (19,20,21) AND r.r_valido = "1"
Also I found out what is causing that weird error when you use 1 instead of "1" in the query:
We strongly recommend that you do not use numbers as enumeration
values, because it does not save on storage over the appropriate
TINYINT or SMALLINT type, and it is easy to mix up the strings and the
underlying number values (which might not be the same) if you quote
the ENUM values incorrectly
I didn't understood you completely but i think this is what you looking for:
SELECT q.questao, r.resposta
FROM questoes as q
INNER JOIN respostas as r
ON q.id_quest=r.id_quest
WHERE
q.id_quest IN (19,20,21) AND
r.r_valido = '1'

Shopping Cart tables - Feedback Database Design

I am developing online shopping system.
This is how the product structure work:
There are number of categories..
Each Category has number of items.
Each Item have one or more Options
An Option can have extra(s) or without extra(s)
The following tables I have:
mysql> desc categories;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| cat_id | int(11) | NO | PRI | NULL | auto_increment |
| company_id | int(11) | NO | | NULL | |
| name | varchar(100) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
Eg: 12, 2, "Google"
Items Table:
mysql> desc items;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| item_id | int(11) | NO | PRI | NULL | auto_increment |
| cat_id | int(11) | NO | | NULL | |
| name | varchar(150) | NO | | NULL | |
| description | varchar(150) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Eg: 2, 12, "Item 1", "Desc... 1"
Eg: 3, 12, "Item 2", "Desc... 2"
Options Table
mysql> desc items_options;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| option_id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | NO | | NULL | |
| name | varchar(150) | NO | | NULL | |
| price | decimal(6,2) | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
Eg: 45, 2, "Regular", "2.20"
Eg: 46, 3, "Small", "1.20"
Eg: 47, 3, "Large", "2.20"
Extras Table:
mysql> desc items_options_extras;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| extra_id | int(11) | NO | PRI | NULL | auto_increment |
| option_id | int(11) | NO | | NULL | |
| name | varchar(150) | NO | | NULL | |
| price | decimal(6,2) | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
Eg: 64, 47, "With Bag", 0.10"
Is this good database design? What could be improved?
I did not create a relationship table, is this necessary? If so, im not sure how do I create a relationship table.
At the moment I use multiple SELECT queries to get the relationship between those tables, like this below:
<?php
$q = mysql_query("SELECT item_id, name FROM items where cat_id = '3'");
while($row = mysql_fetch_assoc($q)) {
echo $row['name'];
$q2 = mysql_query("SELECT price FROM items_options_extras where item_id =" . $row['item_id']);
while($row2 = mysql_fetch_assoc($q2))
echo $row2['price'];
}
}
?>
When I want to delete an Item and including options, I use similar php code like above.
Edit: Forgot to add Options table
Edit: Updated some data example.
Consider that an Item may belong to multiple Categories.
Here are things to consider:
If the price of an item changes with the category, then the ItemPrice column goes into the CategoryItems table. If not, it goes into the Items table.
If the price of an option changes with the item, then the OptionPrice column goes into the ItemOptions table. If not, it goes into the Option table.

Categories