Performance issue with mysql query - mysql

I am working in MYSQL for the first and I am having issues with the following query
SELECT
t.id,
t.name,
t.description,
(
SELECT
GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM
occupation_skill_rate s
INNER JOIN hard_skills hs ON s.hard_skill_id = hs.id
WHERE
s.occupation_id = t.id
ORDER BY
s.rate DESC LIMIT 15
) AS skills,
(
SELECT
GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM
occupation_knowledge_rate s
INNER JOIN knowledge hs ON s.knowledge_id = hs.id
WHERE
s.occupation_id = t.id
ORDER BY
s.rate DESC LIMIT 15
) AS knowledge,
(
SELECT
GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM
occupation_abilities_rate s
INNER JOIN ability hs ON s.ability_id = hs.id
WHERE
s.occupation_id = t.id
ORDER BY
s.rate DESC LIMIT 15
) AS knowledge
FROM
occupations t
The occupation table contains 1033 rows occupation_skill_rate contains 34160 rows and it takes more than 1 minute to execute this query. Please let me know if you need further clarification for helping me.
Thanks for your help
Ajai

The occupation_%_rate tables seem to be many-to-many, correct? They need these indexes and no id:
PRIMARY KEY(occupation_id, xxx_id)
INDEX(xxx_id, occupation_id)
But, it seems like the ORDER BY and LIMIT when used with GROUP_CONCAT(). Please describe what the query's intention is; we may be able to help in rewriting it.
GROUP_CONCAT allows an ORDER BY clause but not a LIMIT. Can you do without the LIMITs?
Example
Instead of
( SELECT GROUP_CONCAT( CONCAT( hs.name, '|', s.rate ) )
FROM occupation_skill_rate s
INNER JOIN hard_skills hs ON s.hard_skill_id = hs.id
WHERE s.occupation_id = t.id
ORDER BY s.rate DESC
LIMIT 15
) AS skills;
Do
( SELECT CONCAT( name, '|', rate ORDER BY rate DESC )
FROM (
SELECT hs.name, s.rate
FROM occupation_skill_rate s
INNER JOIN hard_skills hs
ON s.hard_skill_id = hs.id
AND s.occupation_id = t.id
ORDER BY s.rate DESC
LIMIT 15
) AS a
) AS skills
But I suspect t is not visible that deeply nested.
If that is the case, rearrange things thus:
SELECT t.id, t.name, t.description, s3.skills, ...
FROM occupations AS t
JOIN (
SELECT s2.occupation_id,
CONCAT( s2.name, '|', s2.rate ORDER BY rate DESC )
AS skills
FROM (
SELECT hs.name, s1.rate, s1.occupation_id
FROM occupation_skill_rate s1
INNER JOIN hard_skills hs
ON s1.hard_skill_id = hs.id
ORDER BY s.rate DESC
LIMIT 15
) AS s2
GROUP BY s2.occupation_id
ORDER BY s2.rate DESC
) AS s3 ON s2.occupation_id = t.id
JOIN ...
JOIN ... ;
Another
There is also a way to build the long GROUP_CONCAT, then chop to 15 items by using SUBSTRING_INDEX(...).

Related

how to combine 2 cte to get grouping

