MySQL query with many joins - mysql

I have query with many joins and I'm searching for optimization for it.
It's about computers:
For examples I have:
Lenovo 8gbRAM 1TB core i5 ips etc. (all these after brand name are attributes)
I have configuration where, I want to change attribute 8gbRAM to 16gbRAM and I have to search for other item with all these attributes and 16gbRAM
Two tables:
**st_item**
- id
- name
...
**st_item_specification_attribute**
- id
- st_item_id
- attribute_id
- attribute_value_id
...
My problem is that my item has 15 attributes. When I tested with lower number of attributes I use this structure of query and it works, but now system has 85k items and over 1kk item attributes
This is the query:
SELECT `st_item`.id FROM `st_item`
LEFT JOIN `st_item_specification_attribute` `sisa_36590` ON st_item.id = sisa_36590.item_id AND sisa_36590.attribute_id = 365
LEFT JOIN `st_item_specification_attribute` `sisa_367910` ON st_item.id = sisa_367910.item_id AND sisa_367910.attribute_id = 367
LEFT JOIN `st_item_specification_attribute` `sisa_374641` ON st_item.id = sisa_374641.item_id AND sisa_374641.attribute_id = 374
LEFT JOIN `st_item_specification_attribute` `sisa_378366` ON st_item.id = sisa_378366.item_id AND sisa_378366.attribute_id = 378
LEFT JOIN `st_item_specification_attribute` `sisa_382500` ON st_item.id = sisa_382500.item_id AND sisa_382500.attribute_id = 382
LEFT JOIN `st_item_specification_attribute` `sisa_372134` ON st_item.id = sisa_372134.item_id AND sisa_372134.attribute_id = 372
LEFT JOIN `st_item_specification_attribute` `sisa_41268` ON st_item.id = sisa_41268.item_id AND sisa_41268.attribute_id = 412
LEFT JOIN `st_item_specification_attribute` `sisa_413368` ON st_item.id = sisa_413368.item_id AND sisa_413368.attribute_id = 413
LEFT JOIN `st_item_specification_attribute` `sisa_414929` ON st_item.id = sisa_414929.item_id AND sisa_414929.attribute_id = 414
LEFT JOIN `st_item_specification_attribute` `sisa_418496` ON st_item.id = sisa_418496.item_id AND sisa_418496.attribute_id = 418
LEFT JOIN `st_item_specification_attribute` `sisa_385748` ON st_item.id = sisa_385748.item_id AND sisa_385748.attribute_id = 385
LEFT JOIN `st_item_specification_attribute` `sisa_36625` ON st_item.id = sisa_36625.item_id AND sisa_36625.attribute_id = 366
LEFT JOIN `st_item_specification_attribute` `sisa_366355` ON st_item.id = sisa_366355.item_id AND sisa_366355.attribute_id = 366
LEFT JOIN `st_item_specification_attribute` `sisa_366816` ON st_item.id = sisa_366816.item_id AND sisa_366816.attribute_id = 366
LEFT JOIN `st_item_specification_attribute` `sisa_366370` ON st_item.id = sisa_366370.item_id AND sisa_366370.attribute_id = 366
WHERE (`parent_id`=1032) AND
(sisa_36590.attribute_value_id = 2230) AND
(sisa_367910.attribute_value_id = 2451) AND
(sisa_374641.attribute_value_id = 3793) AND
(sisa_378366.attribute_value_id = 2955) AND
(sisa_382500.attribute_value_id = 3879) AND
(sisa_372134.attribute_value_id = 2780) AND
(sisa_41268.attribute_value_id = 3363) AND
(sisa_413368.attribute_value_id = 3373) AND
(sisa_414929.attribute_value_id = 3378) AND
(sisa_418496.attribute_value_id = 3844) AND
(sisa_385748.attribute_value_id = 3036) AND
(sisa_36625.attribute_value_id = 2315) AND
(sisa_366355.attribute_value_id = 2408) AND
(sisa_366816.attribute_value_id = 2412) AND
(sisa_366370.attribute_value_id = 2420)
Query must compare specific pair attribute_id => attribute_value_id, that's the reason my "ON clause" to be with item_id and attribute_id and specific alias

You can use aggregation:
select i.id
from st_item i join
st_item_specification_attribute sisa
ON sisa.item_id = i.item_id
where i.parent_id = 1032 and
(sisa.attribute_id, attribute_value_id) in ( (365, 2230), (367, 2451), . . .)
group by i.id
having count(*) = 15;

