How do I optimize this mysql query with nested joins? - mysql

Below is my query which I need to optimize.
SELECT
UPPER(IFNULL(op.supplier_payment_method,s.default_payment_method)) AS Payment_Method,
op.supplier_payment_date AS Payment_Date,
Date_format(IFNULL(op.supplier_payment_date,op.ship_date),'%M %Y') AS Payment_Month,
s.supplier_name AS Farm,
op.sub_order_id AS Order_num,
Date_format(op.ship_date,'%b-%d-%Y') AS Ship_Date,
op.farm_credit AS Farm_Credit,
op.credit_memo AS Credit_Memo,
op.credit_description AS Credit_Description,
opb.boxes AS Box_Type,
CONCAT('$',FORMAT(op.box_charge,2)) AS Box_Charge,
op.invoice_num AS Invoice_num,
CONCAT('$',FORMAT(op.invoice_amt,2)) AS Invoice_Amt,
CONCAT('$',FORMAT(op.total_invoice_amt,2)) AS Total_Invoice_Amt,
CONCAT(op.UM_qty,' ',op.UM_type) AS St_Bu_Qty,
op.PO_Product_Name AS Invoice_desc,
CONCAT('$',FORMAT((op.price_um*op.um_qty),2)) AS Cost_product_cms,
op.supplier_invoice_note AS Supplier_Invoice_Notes,
CONCAT('$',FORMAT(op.cms_invoice_cost,2)) AS CMS_Invoice_diff,
CONCAT('$',FORMAT(op.total_farm_cost,2)) AS Farm_Cost
FROM
orders_products op
INNER JOIN
suppliers s ON s.supplier_id = op.supplier_name
LEFT JOIN
(
SELECT
sub_order_id,
GROUP_CONCAT(CONCAT(box_type_qty,' ',bo.box_option_name) SEPARATOR ', ') AS boxes
FROM
order_products_boxes opb
INNER JOIN box_options bo ON bo.id=opb.box_type_id
GROUP BY
opb.sub_order_id
) opb ON opb.sub_order_id = op.sub_order_id
WHERE
op.order_active=0
AND op.ship_date>='2013-03-01'
AND op.ship_date<='2013-04_01'
ORDER BY op.ship_date DESC
As you can see, the query comprises of 4 tables, each containing around 20k-30k rows each. So as soon as I add in the sub-query, the query becomes exceptionally slow. It takes approx 1.5mins to fetch just 500 rows of record. Is there a way to speed things up within a single query?

Your left-join query that pre-concatenates the sub-box options is querying the ENTIRE DATABASE FIRST, then only joins to those that are within the criteria you are limiting for the outer WHERE clause. It may be a little more effort, but pull the same outer criteria into your inner query should significantly help. As for doing a field-level sql-select on a per-every-row basis could be a performance killer.
You should only need to change at the "FROM" clause...
from
orders_products op
INNER JOIN suppliers s
ON op.supplier_name = s.supplier_id
LEFT JOIN
( SELECT
opb2.sub_order_id,
GROUP_CONCAT(CONCAT(box_type_qty,' ',bo.box_option_name) SEPARATOR ', ') AS boxes
FROM
orders_products op2
JOIN order_products_boxes opb2
on op2.sub_order_id = opb2.sub_order_id
INNER JOIN box_options bo
ON opb2.box_type_id = bo.id
WHERE
op2.order_active = 0
AND op2.ship_date >= '2013-03-01'
AND op2.ship_date <= '2013-04_01'
GROUP BY
opb2.sub_order_id ) opb
ON op.sub_order_id = opb.sub_order_id
WHERE
op.order_active = 0
AND op.ship_date >= '2013-03-01'
AND op.ship_date <= '2013-04_01'

