MySQL HAVING with specific number within GROUP_CONCAT - mysql

I have a Query that works like a charm, but now I need to filter it by a value that is currently in a GROUP_CONCAT created field. i have learned that I can use HAVING to filter but I can't find the syntax to use as HAVING groupconcatname LIKE '%6%' will find 6, 16, 26 etc .. I need to have the statement search for specific values so it can find "8" within the groupconcat containing 1,5,8,18,30,58 and not find 8 AND 18 AND 58 because i used a LIKE '%%'.
My Query (works right now .. but finds too many results based on a partial match of %6%):
SELECT offers.*, s.short_name AS sponsorName, s.logo AS sponsorLogo, GROUP_CONCAT( mn.title) titles,
GROUP_CONCAT( mo.`name`) metas, GROUP_CONCAT( om.meta_option) AS meta_ids
FROM offers
INNER JOIN offer_metas as om ON om.offer_id = local_ld_offers.id
INNER JOIN meta_options as mo ON om.meta_option = mo.id
INNER JOIN meta_names as mn ON mo.category = mn.category AND mo.cat_seq = mn.seq
LEFT JOIN sponsors AS s ON s.id=offers.sponsor
GROUP BY offers.id
HAVING meta_ids LIKE '%6%'
ORDER BY end_date ASC
I need to replace HAVING meta_ids LIKE '%6%' with something more akin to WHERE '6' IN meta_ids but when i try to replace the HAVING statement, with what seems like a reasonable attempt at a functioning WHERE, it ignores the fact that meta_ids is a valid variable/field to use in my clause and errors out.
Any recommendations?
Thanks,
Silver Tiger
UPDATE - Solved unless there is a more elegant way
Based on the recommendations from Thomas below I have updated the query and apparently I can search for as many individual values as I need by adding a line per option using the updated method as follows:
WHERE EXISTS( SELECT * FROM offer_metas AS om1 WHERE om1.meta_option = mo.id AND om1.meta_option = '23')
OR EXISTS( SELECT * FROM offer_metas AS om1 WHERE om1.meta_option = mo.id AND om1.meta_option = '16')

It isn't clear exactly what is being asked here (some test inputs with expected outputs would help) but it doesn't sound like you really need a Having clause. If you want to filter for values where say om.meta_option is "like" 8, then add a Where clause like so:
Where Exists (
Select 1
From meta_options As om1
Where om1.meta_option = mo.id
And om1.meta_option = Like '%8%'
)
Of course, this return rows where there exists 8, 80, 18 and 181. If you are looking for a specific value equal to 8, then use:
Where Exists (
Select 1
From meta_options As om1
Where om1.meta_option = mo.id
And om1.meta_option = '8'
)
If what you seek is a series of values, then you can do something like:
Where Exists (
Select 1
From meta_options As om1
Where om1.meta_option = mo.id
And om1.meta_option In('8','23')
)
Remember that the purpose of the Having clause to apply filters after the grouping and the Where clause applies filters prior to the grouping. In this case, it sounds like the filtering should happen prior to the grouping and thus the Where clause should be used.

You can change HAVING meta_ids LIKE '%6%' to HAVING meta_ids LIKE '%,6,%' OR '6,%' OR '%,6' OR '6' which should get what you're looking for.
Alternatives include things like subqueries, like the % operator are not very nice performance wise.

Related

How do I improve the performance of my sub query?