You can move your WHERE conditions into ON conditions
and change LEFT JOIN to INNER JOIN.
SELECT `st_item`.id FROM `st_item`
JOIN `st_item_specification_attribute` `sisa_36590`
ON st_item.id = sisa_36590.item_id AND sisa_36590.attribute_id = 365
AND sisa_36590.attribute_value_id = 2230
JOIN `st_item_specification_attribute` `sisa_367910`
ON st_item.id = sisa_367910.item_id AND sisa_367910.attribute_id = 367
AND sisa_367910.attribute_value_id = 2451
...
WHERE `parent_id`=1032
2nd approach
SELECT `st_item`.id FROM `st_item`
JOIN `st_item_specification_attribute` `sisa`
ON st_item.id = sisa.item_id AND
(
(sisa.attribute_id = 365 AND sisa.attribute_value_id = 2230)
OR
(sisa.attribute_id = 367 AND sisa.attribute_value_id = 2451)
...
)
WHERE `parent_id`=1032
GROUP BY `st_item`.id
HAVING COUNT(*) = 15

I cannot predict the performance, but I think you could make on subselect out of all that joins
(I presume atribute_id and Attribute_value_id pairs are unique per item_id)
SELECT `st_item`.id FROM `st_item`
WHERE (`parent_id`=1032) AND
15 = (SELECT COUNT(*) FROM st_item_specification_attribute attr
WHERE `st_item`.id = attr.item_id
AND ( attribute_id = 365 AND attribute_value_id = 2230 OR
...
)

I would use a UNION ALL approach for this. It's easy to change, and fairly simple to read. Performance should be pretty good too:
--A CTE so you only have to change parent_id in one place
--I believe not all mysql versions support this, though
--You could of course just select the parent_id in de UNION ALL and
-- use a single WHERE in the outer query.
WITH st_item_id AS
(
SELECT id
, attribute_id
, attribute_value_id
FROM st_item
WHERE parent_id = 1032
)
SELECT UA.id
FROM (
SELECT st_item.id
FROM st_item_id
INNER JOIN st_item_specification_attribute sisa_36590
ON st_item_id.id = sisa_36590.item_id
AND sisa_36590.attribute_id = 365
AND sisa_36590.attribute_value_id = 2230
UNION ALL
SELECT st_item.id
FROM st_item_id
INNER JOIN st_item_specification_attribute sisa_367910
ON st_item_id.id = sisa_367910.item_id
AND sisa_367910.attribute_id = 367
AND sisa_367910.attribute_value_id = 2451
) UA

Related

SQL query to create new meta descriptions in spreadsheet

I building a custom SQL query of a product name with id_product, id_lang, id_manufacturer, category, meta_description, brand name and multiple feature ids. This SQL query shows no result.
Here's my query:
SELECT pl.id_product, pl.id_lang, ml.id_manufacturer, p.active, pl.name as name_product, fp.id_feature as name_attribute, cl.name as category, m.name as brand, pl.meta_description
FROM pr_product p
LEFT JOIN pr_product_lang pl ON (p.id_product = pl.id_product)
LEFT JOIN pr_category_lang cl ON (cl.id_category = p.id_category_default and cl.id_lang = pl.id_lang)
LEFT JOIN pr_lang l on (l.id_lang = pl.id_lang)
LEFT JOIN pr_manufacturer_lang ml on (l.id_lang = pl.id_lang and l.id_lang = ml.id_lang)
LEFT JOIN pr_manufacturer m on (ml.id_manufacturer = m.id_manufacturer)
LEFT JOIN pr_feature_product fp on (pl.id_product = fp.id_product)
Where l.active = 1 AND pl.id_lang = 2 AND cl.name = 9 and fp.id_feature = 2 AND fp.id_feature = 3 AND fp.id_feature = 35 AND fp.id_feature = 39
Order by p.id_product, pl.id_lang, ml.id_manufacturer, fp.id_feature
The goal is to creat a new meta description in the spreadsheet and import it to the database. In php, the query would be easier, but I have zero knowledge about it.

search a keyword from the result of the subquery with multiple rows results

I would like to search a keyword from the result of the subquery. In my subquery I already filtered the grants I need from different category required. Now I need to search the keyword in title for those results. Or is there efficient way to do this query because my work is not working after days stuck?
I tried working on the IN but still did not get it right.
SELECT DISTINCT gt.grant_id, gt.*,
infra.infra_name,
infrasub.infrasub_name,
lga.lga_name,
stream.stream_id,
stream.stream_n,
stream.stream_dept,
stream.stream_desc
FROM
grant_tbl AS gt
LEFT JOIN grant_details AS gd
ON (
gt.grant_id = gd.grant_id
)
LEFT JOIN infra_sub_tbl AS infrasub
ON (
infrasub.infra_sub_id = gd.infrasub_id
)
LEFT JOIN infra_tbl AS infra
ON (
infra.infra_id = gd.infra_id
)
LEFT JOIN lga_tbl AS lga
ON (
lga.lga_id = gd.lga_id
)
LEFT JOIN streams_tbl AS stream
ON (
stream.stream_id = gd.stream_id
)
WHERE gt.grant_id IN
(
SELECT DISTINCT gd.grant_id, CONCAT(gt.grant_name,"|",gt.grant_desc,"|",gt.keywords)
FROM grant_details AS gd
LEFT JOIN grant_tbl AS gt
ON gt.grant_id = gd.grant_id
WHERE gd.lga_id = 1
OR gd.lga_id = 2
AND gd.stream_id = 1
OR gd.stream_id = 2
GROUP BY gt.grant_id
)
my result should narrow down from my subquery. display only with matching keyworkds
You can use EXISTS instead of IN,
SELECT DISTINCT gt.grant_id, gt.*,
infra.infra_name,
infrasub.infrasub_name,
lga.lga_name,
stream.stream_id,
stream.stream_n,
stream.stream_dept,
stream.stream_desc
FROM grant_tbl AS gt
LEFT JOIN grant_details AS gd ON gt.grant_id = gd.grant_id
LEFT JOIN infrasub_tbl AS infrasub ON infrasub.infrasub_id = gd.infrasub_id
LEFT JOIN infra_tbl AS infra ON infra.infra_id = gd.infra_id
LEFT JOIN lga_tbl AS lga ON lga.lga_id = gd.lga_id
LEFT JOIN stream_tbl AS stream ON stream.stream_id = gd.stream_id
WHERE exists
(
SELECT DISTINCT gd.grant_id,CONCAT(gt2.grant_name,"|",gt2.grant_desc,"|",gt2.keywords)
FROM grant_details AS gd
LEFT JOIN grant_tbl AS gt2 ON gt2.grant_id = gd.grant_id
WHERE (gd.lga_id = 10 OR gd.lga_id = 20) AND (gd.stream_id = 100 OR gd.stream_id = 200)
and gt.grant_id=gt2.grant_id
GROUP BY gt.grant_id
)
Hope this helps.

Common results of two MySQL queries

I am looking to combine two MySQL queries so it returns the common results so I know I am not looking to use UNION on this one. I tried writing a subselect statement but that didn't work out
First query:
SELECT s.ses_id, h.page, m.question, m.answer FROM session s
INNER JOIN history h on h.ses_id = s.ses_id
INNER JOIN multiple m on m.ses_id = s.ses_id
WHERE m.question = 4 and m.answer = 3 and h.page = 4
Second query:
SELECT s.ses_id, h.page, m.question, m.answer FROM session s
INNER JOIN history h on h.ses_id = s.ses_id
INNER JOIN multiple m on m.ses_id = s.ses_id
WHERE m.question = 114 and m.answer = 1 and h.page = 114
Failed merge query:
SELECT s.ses_id FROM session s, multiple, history h
JOIN (
SELECT session.ses_id
FROM session, history, multiple
WHERE multiple.question = 114 and multiple.answer = 1 and history.page = 114 and history.ses_id = session.ses_id and multiple.ses_id=session.ses_id
) q1 ON q1.ses_id = s.ses_id
WHERE s.interview = 'lifestyle' and s.finished = 'y' and multiple.page=4 and multiple.answer = 3 and h.page = 4 and h.ses_id = s.ses_id and multiple.ses_id=s.ses_id
The multiple table contains questions and answers and I am looking to find the ids of those who have answered the two questions with those specific answers.
I realize this should be easy and I am most likely overthinking and/or missing something.
You simply need to join both the history and multiple tables an additional time each for the 2nd question/answer combination.
SELECT s.ses_id,
m1.question, m1.answer, h1.page,
m2.question, m2.answer, h2.page
FROM session s
INNER JOIN history h1
ON h1.ses_id = s.ses_id AND h1.page = 4
INNER JOIN multiple m1
ON m1.ses_id = s.ses_id
AND m1.question = 4 AND m1.answer = 3
INNER JOIN history h2
ON h2.ses_id = s.ses_id AND h2.page = 114
INNER JOIN multiple m2
ON m2.ses_id = s.ses_id
AND m2.question = 114 and m2.answer = 1
WHERE s.interview = 'lifestyle' and s.finished = 'y'
Below simply altered query may be useful.,
SELECT s.ses_id, h.page, m.question, m.answer FROM session s
INNER JOIN history h on h.ses_id = s.ses_id
INNER JOIN multiple m on m.ses_id = s.ses_id
WHERE
( m.question = 4 and m.answer = 3 and h.page = 4 )
OR
( m.question = 114 and m.answer = 1 and h.page = 114 )
Merge operation is not needed.
if you are looking for intersection of two queries then you can simply use IN set operator of mysql
SELECT s.ses_id, h.page, m.question, m.answer
FROM session s
INNER JOIN history h on h.ses_id = s.ses_id
INNER JOIN multiple m on m.ses_id = s.ses_id
WHERE m.question = 4 and m.answer = 3 and h.page = 4 and s.ses_id
IN
(
SELECT s.ses_id
FROM session s
INNER JOIN history h on h.ses_id = s.ses_id
INNER JOIN multiple m on m.ses_id = s.ses_id
WHERE m.question = 114 and m.answer = 1 and h.page = 114
)

MySQL select two columns multiple name value pair

I need help about generating query for multiple column.
part of my tbl_advert_specific_fields_values table look like:
id advert_id field_name field_value
1 654 t1_sqft 50
2 655 t1_yearbuilt 1999
3 1521 t2_doorcount 5
4 656 t1_yearbuilt 2001
5 656 t1_sqft 29
6 654 t1_yearbuilt 2004
SELECT p.*, p.id AS id, p.title AS title, usr.id as advert_user_id,
p.street_num, p.street,c.icon AS cat_icon,c.title AS cat_title,c.title AS cat_title,
p.description as description,
countries.title as country_name,
states.title as state_name,
date_FORMAT(p.created, '%Y-%m-%d') as fcreated
FROM tbl AS p
LEFT JOIN tbl_advertmid AS pm ON pm.advert_id = p.id
INNER JOIN tbl_usermid AS am ON am.advert_id = p.id
LEFT JOIN tbl_users AS usr ON usr.id = am.user_id
INNER JOIN tbl_categories AS c ON c.id = pm.cat_id
INNER JOIN tbl_advert_specific_fields_values AS asfv ON asfv.advert_id = p.id
LEFT JOIN tbl_countries AS countries ON countries.id = p.country
LEFT JOIN tbl_states AS states ON states.id = p.locstate
WHERE p.published = 1 AND p.approved = 1 AND c.published = 1
AND (asfv.field_name = 't1_yearbuilt'
AND CONVERT(asfv.field_value,SIGNED) <= 2004 )
AND (asfv.field_name = 't1_sqft'
AND CONVERT(asfv.field_value,SIGNED) <= 50)
AND p.price <= 10174945 AND (p.advert_type_id = 1)
AND (c.id = 43 OR c.parent = 43)
GROUP BY p.id
ORDER BY p.price DESC
ok, the problem is in this asfv query part that are generated dynamically. It belong to objects which represent adverts by its specific fields. asfv is actually advert_specific_fields_values table (table name say all about it).
Without part:
AND (asfv.field_name = 't1_yearbuilt'
AND CONVERT(asfv.field_value,SIGNED) <= 2004 )
AND (asfv.field_name = 't1_sqft'
AND CONVERT(asfv.field_value,SIGNED) <= 50)
query return all adverts that belong on advert_type_id and price of them are less than 10.174.945,00 €.
All what I need is query update that return only adverts, for example t1_yearbuilt less than 2005 and t1_sqft less than 51 (advert_id => 654,656).
I also need query for values between for example t1_sqft >=30 AND t1_sqft <=50 (advert_id => 654).
Can anybody know how, update this query?
TNX

using joins together with aggregates, and retrieving rows when no aggregate exists

The following query on my MySQL tables returns rows from the purchaseorder table that have corresponding entries in the deliveryorder table. How do I construct this query so that I get rows from the purchaseorder table even if no corresponding rows exist in the deliveryorder table? If the users want to see sql table CREATE statements, I can post those, but I'm not posting now as it really makes the question too big.
SELECT
`purchaseorder`.`id` AS `po_id`,
`purchaseorder`.`order_quantity` AS `po_order_quantity`,
`purchaseorder`.`applicable_approved_unit_rate` AS `po_unit_rate`,
`purchaseorder`.`applicable_sales_tax_rate` AS `po_tax_rate`,
`purchaseorder`.`order_date` AS `po_order_date`,
`purchaseorder`.`remarks` AS `po_remarks`,
`purchaseorder`.`is_open` AS `po_is_open`,
`purchaseorder`.`is_active` AS `po_is_active`,
`purchaseorder`.`approved_rate_id` AS `po_app_rate_id`,
`supplier`.`name` AS `sup_name`,
SUM(`deliveryorder`.`quantity`) AS `total_ordered`
FROM `purchaseorder`
LEFT JOIN `deliveryorder` ON (`deliveryorder`.`purchase_order_id` = `purchaseorder`.`id`)
INNER JOIN `approvedrate` ON (`purchaseorder`.`approved_rate_id` = `approvedrate`.`id`)
INNER JOIN `supplier` ON (`approvedrate`.`supplier_id` = `supplier`.`id`)
WHERE (
`purchaseorder`.`is_active` = 1
AND `purchaseorder`.`is_open` = 1
AND `deliveryorder`.`is_active` = 1
AND `approvedrate`.`material_id` = 2
)
HAVING `purchaseorder`.`order_quantity` >= `total_ordered` + 1
You have an aggregating function but no GROUP BY clause, which is wierd, but anyway - something like this? Oops - edited...
SELECT po.id po_id
, po.order_quantity po_order_quantity
, po.applicable_approved_unit_rate po_unit_rate
, po.applicable_sales_tax_rate po_tax_rate
, po.order_date po_order_date
, po.remarks po_remarks
, po.is_open po_is_open
, po.is_active po_is_active
, po.approved_rate_id po_app_rate_id
, s.name sup_name
, SUM(do.quantity) total_ordered
FROM purchaseorder po
LEFT
JOIN deliveryorder do
ON do.purchase_order_id = po.
AND do.is_active = 1
LEFT
JOIN approvedrate ar
ON ar.id = po.approved_rate_id
AND ar.material_id = 2
LEFT
JOIN supplier s
ON s.id = ar.supplier_id
WHERE po.is_active = 1
AND po.is_open = 1
HAVING po.order_quantity >= total_ordered + 1
I couldn't work out how to get the desired results all in one query, but ended up using the following two queries to fulfill my requirements: -
1st query
SELECT
pot.`id` AS `po_id`,
pot.`order_quantity` AS `po_order_quantity`,
pot.`applicable_approved_unit_rate` AS `po_unit_rate`,
pot.`applicable_sales_tax_rate` AS `po_tax_rate`,
pot.`is_open` AS `po_is_open`,
pot.`is_active` AS `po_is_active`,
st.`id` AS `sup_id`,
st.`name` AS `sup_name`,
SUM(dot.`quantity`) AS `total_ordered`
FROM `purchaseorder` pot
INNER JOIN `deliveryorder` dot ON (dot.`purchase_order_id` = pot.`id`)
INNER JOIN `approvedrate` art ON (pot.`approved_rate_id` = art.`id`)
INNER JOIN `supplier` st ON (art.`supplier_id` = st.`id`)
WHERE (
pot.`is_active` = 1
AND pot.`is_open` = 1
AND art.`material_id` = #materialid
AND art.`in_effect` = 1
AND art.`is_active` = 1
AND dot.`is_active` = 1
AND st.`is_active` = 1
)
HAVING pot.`order_quantity` >= `total_ordered` + #materialquantity
2nd query
SELECT
pot.`id` AS `po_id`,
pot.`order_quantity` AS `po_order_quantity`,
pot.`applicable_approved_unit_rate` AS `po_unit_rate`,
pot.`applicable_sales_tax_rate` AS `po_tax_rate`,
pot.`is_open` AS `po_is_open`,
pot.`is_active` AS `po_is_active`,
st.`id` AS `sup_id`,
st.`name` AS `sup_name`,
0 AS `total_ordered`
FROM `purchaseorder` pot
INNER JOIN `approvedrate` art ON (pot.`approved_rate_id` = art.`id`)
INNER JOIN `supplier` st ON (art.`supplier_id` = st.`id`)
WHERE (
pot.`is_active` = 1
AND pot.`is_open` = 1
AND art.`material_id` = #materialid
AND art.`in_effect` = 1
AND art.`is_active` = 1
AND st.`is_active` = 1
AND pot.`order_quantity` >= #materialquantity
AND pot.`id` NOT IN
(
SELECT dot.`purchase_order_id`
FROM `deliveryorder` dot
WHERE dot.is_active = 1
)
)