Lots of nested queries - mysql

SELECT
productos.prod_id,
productos.prod_codigo1,
productos.prod_descripcion,
(SELECT SUM(cotdetalle.cotd_cantidad)
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1
AND cotdetalle.cotd_cote_id IN(
SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada'
AND MONTH(cotencabezado.cote_cierre) = MONTH(NOW()) AND YEAR(cotencabezado.cote_cierre) = YEAR(NOW())
)
) AS cuantos,
(SELECT SUM(cotdetalle.cotd_cantidad * cotdetalle.cotd_precio)
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1
AND cotdetalle.cotd_cote_id IN(
SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada'
AND MONTH(cotencabezado.cote_cierre) = MONTH(NOW()) AND YEAR(cotencabezado.cote_cierre) = YEAR(NOW())
)
) AS monto
FROM productos
ORDER BY monto DESC
LIMIT 0, 50
Came out with this query last night, I guess my question is: is it too much? I'm pretty sure there are other ways to get the same results without all that nested queries.. It works but it takes some time on my development environment, I bet it will take a lot more on a production environment... Any suggestions?
Please let me know if you need the table structure...
Edit: Actually that second SELECT is what bothers me most, it is exactly the same as the first one but I need that second result, but if I try to get that second result in the first SELECT it give me the "Operand should contain 1 column(s)" error...
A little more info: I need to be able to order by monto, cuantos, productos.prod_codigo1, productos.prod_descripcion
sqlfiddle.com example: http://sqlfiddle.com/#!2/c4391/1