I have the following two separate queries:
SELECT qry_tbl_G_ov_uni_atp.ID, Max(qry_tbl_G_ov_uni_atp.nElo_Ov) AS MaxOfnElo_Ov
FROM qry_tbl_G_ov_uni_atp
GROUP BY qry_tbl_G_ov_uni_atp.ID;
And:
SELECT qry_tbl_G_ov_uni_atp.ID, Max(qry_tbl_G_ov_uni_atp.nElo_Sur) AS MaxOfnElo_Sur1
FROM qry_tbl_G_ov_uni_atp
WHERE qry_tbl_G_ov_uni_atp.ID_C = 1
GROUP BY qry_tbl_G_ov_uni_atp.ID;
Both run fine and within a second or two. I want to combine them into one query so I have ID, MaxOfnElo_Ov and MaxOfnElo_Sur1 in the same output.
I know I need to use a sub query but my attempts take absolutely ages to display anything and then are barely useable as any attempt at scrolling locks Access up for an age again. I'm clearly not doing something right. Here's my sub query code:
SELECT qry_tbl_G_ov_uni_atp.ID, Max(qry_tbl_G_ov_uni_atp.nElo_Ov) AS MaxOfnElo_Ov, (SELECT Max(tt.nElo_Sur)
FROM qry_tbl_G_ov_uni_atp as tt
WHERE tt.ID_C = 1
AND tt.ID = qry_tbl_G_ov_uni_atp.ID) AS MaxOfnElo_Sur1
FROM qry_tbl_G_ov_uni_atp
GROUP BY qry_tbl_G_ov_uni_atp.ID;
You can use a subquery to achieve this as you have indicated. By using a sub-query in the JOIN as well you will get all results from a (your first query) and matching results from b (your second query):
SELECT a.ID,
a.MaxOfnElo_Ov,
b.MaxOfnElo_Sur1
FROM (
SELECT qry_tbl_G_ov_uni_atp.ID,
Max(qry_tbl_G_ov_uni_atp.nElo_Ov) AS MaxOfnElo_Ov
FROM qry_tbl_G_ov_uni_atp
GROUP BY qry_tbl_G_ov_uni_atp.ID
) a
LEFT JOIN (
SELECT qry_tbl_G_ov_uni_atp.ID,
Max(qry_tbl_G_ov_uni_atp.nElo_Sur) AS MaxOfnElo_Sur1
FROM qry_tbl_G_ov_uni_atp
WHERE qry_tbl_G_ov_uni_atp.ID_C = 1
GROUP BY qry_tbl_G_ov_uni_atp.ID
) b ON b.ID = a.ID
Note that this is untested, and assumes the ID is the same in both a and b (which I believe it is).

select last record in sub query

have read all similar qs but cant apply to my sql
Would like to select all customers AND the last action record inserted.
The sql below first selects the max actionid then uses that on another sub query - this takes 5+secs to run ;(
Please advise TQ
SELECT cus.cusid,cus.FirstName,cus.Surname,
lastact.actionid, lastact.actiondate, lastact.siteid
FROM cus
LEFT JOIN(
SELECT MAX(actionid) AS maxactionid, cusid
FROM `action`
INNER JOIN `event` ON event.eventid = action.`eventid`
GROUP BY cusid
) AS maxactionid ON maxactionid.cusid = cus.cusid
LEFT JOIN (
SELECT
action.actionid,
action.actiondate,
event.cusid,
event.siteid
FROM
`action`
INNER JOIN `event`
ON event.eventid = action.eventid
ORDER BY actionid DESC
) AS lastact ON lastact.actionid = maxactionid
WHERE UCASE(CONCAT(firstname, surname)) LIKE '%JIM%HEMM%'
TQ for ideas - please see following:
1) the limit idea, provides null results for lastact.actionid, lastact.actiondate, lastact.siteid - but does run in 0.075 secs!
Such a shame this idea fails
SELECT cus.cusid,cus.FirstName,cus.Surname, lastact.actionid, lastact.actiondate, lastact.siteid
FROM cus
LEFT JOIN (SELECT action.actionid, action.actiondate, event.cusid, event.siteid
FROM action
INNER JOIN event ON event.eventid = action.eventid
ORDER BY actionid DESC LIMIT 1
) AS lastact ON lastact.cusid = cus.cusid
WHERE UCASE(CONCAT(firstname, surname)) LIKE '%JIM%HEMM%'
2) EXPLAIN results of original query are:
3) Adding LIKE 'JIM%' AND cus.surname LIKE 'HEMM%' doesn't affect query time much but will include as per suggestion
Hi - have got great result by using ideas from everyone - Thank you
1) Changed WHERE to cus.FirstName LIKE 'JIM%' AND cus.surname LIKE 'H%'
2) Added index on firstname, surname
3) Added cusid in action table (don't need event table anymore)
4) Moved lookup tables (not in orig question) outside of action sub query
Finished sql looks like (runs in 0.063 secs - tested with a surname of only one letter!)
SELECT cus.cusid,cus.FirstName,cus.Surname, lastact.actionid, lastact.actiondate, lastact.siteid, actiontype.action,
FROM cus
LEFT JOIN (
SELECT action.actionid, action.actiondate, event.cusid, event.siteid
FROM action
ORDER BY actionid DESC
) AS lastact ON lastact.cusid = cus.cusid
LEFT JOIN actiontype ON actiontype.actiontypeid = lastact.typeid
WHERE cus.FirstName LIKE 'JIM%' AND cus.surname LIKE 'H%'
GROUP BY lastact.cusid
As JC Sama said "change select MAX(actionid) by select actionid and adding a limit 1 and order by desc", helps indexed
searchs.
As David K-J said "run an EXPLAIN first to see what the planner is trying to do. I would suspect it's the (non-indexable) search on concatenation strings".
You shouldn't put jokers '%' at the begining of a string when comparing, that disables indexed search.
You shouldn't use functions when comparing (at least, avoid them if you can), also for the indexed search.
Now that you can use indexes, add them if you haven't done it yet.
I may be wrong here, but I don't see the point of the last LEFT JOIN, as far as I'm concerned. You could withdraw that data from the first LEFT JOIN. Neither why are you grouping by cusid.
With all, the sql I made is (obviously not tested, you may have to fix some thing):
SELECT cus.cusid,cus.FirstName,cus.Surname,
maxaction.actionid, maxaction.actiondate, maxaction.siteid
FROM cus
LEFT JOIN(
SELECT actionid AS maxaction, action.actiondate, event.cusid, event.siteid
FROM `action`
INNER JOIN `event` ON event.eventid = action.eventid
order by actionid desc limit 1
) AS maxaction ON maxaction.cusid = cus.cusid
WHERE cus.FirstName like 'JIM%' and cus.surname like 'HEMM%'