SELECT
UPPER(IFNULL(op.supplier_payment_method,s.default_payment_method)) AS Payment_Method,
op.supplier_payment_date AS Payment_Date,
Date_format(IFNULL(op.supplier_payment_date,op.ship_date),'%M %Y') AS Payment_Month,
s.supplier_name AS Farm,
op.sub_order_id AS Order_num,
Date_format(op.ship_date,'%b-%d-%Y') AS Ship_Date,
op.farm_credit AS Farm_Credit,
op.credit_memo AS Credit_Memo,
op.credit_description AS Credit_Description,
(
SELECT
GROUP_CONCAT(CONCAT(box_type_qty,' ',bo.box_option_name) SEPARATOR ', ')
FROM
order_products_boxes opb
INNER JOIN box_options bo ON bo.id=opb.box_type_id
GROUP BY
opb.sub_order_id
) AS Box_Type,
CONCAT('$',FORMAT(op.box_charge,2)) AS Box_Charge,
op.invoice_num AS Invoice_num,
CONCAT('$',FORMAT(op.invoice_amt,2)) AS Invoice_Amt,
CONCAT('$',FORMAT(op.total_invoice_amt,2)) AS Total_Invoice_Amt,
CONCAT(op.UM_qty,' ',op.UM_type) AS St_Bu_Qty,
op.PO_Product_Name AS Invoice_desc,
CONCAT('$',FORMAT((op.price_um*op.um_qty),2)) AS Cost_product_cms,
op.supplier_invoice_note AS Supplier_Invoice_Notes,
CONCAT('$',FORMAT(op.cms_invoice_cost,2)) AS CMS_Invoice_diff,
CONCAT('$',FORMAT(op.total_farm_cost,2)) AS Farm_Cost
FROM
orders_products op
INNER JOIN
suppliers s ON s.supplier_id = op.supplier_name
LEFT JOIN
order_products_boxes opb (
INNER JOIN box_options bo ON bo.id=opb.box_type_id
) opb ON opb.sub_order_id = op.sub_order_id
WHERE
op.order_active=0
AND op.ship_date>='2013-03-01'
AND op.ship_date<='2013-04_01'
ORDER BY op.ship_date DESC

Related

a sum() function with aritmatich 4 table

this is sample data in table pengiriman_supply.
and this is for data_barang
this is for data_supplier and table masuk.
if I'm not using 3 tables the sum is no a problem but if I'm using 4 tables and using subtraction with (sum(table1.a)-ifnull(table2.b)). here is the result with just sum
and this is the picture with subtraction
the code is like this
SELECT DISTINCT
row_number() over(
order by pengiriman_supply.po_nomor desc) as no,
pengiriman_supply.po_nomor as PO,
data_supplier.nama_supplier,
data_barang.nama_barang,
((sum( pengiriman_supply.jumlah ))- (sum( COALESCE ( masuk.terima, 0 )) over ( PARTITION BY masuk.refrence ))) as total
FROM
pengiriman_supply
LEFT JOIN masuk ON pengiriman_supply.po_nomor = masuk.refrence
INNER JOIN data_supplier ON data_supplier.id_supplier = pengiriman_supply.idsupplier
INNER JOIN data_barang ON data_barang.idbarang = pengiriman_supply.idbarang
WHERE
pengiriman_supply.tanggal between date_sub(curdate(), interval 60 day) and curdate()
GROUP BY
pengiriman_supply.po_nomor,masuk.po_nomor,data_supplier.nama_supplier
ORDER BY
GROUP_CONCAT(DISTINCT pengiriman_supply.po_nomor) DESC
this the code that SQL statement that I can find. but the group by not make the SQL statement just pengiriman_supply.po_nomor. can I make the group by just the pengiriman_supply.po_nomor .
can the number 31194 make in one group?
it seems you need to include ifnull(masuk.terima,0) inside sum()
SELECT
pengiriman_supply.po_nomor AS po,
data_supplier.nama_supplier,
data_barang.nama_barang,
Sum((pengiriman_supply.jumlah)-ifnull(masuk.terima,0)) as total
FROM
pengiriman_supply
INNER JOIN data_barang ON pengiriman_supply.idbarang = data_barang.idbarang
INNER JOIN data_supplier ON pengiriman_supply.idsupplier = data_supplier.id_supplier
LEFT JOIN masuk ON masuk.refrence = pengiriman_supply.po_nomor
GROUP BY
pengiriman_supply.po_nomor
ORDER BY
po DESC

What is slowing down this product search MySQL query?