You should do this as a simple join with a group by:
SELECT p.prod_id, p.prod_codigo1, p.prod_descripcion,
t.quantos, t.monto
FROM productos p left outer join
(SELECT cotdetalle.cotd_codigo, SUM(cotdetalle.cotd_cantidad) as quantos,
SUM(cotdetalle.cotd_cantidad * cotdetalle.cotd_precio) as monto
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1 and
cotdetalle.cotd_cote_id IN
(SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada' and
MONTH(cotencabezado.cote_cierre) = MONTH(NOW()) AND
YEAR(cotencabezado.cote_cierre) = YEAR(NOW()
)
group by cotdetalle.cotd_codigo
) t
on t.cotd_codigo = p.prod_codigo1
ORDER BY monto DESC
LIMIT 0, 50
This should improve things. However, MySQL does a poor job with IN and a subquery. So, instead of "IN" in the subquery, we want to change that to a join. Note the addition of "distinct" in the subquery. This isn't necessary for IN but it is for the join:
SELECT p.prod_id, p.prod_codigo1, p.prod_descripcion,
t.quantos, t.monto
FROM productos p join
(SELECT cd.cotd_codigo, SUM(cd.cotd_cantidad) as quantos,
SUM(cd.cotd_cantidad * cd.cotd_precio) as monto
FROM cotdetalle cd join
(SELECT distinct cc.cote_id
FROM cotencabezado cc
WHERE cc.cote_status = 'cerrada' and
MONTH(cc.cote_cierre) = MONTH(NOW()) AND
YEAR(cc.cote_cierre) = YEAR(NOW()
) cc
on cd.cotd_cote_id = cc.cote_id
group by cd.cotd_codigo
) t
on t.cotd_codigo = p.prod_codigo1
ORDER BY monto DESC
LIMIT 0, 50
I didn't test this on SQL Fiddle, so there may be syntax errors.

Why not simplify the statement as
SELECT productos.prod_id,
productos.prod_codigo1,
productos.prod_descripcion,
( SELECT SUM(cotdetalle.cotd_cantidad) AS cuantos,
SUM(cotdetalle.cotd_cantidad * cotdetalle.cotd_precio) AS monto
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1
AND cotdetalle.cotd_cote_id IN( SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada'
AND MONTH(cotencabezado.cote_cierre) = MONTH(NOW())
AND YEAR(cotencabezado.cote_cierre) = YEAR(NOW())
)
)
FROM productos
ORDER BY monto DESC
LIMIT 0, 50

Ok, it ended like this:
SELECT p.prod_id, p.prod_codigo1, p.prod_descripcion, t.cuantos, t.monto
FROM productos AS p
LEFT JOIN(
SELECT cd.cotd_codigo,
SUM(cd.cotd_cantidad) AS cuantos,
SUM(cd.cotd_cantidad * cd.cotd_precio) AS monto
FROM cotdetalle AS cd
JOIN(
SELECT DISTINCT ce.cote_id
FROM cotencabezado AS ce
WHERE ce.cote_status = 'cerrada'
AND MONTH(ce.cote_cierre) = MONTH(NOW())
AND YEAR(ce.cote_cierre) = YEAR(NOW())
) AS ce
ON cd.cotd_cote_id = ce.cote_id
GROUP BY cd.cotd_codigo
) AS t
ON cd.cotd_codigo = p.prod_codigo1
ORDER BY monto DESC
LIMIT 0, 50
This works a lot better an it's based on Gordon Linoff's suggestions, Thanks man!

Related

How to optimize the product fetching query

I have used below query for product listing. Query is working fine but it takes approximately 0.4534 seconds. How can I optimize the same query.
SELECT SQL_CALC_FOUND_ROWS DISTINCT tp.prod_id, tp.prod_name, tp.prod_shop, tp.prod_retail_price, tp.prod_sale_price, tp.prod_initial_price, tp.prod_stock, ts.shop_id, ts.shop_name, ts.shop_logo, ts.shop_description, ts.shop_title, tu.user_profile_image, ( SELECT pdiscount_price FROM tbl_product_discounts tpd WHERE tpd.pdiscount_product_id = tp.prod_id AND tpd.pdiscount_qty = '1' AND( ( tpd.pdiscount_start_date = '0000-00-00' OR tpd.pdiscount_start_date < NOW()) AND( tpd.pdiscount_end_date = '0000-00-00' OR tpd.pdiscount_end_date > NOW()) ) ORDER BY tpd.pdiscount_priority ASC, tpd.pdiscount_price ASC LIMIT 1 ) AS discount FROM tbl_products tp LEFT JOIN tbl_shops ts ON tp.prod_shop = ts.shop_id AND ts.shop_is_deleted = 0 INNER JOIN tbl_users tu ON ts.shop_user_id = tu.user_id WHERE tp.prod_is_deleted = '0' LIMIT 0, 20
Without checking you table or Requirement :
Try to use group by instead of DISTINCT
Do not use sub query if possible .
Try To use indexing in you table .
This will help you to optimize you query.

Indexing Query In MySql Workbench

I need to write a query which involves subquery and do indexing. So, I came up with a query to find the team name which has the Highest build up speed in a year:
SELECT CONCAT(team_long_name, ',', team_short_name) AS TeamName,
EXTRACT(YEAR FROM date) AS Year,
buildUpPlaySpeed
FROM team JOIN
team_attributes
ON team.team_api_id = team_attributes.team_api_id
WHERE
(buildUpPlaySpeed,team_attributes.date) in (SELECT
MAX(buildUpPlaySpeed),team_attributes.date
FROM team_attributes
WHERE team_attributes.date = team_attributes.date
group by team_attributes.date)
ORDER BY date desc;
Indexes are present on date,team_api_id,buildUpPlaySpeed columns.
Any suggestions on how to reduce the cost further??
Yes. Replace the correlated subquery with a join:
SELECT CONCAT (
team_long_name
,','
,team_short_name
) AS TeamName
,EXTRACT(YEAR FROM [DATE]) AS Year
,buildUpPlaySpeed
FROM team
INNER JOIN team_attributes ON team.team_api_id = team_attributes.team_api_id
INNER JOIN (
SELECT MAX(buildUpPlaySpeed) maxbuildup
,team_attributes.[DATE]
FROM team_attributes
WHERE team_attributes.[DATE] = team_attributes.DATE
GROUP BY team_attributes.[DATE]
) x ON buildUpPlaySpeed = x.maxbuildup
AND team_attributes.DATE = x.[DATE]
ORDER BY [DATE] DESC
;
and, for sanity's sake please don't name columns "date" because that's used by SQL. Very confusing.
try this also
SELECT CONCAT(team_long_name,',',team_short_name) AS TeamName,EXTRACT(YEAR FROM DATE) AS Year,buildUpPlaySpeed
FROM team
INNER JOIN team_attributes ON team.team_api_id = team_attributes.team_api_id
INNER JOIN ( SELECT MAX(buildUpPlaySpeed) maxbuildup,team_attributes.DATE FROM team_attributes
WHERE team_attributes.DATE = team_attributes.DATE
GROUP BY team_attributes.DATE ) t1
where buildUpPlaySpeed = t1.maxbuildup AND team_attributes.DATE = t1.DATE ORDER BY DATE DESC;

MySQL join on substring is slow

I have a query where I do a join on a substring, the problem is that this is really slow to complete. Is there a more effecient way to write this?
SELECT *, SUM(s.pris*s.antall) AS total, SUM(s.antall) AS antall
FROM ecs_statistikk AS s
JOIN butikk_ordre AS bo ON ordreId=bo.ecs_ordre_id AND butikkNr=bo.site_id
JOIN ecs_supplier AS l ON SUBSTRING( s.artikkelId, 1,2 )=l.lev_id
WHERE s.salgsDato>='2016-6-01' AND s.salgsDato<='2016-09-30'
GROUP BY l.lev_id ORDER BY total DESC
First, I would check indexes. For this query:
SELECT *, SUM(s.pris*s.antall) AS total, SUM(s.antall) AS antall
FROM ecs_statistikk s JOIN
butikk_ordre bo
ON s.ordreId = bo.ecs_ordre_id AND
s.butikkNr = bo.site_id JOIN
ecs_supplier l
ON SUBSTRING(s.artikkelId, 1, 2 ) = l.lev_id
WHERE s.salgsDato >= '2016-06-01' AND s.salgsDato <= '2016-09-30'
GROUP BY l.lev_id
ORDER BY total DESC ;
You want indexes on ecs_statistikk(salgsDato, ordreId, butikkNr, artikkelId), butikk_ordre(ecs_ordre_id, site_id), and ecs_supplier(lev_id)`.
Next, I would question whether you need the last JOIN at all. Does this do what you want?
SELECT LEFT(s.artikkelId, 2) as lev_id, *,
SUM(s.pris*s.antall) AS total, SUM(s.antall) AS antall
FROM ecs_statistikk s JOIN
butikk_ordre bo
ON s.ordreId = bo.ecs_ordre_id AND
s.butikkNr = bo.site_id
WHERE s.salgsDato >= '2016-06-01' AND s.salgsDato <= '2016-09-30'
GROUP BY LEFT(s.artikkelId, 2)
ORDER BY total DESC ;

2 requests in only one using mysql

I have two requests
UPDATE :
I need to do something like that :
SELECT poste_nom, ups_type_contrat,
(SELECT `entpro_date`
FROM ENT_PRO
WHERE entpro_user_id = 2
ORDER BY `entpro_id` DESC
LIMIT 1) ,
serv_nom,
serv_id_resp,
user_credit_cpf,
user_indice_salarial,
FLOOR( DATEDIFF( CURDATE( ) , user_dateentree ) /365 ) AS dateEntree
FROM USER
INNER JOIN USER_POSTE_SERVICE
ON USER.user_id= USER_POSTE_SERVICE.ups_poste_id
INNER JOIN POSTE
ON USER_POSTE_SERVICE. ups_poste_id = POSTE.poste_id
INNER JOIN SERVICE
ON USER_POSTE_SERVICE.ups_id_serv = SERVICE.serv_id
WHERE user_id = 2
ORDER BY user_nom ASC
Is it possible to gather two requests in only one ?
From what I understood you want to simple merge the result of your sub-query to your main SELECT, if so you could try it this way:
SELECT poste_nom,
ups_type_contrat,
ENT_PRO_RESULT.entpro_date,
serv_nom,
serv_id_resp,
user_credit_cpf,
user_indice_salarial,
FLOOR( DATEDIFF( CURDATE( ) , user_dateentree ) /365 ) AS dateEntree
FROM USER
LEFT JOIN (SELECT entpro_date,
entpro_user_id
FROM ENT_PRO
ORDER BY entpro_id DESC
LIMIT 1) ENT_PRO_RESULT
ON USER.user_id = ENT_PRO_RESULT.entpro_user_id
INNER JOIN USER_POSTE_SERVICE
ON USER.user_id = USER_POSTE_SERVICE.ups_poste_id
INNER JOIN POSTE
ON USER_POSTE_SERVICE.ups_poste_id = POSTE.poste_id
INNER JOIN SERVICE
ON USER_POSTE_SERVICE.ups_id_serv = SERVICE.serv_id
WHERE user_id = 2
ORDER BY user_nom ASC
I've joined it on:
ON USER.user_id = ENT_PRO_RESULT.entpro_user_id
So you only need to specify the:
WHERE user_id = 2
And the sub-query will use the current row user id for the LEFT JOIN.

Join between sub-queries in SQLAlchemy

In relation to the answer I accepted for this post, SQL Group By and Limit issue, I need to figure out how to create that query using SQLAlchemy. For reference, the query I need to run is:
SELECT t.id, t.creation_time, c.id, c.creation_time
FROM (SELECT id, creation_time
FROM thread
ORDER BY creation_time DESC
LIMIT 5
) t
LEFT OUTER JOIN comment c ON c.thread_id = t.id
WHERE 3 >= (SELECT COUNT(1)
FROM comment c2
WHERE c.thread_id = c2.thread_id
AND c.creation_time <= c2.creation_time
)
I have the first half of the query, but I am struggling with the syntax for the WHERE clause and how to combine it with the JOIN. Any one have any suggestions?
Thanks!
EDIT: First attempt seems to mess up around the .filter() call:
c = aliased(Comment)
c2 = aliased(Comment)
subq = db.session.query(Thread.id).filter_by(topic_id=122098).order_by(Thread.creation_time.desc()).limit(2).offset(2).subquery('t')
subq2 = db.session.query(func.count(1).label("count")).filter(c.id==c2.id).subquery('z')
q = db.session.query(subq.c.id, c.id).outerjoin(c, c.thread_id==subq.c.id).filter(3 >= subq2.c.count)
this generates the following SQL:
SELECT t.id AS t_id, comment_1.id AS comment_1_id
FROM (SELECT count(1) AS count
FROM comment AS comment_1, comment AS comment_2
WHERE comment_1.id = comment_2.id) AS z, (SELECT thread.id AS id
FROM thread
WHERE thread.topic_id = :topic_id ORDER BY thread.creation_time DESC
LIMIT 2 OFFSET 2) AS t LEFT OUTER JOIN comment AS comment_1 ON comment_1.thread_id = t.id
WHERE z.count <= 3
Notice the sub-query ordering is incorrect, and subq2 somehow is selecting from comment twice. Manually fixing that gives the right results, I am just unsure of how to get SQLAlchemy to get it right.
Try this:
c = db.aliased(Comment, name='c')
c2 = db.aliased(Comment, name='c2')
sq = (db.session
.query(Thread.id, Thread.creation_time)
.order_by(Thread.creation_time.desc())
.limit(5)
).subquery(name='t')
sq2 = (
db.session.query(db.func.count(1))
.select_from(c2)
.filter(c.thread_id == c2.thread_id)
.filter(c.creation_time <= c2.creation_time)
.correlate(c)
.as_scalar()
)
q = (db.session
.query(
sq.c.id, sq.c.creation_time,
c.id, c.creation_time,
)
.outerjoin(c, c.thread_id == sq.c.id)
.filter(3 >= sq2)
)