How to optimize this complected query?

While working with following query on mysql, Its getting locked,
SELECT event_list.*
FROM event_list
INNER JOIN members
ON members.profilenam=event_list.even_loc
WHERE (even_own IN (SELECT frd_id
FROM network
WHERE mem_id='911'
GROUP BY frd_id)
OR even_own = '911' )
AND event_list.even_active = 'y'
GROUP BY event_list.even_id
ORDER BY event_list.even_stat ASC
The Inner query inside IN constraint has many frd_id, So because of that above query is slooow..., So please help.
Thanks.
Try this:
SELECT el.*
FROM event_list el
INNER JOIN members m ON m.profilenam = el.even_loc
WHERE el.even_active = 'y' AND
(el.even_own = 911 OR EXISTS (SELECT 1 FROM network n WHERE n.mem_id=911 AND n.frd_id = el.even_own))
GROUP BY el.even_id
ORDER BY el.even_stat ASC
You don't need the GROUP BY on the inner query, that will be making the database engine do a lot of unneeded work.
If you put even_own = '911' before the select from network, then if even_own IS 911 then it will not have to do the subquery.
Also why do you have a group by on the subquery?
Also run explain plan top find out what is taking the time.
This might work better:
( SELECT e.*
FROM event_list AS e
INNER JOIN members AS m ON m.profilenam = e.even_loc
JOIN network AS n ON e.even_own = n.frd_id
WHERE n.mem_id = '911'
AND e.even_active = 'y'
ORDER BY e.even_stat ASC )
UNION DISTINCT
( SELECT e.*
FROM event_list AS e
INNER JOIN members AS m ON m.profilenam = e.even_loc
WHERE e.even_own = '911'
AND e.even_active = 'y' )
ORDER BY e.even_stat ASC
Since I don't know whether the JOINs one-to-many (or what), I threw in DISTINCT to avoid dups. There may be a better way, or it may be unnecessary (that is, UNION ALL).
Notice how I avoid two things that are performance killers:
OR -- turned into UNION
IN (SELECT...) -- turned into JOIN.
I made aliases to cut down on the clutter. I moved the ORDER BY outside the UNION (and added parens to make it work right).

SELECT CASE WHEN THEN (SELECT)