We are using the OpenCart ecommerce platform running on PHP 7.2 with MySQL 5.7.27, with about 5000 products.
We use an extension to search through products in the admin panel and it takes about 70-80 seconds on average to execute the search query.
Raw query:
SELECT
SQL_CALC_FOUND_ROWS pd.*,
p.*,
(
SELECT
price
FROM
product_special
WHERE
product_id = p.product_id
AND
(
date_start = '0000-00-00'
OR date_start < NOW()
AND
(
date_end = '0000-00-00'
OR date_end > NOW()
)
)
ORDER BY
priority,
price LIMIT 1
)
AS special_price,
IF(p.image IS NOT NULL
AND p.image <> ''
AND p.image <> 'no_image.png', 'Igen', 'Nem') AS image_text,
IF(p.status, 'Engedélyezett', 'Letiltott') AS status_text,
GROUP_CONCAT(DISTINCT CONCAT_WS(' > ', fgd.name, fd.name)
ORDER BY
CONCAT_WS(' > ', fgd.name, fd.name) ASC SEPARATOR '
') AS filter_text, GROUP_CONCAT(DISTINCT fd.filter_id ORDER BY CONCAT_WS(' > ', fgd.name, fd.name) ASC SEPARATOR '_') AS filter, GROUP_CONCAT(DISTINCT cat.name ORDER BY cat.name ASC SEPARATOR ' ') AS category_text, GROUP_CONCAT(DISTINCT cat.category_id ORDER BY cat.name ASC SEPARATOR '_') AS category, GROUP_CONCAT(DISTINCT IF(p2s.store_id = 0, 'ButopĂȘa HU', s.name) SEPARATOR ' ') AS store_text, GROUP_CONCAT(DISTINCT p2s.store_id SEPARATOR '_') AS store FROM product p LEFT JOIN product_description pd ON (p.product_id = pd.product_id AND pd.language_id = '2') LEFT JOIN product_to_category p2c ON (p.product_id = p2c.product_id) LEFT JOIN (SELECT cp.category_id AS category_id, GROUP_CONCAT(cd1.name ORDER BY cp.level SEPARATOR ' > ') AS name FROM category_path cp LEFT JOIN category c ON (cp.path_id = c.category_id) LEFT JOIN category_description cd1 ON (c.category_id = cd1.category_id) LEFT JOIN category_description cd2 ON (cp.category_id = cd2.category_id) WHERE cd1.language_id = '2' AND cd2.language_id = '2' GROUP BY cp.category_id ORDER BY name) AS cat ON (p2c.category_id = cat.category_id) LEFT JOIN product_to_category p2c2 ON (p.product_id = p2c2.product_id) LEFT JOIN product_filter p2f ON (p.product_id = p2f.product_id) LEFT JOIN filter f ON (f.filter_id = p2f.filter_id) LEFT JOIN filter_description fd ON (fd.filter_id = p2f.filter_id AND fd.language_id = '2') LEFT JOIN filter_group_description fgd ON (f.filter_group_id = fgd.filter_group_id AND fgd.language_id = '2')
LEFT JOIN
product_filter p2f2
ON (p.product_id = p2f2.product_id)
LEFT JOIN
product_to_store p2s
ON (p.product_id = p2s.product_id)
LEFT JOIN
store s
ON (s.store_id = p2s.store_id)
LEFT JOIN
product_to_store p2s2
ON (p.product_id = p2s2.product_id)
GROUP BY
p.product_id
ORDER BY
pd.name ASC LIMIT 0,
190
I tried using MySQL's EXPLAIN functionality to see what's going on, but nothing catches my attention right away:
My test environment is running on Intel NVME, 2666 MHz DDR4 RAM, and i7 8th gen. CPU, and yet it's still very slow.
I appreciate any hints as to what is slowing this query down.
It looks like some many-to-many mappings being used in that SELECT. WooCommerce (and the underlying Wordpress) have an inefficient way of implementing such.
Here is my discussion of how to change the schema to improve performance of wp_postmeta and similar tables (product_to_category and product_to_store): http://mysql.rjweb.org/doc.php/index_cookbook_mysql#speeding_up_wp_postmeta
This may be a bug:
( date_start = '0000-00-00'
OR date_start < NOW()
AND ( date_end = '0000-00-00'
OR date_end > NOW() )
)
You probably wanted extra parentheses:
( ( date_start = '0000-00-00'
OR date_start < NOW() )
AND ( date_end = '0000-00-00'
OR date_end > NOW() )
)
Also,
( date_start = '0000-00-00' OR date_start < NOW() )
can be simplified to just
( date_start < NOW() )
And, the query seems to have the explode-implode syndrome where the JOINs expand to generate a large temp table, only to have the GROUP BY collapse down to the original size. The workaround is to turn
GROUP_CONCAT(... foo.x ...) AS blah.
...
LEFT JOIN foo ... ON ...
into
( SELECT GROUP_CONCAT(... foo.x ...) FROM foo WHERE ... ) AS blah,
If that eliminates all the LEFT JOINs, then the GROUP BY p.product_id can also be eliminated.
Do not say LEFT JOIN when the 'right' table is not optional. (Instead, say JOIN.)
LEFT JOIN category_description cd1 ON (cp.category_id = cd1.category_id)
WHERE cd1.language_id = '2' -- this invalidates the `LEFT`
cd2 seems not to be used except for checking that cd2.language_id = '2'. Consider removing references to it.
This requires two temp tables since they are different:
GROUP BY p.product_id
ORDER BY pd.name ASC
Am I correct in saying that pd.name is simply the name for p.product_id in 'language' 2? If so, this may be semantically the same, but faster because of eliminating a temp table and sort):
GROUP BY pd.name
ORDER BY pd.name
Once that is done, it may be better to have INDEX(language_id, name) on pd.
The speedup from LIMIT 0, 190 is mostly eliminated by SQL_CALC_FOUND_ROWS.
Over-normalization led to having the two components of this in separate tables?
CONCAT_WS(' > ', fgd.name, fd.name)