hi i need help combining 2 cte to get who get 100 attendance percentage but failed at exam
here my first cte
with main as(
select ca.STUDENT_ID,
ca.SCHEDULE_ID,
s.COURSE_ID,
co.NAME as course_name,
st.NAME,
count(ca.ID) as total_attendance,
((CHAR_LENGTH(s.COURSE_DAYS) - CHAR_LENGTH(REPLACE(s.COURSE_DAYS , ',', '')) + 1) * 13) as attendance_needed
from univ.course_attendance ca
left join univ.schedule s on ca.SCHEDULE_ID = s.ID
left join univ.student st on ca.SCHEDULE_ID = st.ID
left join univ.course co on ca.SCHEDULE_ID = co.ID
group by ca.STUDENT_ID, ca.SCHEDULE_ID
)
select *,total_attendance/attendance_needed as attendance_percentage
from main
order by 1,2;
second cte
;with inputdata as
(
select es.STUDENT_ID,es.EXAM_ID,es.SCORE,e.PASS_THRESHOLD, s.NAME , c.NAME as Course_name, es.EXAM_DT,
case
when SCORE>=PASS_THRESHOLD then 'PASS'
else 'Fail'
end as Flag
from exam_submission es
left join student s on es.STUDENT_ID = s.ID
left join exam e on es.EXAM_ID = e.ID
left join course c on e.COURSE_ID = c.ID
)
select * from inputdata I
join
( select student_id,exam_id from
inputdata
group by student_id, exam_id
)T on I.student_id=T.student_id and I.exam_id=T.exam_id
order by exam_dt asc
result:
what i need student name, course name, attendace percentage & flag "failed/pass"
Just chain multiple table expressions in a single CTE by introducing "aliases" like main_ordered for the first CTE and inputdata_grouped for the second one. I'm sticking with the original naming, but it could be improved.
with
main as (
select ca.STUDENT_ID,
...
group by ca.STUDENT_ID, ca.SCHEDULE_ID),
main_ordered as (
select *,total_attendance/attendance_needed as attendance_percentage
...
order by 1,2),
inputdata as (
select es.STUDENT_ID,es.EXAM_ID,es.S...
...),
inputdata_grouped as (
select * from inputdata I
...
group by student_id, exam_id...
...
order by exam_dt asc)
select *
from main_ordered join inputdata_grouped on ...

SQL paging query with linked table and ordering

I want to share some SQL queries that I use for paging when a joined table and ordering is needed.
In these examples, the result set is limited to row numbers 2-3.
MySql 5.1:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT DISTINCT o.id FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
)
ORDER BY outerO.Value DESC, outerO.Id ASC
LIMIT 2
OFFSET 1
This one shows best what I want to achieve. Oracle 11 and DB2 are much more complicated.
Oracle 11:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT id FROM(
SELECT row_.id, rownum rownum_
FROM (
SELECT id
FROM (
SELECT DISTINCT o.Value, o.Id
FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
ORDER BY o.Value DESC, o.Id ASC
)
) row_ WHERE rownum <= 3
) WHERE rownum_ > 1
)
ORDER BY outerO.Value DESC, outerO.Id ASC
I found no way to do this with fewer subselects.
DB2 10.5:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT inner1_.id
FROM (
SELECT inner2_.id, rownumber()
OVER(ORDER BY ORDER OF inner2_) AS rownumber_
FROM (
SELECT id
FROM (
SELECT DISTINCT o.Value, o.Id
FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
ORDER BY o.Value DESC, o.Id ASC
)
FETCH FIRST 3 ROWS ONLY
) AS inner2_
) AS inner1_
WHERE inner1_.rownumber_ > 1 ORDER BY inner1_.rownumber_
)
ORDER BY outerO.Value DESC, outerO.Id ASC
I found no way to do this with fewer subselects.
Microsoft SQL-Server 2016:
SELECT outerO.Id, outerO.Value
FROM MyTable outerO
WHERE outerO.id IN (
SELECT DISTINCT o.id
FROM MyTable o
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
)
ORDER BY outerO.Value DESC, outerO.Id ASC
OFFSET 1 ROWS
FETCH NEXT 2 ROWS ONLY
These queries do work for me, but perhaps there are better solutions available?
This is how I would do it in DB2
SELECT ID, VALUE
FROM (
SELECT ID, VALUE,
ROW_NUMBER() OVER (ORDER BY o.Value DESC, o.Id ASC) AS RN
FROM MyTable
JOIN LinkTable l ON l.Fk = o.Id
WHERE o.Value LIKE ? ESCAPE '!'
ORDER BY o.Value DESC, o.Id ASC
) X
WHERE RN > 1 AND RN <= 3

Arithmetic with columns in different tables in mysql [optmization]

