mysql query - not scaling well, slow with 15k records - php
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
Related
SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column
I am building an application with postgres as my preferred database. I have a users table and in the process of saving into it, I got the error below: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column \"verification_code\" violates not-null constraint\nDETAIL: Failing row contains (825, 2348035442578, S-LF79a_O3ELcNc0TQmLOjy5No0KScjT, $2y$13$tPS82tr5pH2CHfNiZ9OSKOmE/z8iXOpc0PQ0uGWmeQYXtMX9AguBq, null, oyedele.phemy#gmail.com, null, 0, 11, 2020-04-25 13:54:32, 2020-04-25 13:54:32, null, null, null, null).\nThe SQL being executed was: INSERT INTO \"users\" (\"username\", \"auth_key\", \"password_hash\", \"email\", \"isActivated\", \"status\", \"created_at\", \"updated_at\") VALUES ('2348035442578', 'S-LF79a_O3ELcNc0TQmLOjy5No0KScjT', '$2y$13$tPS82tr5pH2CHfNiZ9OSKOmE/z8iXOpc0PQ0uGWmeQYXtMX9AguBq', 'oyedele.phemy#gmail.com', 0, 11, NOW(), NOW()) RETURNING \"id\" Here is the table structure: Table "public.users" Column | Type | Collation | Nullable | Default ----------------------+--------------------------------+-----------+----------+----------------------------------- id | integer | | not null | nextval('users_id_seq'::regclass) username | character varying(255) | | not null | auth_key | character varying(32) | | not null | password_hash | character varying(255) | | not null | password_reset_token | character varying(255) | | | email | character varying(255) | | not null | verification_code | character varying(255) | | not null | isActivated | smallint | | not null | 0 status | smallint | | not null | 10 created_at | timestamp(0) without time zone | | not null | updated_at | timestamp(0) without time zone | | not null | profile_picture | character varying(55) | | | filename | character varying(70) | | | pin_no | integer | | | last_login_date | date | | | Where do you think I should check for correction or what exactly am I not doing right?
Insert trigger subtract value from another table
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 | +--------+----------+-----------+----------+------------+
Select all categories with latest post, user, and topic information
I am working on a custom forum for a web project. I have categories with id, topics with id and category, and posts with id and topic and user id. What I'm trying to do is display a list of the categories with data from the categories table along with data from the posts table for the newest post in that category, data for that posts's associated user, as well as some data for the topic associated with that latest post. I've been banging my head on the wall trying to figure the query out, but I just don't have a good enough understanding of complex mysql queries to know what pattern or technique to use here. Here is what I have so far: SELECT u1.*, fp1.*, ft1.*, fc1.* from forum_posts AS fp1 LEFT JOIN users AS u1 ON u1.id = fp1.post_by LEFT JOIN forum_topics AS ft1 on ft1.id = fp1.post_topic LEFT JOIN forum_categories AS fc1 on fc1.id = ft1.topic_cat GROUP BY fc1.id ORDER BY fp1.id ASC; But this does not return the results I'm looking for. The problem is in trying to get the newest post for each category and the associate topic for that post. Here is the DB structure for each table: forum_categories +-----------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | cat_name | varchar(255) | NO | UNI | NULL | | | cat_description | varchar(500) | NO | | NULL | | | cat_views | bigint(20) unsigned | YES | | 0 | | | status | tinyint(1) | NO | | 1 | | +-----------------+---------------------+------+-----+---------+----------------+ forum_topics +---------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | topic_subject | varchar(255) | NO | | NULL | | | topic_date | datetime | NO | | NULL | | | topic_cat | bigint(20) unsigned | NO | MUL | NULL | | | topic_by | bigint(20) unsigned | NO | MUL | NULL | | | topic_views | bigint(20) unsigned | YES | | 0 | | +---------------+---------------------+------+-----+---------+----------------+ forum_posts +--------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | post_content | text | NO | | NULL | | | post_date | datetime | NO | | NULL | | | post_topic | bigint(20) unsigned | NO | MUL | NULL | | | post_by | bigint(20) unsigned | NO | MUL | NULL | | +--------------+---------------------+------+-----+---------+----------------+ users +-----------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | user_type | varchar(50) | YES | | NULL | | | email | varchar(255) | NO | UNI | NULL | | | username | varchar(255) | YES | UNI | NULL | | | password | char(60) | NO | | NULL | | | image | text | YES | | NULL | | | status | tinyint(1) | NO | | 1 | | +-----------+---------------------+------+-----+---------+----------------+ Here is an image of the output I'm trying to achieve. "Categories" shows the data from the forum_categories table and under "Recent" is the user, post and topic data for the latest post: Please help me out. Thank you.
While it's simple enough to join all the tables together to work out the topic, category and user for each post, you also need one additional step - you need to join to a subquery that retrieves the max(post_id) per category. Here's one way you can do that: select fc.cat_name, fc.cat_description, fc.cat_views, u.username, fp.post_date, ft.topic_subject from forum_categories fc inner join forum_topics ft on fc.id = ft.topic_cat inner join forum_posts fp on fp.post_topic = ft.id inner join users u on fp.post_by = u.id inner join ( select topic_cat, max(fp.id) most_recent_post from forum_topics ft inner join forum_posts fp on fp.post_topic = ft.id group by topic_cat ) q on q.topic_cat = ft.topic_cat and fp.id = q.most_recent_post; There's a demo you can play with here: http://sqlfiddle.com/#!9/3736b/1
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'