How can I merge these SQL queries

I need to merge this query
SELECT
*,
(SELECT
CONCAT(c.firstname, ' ', c.lastname)
FROM
wwwpser_customer c
WHERE
c.customer_id = o.customer_id) AS customer
FROM
wwwpser_order o
WHERE
o.order_id = '20'
with
SELECT
orders . *, wwwpser_comuna.provincia_id AS payment_provincia_id
FROM
wwwpser_order orders
LEFT JOIN
wwwpser_comuna ON (orders.payment_city = wwwpser_comuna.comuna_id)
SQL syntax is new for me so I need a little help with this, thx
SELECT CONCAT(c.firstname, ' ', c.lastname)
, wc.provincia_id
, o.*
FROM wwwpser_order o
LEFT JOIN
wwwpser_customer c
ON c.customer_id = o.customer_id
LEFT JOIN
wwwpser_comuna wc
ON wc.comuna_id = o.payment_city
WHERE o.order_id = 20
It looks like this should be your solution.
SELECT
o.*, -- modified (qualified the
(SELECT -- asterisk)
CONCAT(c.firstname, ' ', c.lastname)
FROM
wwwpser_customer c
WHERE
c.customer_id = o.customer_id) AS customer,
c.provincia_id AS payment_provincia_id -- added from second query
FROM
wwwpser_order o
LEFT JOIN -- added from second query
wwwpser_comuna c ON (o.payment_city = c.comuna_id) -- added from second query
WHERE
o.order_id = '20'
I strongly recommend learning about the general SQL syntax, though. There are some useful examples in the MySQL tutorials

Mysql max date within a subquery