In the following query:
SELECT
(SELECT nick FROM nicks n WHERE n.pid=p.id LIMIT 1 ORDER BY id DESC) as nick
, (
(
( SELECT COUNT(*) FROM kills k WHERE k.pid = p.id )
+
( SELECT COUNT(*) FROM votos v WHERE v.pid = p.id )
)
- (SELECT COUNT(*) FROM deaths d WHERE d.pid = p.id )
) as score
, (SELECT COUNT(*) FROM kills k WHERE k.pid = p.id ) as kills
, (SELECT COUNT(*) FROM deaths d WHERE d.pid = p.id ) as deaths
, (SELECT COUNT(*) FROM headshots h WHERE h.pid = p.id ) as headshots
, (SELECT COUNT(*) FROM votos v WHERE v.pid = p.id ) as reputation
FROM players p
WHERE p.uuid='STEAM_x:x:xxxxxx'
GROUP BY kills
This query works fine... but i think there exists a better way to do this.
Can anyone help me optimize this query?
Here is a somewhat better way to write the query:
SELECT p.*, (kills + reputation - deaths) as score
FROM (SELECT (SELECT nick FROM nicks n WHERE n.pid = p.id ORDER BY id DESC LIMIT 1
) as nick,
(SELECT COUNT(*) FROM kills k WHERE k.pid = p.id ) as kills,
(SELECT COUNT(*) FROM deaths d WHERE d.pid = p.id ) as deaths,
(SELECT COUNT(*) FROM headshots h WHERE h.pid = p.id ) as headshots,
(SELECT COUNT(*) FROM votos v WHERE v.pid = p.id ) as reputation
FROM players p
WHERE p.uuid = 'STEAM_x:x:xxxxxx'
) p
GROUP BY kills;
Note: I don't understand what the GROUP BY is doing. You are only aggregating by one column, so the rest of the columns have indeterminate values. Perhaps you intend ORDER BY.
I am guessing that the overhead for materializing the subquery before the group by is slightly less than the additional subqueries. But your version may have very comparable performance.
For either version, you want the following indexes:
players(uuid)
kills(pid)
deaths(pid)
headshots(pid)
votos(pid)

Can we make a DISTINCT of a group_concat(distinct somefield)?

http://sqlfiddle.com/#!2/37dd94/17
If I do SELECT DISTINCT I get the same results as doing just SELECT.
On the query results, you will see two activities that contains the District "Evora".
Only one should appear.
Any clue?
How about the following query (SQL FIDDLE):
SELECT GROUP_CONCAT(APA_T.district), t.name
FROM tbl_activity AS t
JOIN tbl_activity_package AS ap ON t.id = ap.id_activity
JOIN
(
SELECT DISTINCT apa.district AS district,
(
SELECT s1.id_activity_package
FROM tbl_activity_package_address s1
WHERE apa.district = s1.district
ORDER BY s1.id DESC
LIMIT 1
) AS idActivityPackage
FROM
tbl_activity_package_address apa
ORDER BY apa.district
) AS APA_T
ON ap.id = APA_T.idActivityPackage
GROUP BY t.name
ORDER BY APA_T.district;
The above query will eliminate the extra Faro and Evora.

SQL order by variable in inner statement

I have a MySQL query like this:
SELECT *
FROM backstage
WHERE backstage_id IN (
SELECT backstage_id
FROM visitor_counter
WHERE backstage_id !=0
GROUP BY backstage_id
ORDER BY COUNT( DISTINCT ( ip_address ) ) DESC
)
LIMIT 0 , 100
I get the results I want, but I would like to order it by COUNT( DISTINCT ( ip_address ) ) DESC as the inner question does.
Any tips on how to do this?
Give this a go and see if it gives you what you're after:
select bs.*
from backstage bs
inner join
(
select backstage_id,count(distinct ip_address) as distIpCount
from visitor_counter
where backstage_id !=0
group by backstage_id
) vc on vc.backstage_id = bs.backstage_id
order by vc.distIpCount desc
limit 0,100;