I am trying to select a different set of results for a product depending on a product type.
So if my product should be a book I want it to look up the UPC and Artist for a normal product these details are however irrelevant and for another product I would want a completely different set of results.
SELECT CASE Product.type_id
WHEN 10 THEN (
SELECT
Product.product_id,
Product.type_id,
Product.product_name,
Product.UPC,
Product_Type.type,
CONCAT_WS(' ' , first_name, middle_name, last_name ) AS artistC
FROM Product, Product_Type, Product_ArtistAuthor
WHERE Product.type_id = Product_Type.type_id
AND Product.product_id = $pid
AND Product.artist_id = Product_ArtistAuthor.artist_id
)
ELSE (
SELECT
Product.product_id,
Product.type_id,
Product.product_name,
Product_Type.type
FROM Product, Product_Type
WHERE Product.type_id = Product_Type.type_id
AND Product.product_id = $pid
)
END
FROM Product
WHERE Product.product_id = $pid
I am not sure where I am going wrong
You Could try the other format for the case statement
CASE WHEN Product.type_id = 10
THEN
(
Select Statement
)
ELSE
(
Other select statement
)
END
FROM Product
WHERE Product.product_id = $pid
See http://msdn.microsoft.com/en-us/library/ms181765.aspx for more information.
You should avoid using nested selects and I would go as far to say you should never use them in the actual select part of your statement. You will be running that select for each row that is returned. This is a really expensive operation. Rather use joins. It is much more readable and the performance is much better.
In your case the query below should help. Note the cases statement is still there, but now it is a simple compare operation.
select
p.product_id,
p.type_id,
p.product_name,
p.type,
case p.type_id when 10 then (CONCAT_WS(' ' , first_name, middle_name, last_name )) else (null) end artistC
from
Product p
inner join Product_Type pt on
pt.type_id = p.type_id
left join Product_ArtistAuthor paa on
paa.artist_id = p.artist_id
where
p.product_id = $pid
I used a left join since I don't know the business logic.
For a start the first select has 6 columns and the second has 4 columns. Perhaps make both have the same number of columns (adding nulls?).
I ended up leaving the common properties from the SELECT queries and making a second SELECT query later on in the page. I used a php IF command to call for different scripts depending on the first SELECT query, the scripts contained the second SELECT query.

optimize Mysql: get latest status of the sale

In the following query, I show the latest status of the sale (by stage, in this case the number 3). The query is based on a subquery in the status history of the sale:
SELECT v.id_sale,
IFNULL((
SELECT (CASE WHEN IFNULL( vec.description, '' ) = ''
THEN ve.name
ELSE vec.description
END)
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
WHERE veh.id_sale = v.id_sale
AND vec.id_stage = 3
ORDER BY veh.id_record DESC
LIMIT 1
), 'x') sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
WHERE 1 =1
AND v.flag =1
AND v.id_quarters =4
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
the query delay 0.0057seg and show 1011 records.
Because I have to filter the sales by the name of the state as it would have to repeat the subquery in a where clause, I have decided to change the same query using joins. In this case, I'm using the MAX function to obtain the latest status:
SELECT
v.id_sale,
IFNULL(veh3.State3,'x') AS sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
LEFT JOIN (
SELECT veh.id_sale,
(CASE WHEN IFNULL(vec.description,'') = ''
THEN ve.name
ELSE vec.description END) AS State3
FROM t_record veh
INNER JOIN (
SELECT id_sale, MAX(id_record) AS max_rating
FROM(
SELECT veh.id_sale, id_record
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign AND vec.id_stage = 3
) m
GROUP BY id_sale
) x ON x.max_rating = veh.id_record
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
) veh3 ON veh3.id_sale = v.id_sale
WHERE v.flag = 1
AND v.id_quarters = 4
This query shows the same results (1011). But the problem is it takes 0.0753 sec
Reviewing the possibilities I have found the factor that makes the difference in the speed of the query:
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
If I remove this clause, both queries the same time delay... Why it works better? Is there any way to use this clause in the joins? I hope your help.
EDIT
I will show the results of EXPLAIN for each query respectively:
q1:
q2:
Interesting, so that little statement basically determines if there is a match between t_record.id_sale and t_sale.id_sale.
Why is this making your query run faster? Because Where statements applied prior to subSelects in the select statement, so if there is no record to go with the sale, then it doesn't bother processing the subSelect. Which is netting you some time. So that's why it works better.
Is it going to work in your join syntax? I don't really know without having your tables to test against but you can always just apply it to the end and find out. Add the keyword EXPLAIN to the beginning of your query and you will get a plan of execution which will help you optimize things. Probably the best way to get better results in your join syntax is to add some indexes to your tables.
But I ask you, is this even necessary? You have a query returning in <8 hundredths of a second. Unless this query is getting ran thousands of times an hour, this is not really taxing your DB at all and your time is probably better spent making improvements elsewhere in your application.