i cant work out how to get this subquery working to get the latest date for the row.
SELECT Thread_Heading.*, Thread_Articles.*, Thread_ArticlesPost.*
FROM Thread_Heading
LEFT JOIN Thread_Articles
ON Thread_Articles.Thread_Article_Head_id=Thread_Heading.Thread_Head_id
LEFT JOIN
(
SELECT Thread_ArticlesPost.*
FROM Thread_ArticlesPost
ORDER BY Thread_ArticlePost_DT DESC
) Thread_ArticlesPost
ON Thread_ArticlesPost.Thread_ArticlePost_Article_id=Thread_Articles.Thread_Article_id
WHERE Thread_Head_Level = '5'
GROUP BY Thread_Heading.Thread_Head_id
ORDER BY Thread_ArticlePost_DT DESC
I need to order the article post date by the latest post for each Thread article.
Fairly new to sql and php i just cant work this one out any help would be appreciated.
T
ABLE: Thread_Heading |
Thread_Head_id
Thread_Head_Name
Thread_Head_Type
Thread_Head_Creator
Thread_Head_Date
Thread_Head_Level
TABLE: Thread_Articles |
Thread_Article_id
Thread_Article_Head_id
Thread_Article_Creator
Thread_Article_DT
Thread_Article_Level
Thread_Article_Type
Thread_Article_Title
TABLE: Thread_ArticlesPost |
Thread_ArticlePost_id
Thread_ArticlePost_Head_id
Thread_ArticlePost_Article_id
Thread_ArticlePost_Creator
Thread_ArticlePost_DT
Thread_ArticlePost_Level
Thread_ArticlePost_Type
Thread_ArticlePost_Title
Thread_ArticlePost_Content
I need to display the date like so:
Head Name | Article title | ORDER BY LATEST ArticlePost DT | ArticlePost Creator
The reason i used left joins was to get the left data even if there are no article or article replys.
Appreciate the help.
Have used this to display data Thanks to Blue
SELECT *
FROM
( SELECT th.Thread_Head_Name,
ta.,
tp1.maxdate,
tp2.
FROM Thread_Heading th
LEFT JOIN Thread_Articles ta
ON th.Thread_Head_id = ta.Thread_Article_Head_id
LEFT JOIN
(
SELECT max(Thread_ArticlePost_DT) maxDate,
Thread_ArticlesPost.*
FROM Thread_ArticlesPost
GROUP BY Thread_ArticlePost_Article_id
) tp1
ON tp1.Thread_ArticlePost_Article_id=ta.Thread_Article_id
LEFT JOIN Thread_ArticlesPost tp2
ON tp1.Thread_ArticlePost_Article_id = tp2.Thread_ArticlePost_Article_id
AND tp1.maxdate = tp2.Thread_ArticlePost_DT
WHERE th.Thread_Head_Level = '5'
ORDER BY tp1.maxdate DESC) m
GROUP BY Thread_Head_Name
ORDER BY Thread_ArticlePost_DT DESC
Without seeing your full table schemas you will want do something similar to this:
SELECT th.*, ta.*, tp.*
FROM Thread_Heading th
LEFT JOIN Thread_Articles ta
ON th.Thread_Head_id = ta.Thread_Article_Head_id
LEFT JOIN
(
SELECT max(Thread_ArticlePost_DT) maxDate, Thread_ArticlesPost.*
FROM Thread_ArticlesPost
GROUP BY Thread_ArticlePost_Article_id
) Thread_ArticlesPost tp
ON tp.Thread_ArticlePost_Article_id=ta.Thread_Article_id
WHERE Thread_Head_Level = '5'
GROUP BY th.Thread_Head_id
ORDER BY tp.maxdate DESC
Based on your edit the following should return the data you want:
SELECT th.Thread_Head_Name,
ta.Thread_Article_Title,
tp1.maxdate,
tp2.Thread_ArticlePost_Creator
FROM Thread_Heading th
LEFT JOIN Thread_Articles ta
ON th.Thread_Head_id = ta.Thread_Article_Head_id
LEFT JOIN
(
SELECT max(Thread_ArticlePost_DT) maxDate,
Thread_ArticlesPost.Thread_ArticlePost_Article_id
FROM Thread_ArticlesPost
GROUP BY Thread_ArticlePost_Article_id
) tp1
ON tp1.Thread_ArticlePost_Article_id=ta.Thread_Article_id
LEFT JOIN Thread_ArticlesPost tp2
ON tp1.Thread_ArticlePost_Article_id = tp2.Thread_ArticlePost_Article_id
AND tp1.maxdate = tp2.Thread_ArticlePost_DT
WHERE th.Thread_Head_Level = '5'
ORDER BY tp1.maxdate DESC
Edit #2, based on your comments, I think the below query should resolve any remaining issues:
SELECT th.Thread_Head_id,
th.Thread_Head_Name,
ta.Thread_Article_Title,
tp.Thread_ArticlePost_Creator,
tap.MaxPostDate
FROM Thread_Heading th
LEFT JOIN
(
SELECT max(ta.Thread_Article_DT) MaxArticleDate,
ta.Thread_Article_Head_id,
max(tp.Thread_ArticlePost_DT) MaxPostDate
FROM Thread_Articles ta
LEFT JOIN Thread_ArticlesPost tp
ON ta.Thread_Article_id = tp.Thread_ArticlePost_Article_id
GROUP BY Thread_Article_Head_id
) tap
ON th.Thread_Head_id = tap.Thread_Article_Head_id
LEFT JOIN Thread_Articles ta
ON tap.Thread_Article_Head_id = ta.Thread_Article_Head_id
AND tap.MaxArticleDate = ta.Thread_Article_DT
LEFT JOIN Thread_ArticlesPost tp
ON tap.MaxPostDate = tp.Thread_ArticlePost_DT
WHERE th.Thread_Head_Level = '5'
ORDER BY MaxPostDate desc
See SQL Fiddle With Demo
You don't have to have a GROUP BY clause outside side your post are already grouped on the subquery based on your latest post. Try this one,
SELECT a.*, b.*, c.*
FROM Thread_Heading a
LEFT JOIN Thread_Articles b
ON b.Thread_Article_Head_id = a.Thread_Head_id
LEFT JOIN Thread_ArticlesPost c
ON c.Thread_ArticlePost_Article_id = b.Thread_Article_id
LEFT JOIN
(
SELECT Thread_ArticlePost_Article_id, MAX(Thread_ArticlePost_DT) maxDate
FROM Thread_ArticlesPost
GROUP BY Thread_ArticlePost_Article_id
) d ON d.Thread_ArticlePost_Article_id = b.Thread_Article_id AND
c.Thread_ArticlePost_DT = d.maxDate
WHERE Thread_Head_Level = '5'
-- GROUP BY a.Thread_Head_id
ORDER BY Thread_ArticlePost_DT DESC

MySQL: "Ignore" if a table row is missing during JOIN

I'm doing a LEFT JOIN on three tables, where the table "time" doesn't necessarily contain any matching rows. But if no matching rows is found in that table, the linked data disappears.
SELECT
w.date AS worker_date,
w.name AS worker_name,
w.address AS worker_address,
w.zip AS worker_zip,
w.place AS worker_place,
w.phone AS worker_phone,
w.email AS worker_email,
w.company AS worker_company,
w.accessibility AS worker_accessibility,
c.date AS client_date,
c.name AS client_name,
c.address AS client_address,
c.zip AS client_zip,
c.place AS client_place,
c.phone AS client_phone,
c.email AS client_email,
c.web AS client_web,
c.contact AS client_contact,
j.date AS job_date,
j.client_id,
j.worker_id,
j.name AS job_name,
j.description AS job_description,
j.type AS job_type,
j.status AS job_status,
j.proof AS job_proof,
j.deadline AS job_deadline,
j.price AS job_price,
j.final_client AS job_final_client,
SUM(t.hours) AS time_hours
FROM
jobs AS j
LEFT JOIN (
workers AS w,
clients AS c,
time AS t
) ON (
w.id = j.worker_id AND
c.id = j.client_id AND
j.id = t.job_id
) GROUP BY
j.id;
How can I make this work?
Thank you in advance.
add
WHERE t.job_id IS NOT NULL before GROUP BY
Try Replace
SUM(t.hours) AS time_hours
to
(SELECT IFNULL(SUM(t.hours),0) FROM time WHERE time.job_id=j.job_id) AS time_hours
And remove the time from the join
I think your basic query is correct (with the join under braces)
Just replace
SUM(t.hours) AS time_hours
with
SUM(if(t.hours is NULL,0,t.hours)) AS time_hours
I am not sure if this is the problem here, but the behavior of commas vs JOINs changed after a certain MySQL version. Try this
...
FROM jobs AS j LEFT JOIN workers AS w ON w.id = j.worker_id
LEFT JOIN clients AS c c.id = j.client_id
LEFT JOIN `time` AS t ON j.id = t.job_id
...
Also modify the SUM with IFNULL as #ajreal suggests.