MySQL view taking forever - mysql

MySQL server 5.6
innodb_buffer_pool_size = 6GB
key_buffer_size = 20M
Here is my schema:
TBL_1 TBL_2 TBL_3 TBL_4 TBL_5 TBL_6
id id id id id id (int, primary key, auto increment)
uid uid uid uid uid uid (varchar 100, key index)
dev dev dev dev dev dev (varchar 80)
intf intf intf intf intf intf (varchar 100)
stat1 stat2 stat3 stat4 stat5 stat6 (float, 11)
The uid field provides the unique relationship across the table rows.
count(*) across all tables is ~52K
My desired view:
VIEW_1
dev intf stat1 stat2 stat3 stat4 stat5 stat6
Here is an example of the view select sql I've tried thus far:
select a.dev, a.intf, a.stat1, b.stat2, c.stat3, d.stat4, e.stat_5, f.stat_6
from TBL_1 a
inner join TBL_2 b on b.uid = a.uid
inner join TBL_3 c on c.uid = a.uid
inner join TBL_4 d on d.uid = a.uid
inner join TBL_5 e on e.uid = a.uid
inner join TBL_6 f on f.uid = a.uid
Once I get beyond 2 joins the query becomes unresponsive. For the above query explain return
|| *id* || *select_type* || *table* || *type* || *possible_keys* || *key* || *key_len* || *ref* || *rows* || *Extra* ||
|| 1 || SIMPLE || e || ALL || uid || || || || 51391 || ||
|| 1 || SIMPLE || c || ref || uid || uid || 102 || db.e.uid || 1 || Using index condition ||
|| 1 || SIMPLE || a || ref || uid || uid || 102 || db.c.uid || 1 || ||
|| 1 || SIMPLE || b || ref || uid || uid || 257 || db.c.uid || 1 || Using index condition ||
|| 1 || SIMPLE || f || ref || uid || uid || 257 || db.c.uid || 1 || Using index condition ||
|| 1 || SIMPLE || d || ref || uid || uid || 102 || db.e.uid || 1 || Using index condition ||
Any suggestions on how this could be improved?

Check explain plan for the query
EXPLAIN SELECT * from ABC;
Check this Understanding the Query Execution Plan
As per output of explain consider adding indexes or using other Optimizing techniques
If this does not help paste your explain plan to look further.
Cheers !!

Related

How to SELECT data which have relation with 2 or more data in MANY to MANY tables

I have 3 tables in mySQL called tb_tour_trip, tb_facilities and tb_master_facilities which have many to many relationship. I want to select 1 data in tb_tour_trip which have 2 or more master_facilities. example : i wanna select trip which have facilities: AC and WiFi
This is the tb_tour_trip table :
tb_tour_trip
============
id || name || description || price
===================================
1 || trip1 || example trip || $200
2 || trip2 || example trip || $300
This is the tb_facilities table (For MANY to MANY relations):
tb_facilities
============
id || id_master_facilities_ref || id_tour_trip_ref
===================================
1 || 1 || 1
2 || 2 || 1
3 || 1 || 2
3 || 3 || 2
And this is my tb_master_facilities table:
tb_master_facilities
============
id || name || status
====================
1 || WiFi || 1
2 || AC || 1
3 || TV || 1
if tried this Query :
SELECT id_tour_trip_ref FROM tb_facilities WHERE id_master_facilities_ref IN(1,2);
this show me id_tour_trip which have facilities : AC or Wifi, but that's not what i want.
I want the id_tour_trip which have AC AND Wifi only.
Exactly output is just :
id_tour_trip_ref
================
1
How can i do that? thanks before.
There's a number of ways to do this, but a reasonably easy to read/maintain method is this:
SELECT id
FROM tb_tour_trip AS ttt
WHERE (
SELECT COUNT(DISTINCT id_master_facilities_ref)
FROM tb_facilities AS tbf
WHERE tbf.id_tour_trip_ref = ttt.id
AND tbf.id_master_facilities_ref IN (1, 2)
) = 2;
If only to get your exact output, this could also work:
SELECT id_tour_trip_ref
FROM tb_facilities INNER JOIN tb_master_facilities ON tb_facilities.id=tb_master_facilities.id
WHERE name IN ("WiFi","AC") AND id_master_facilities_ref IN (1,2)
GROUP BY id_tour_trip_ref;
One method is aggregation with a HAVING clause:
SELECT f.id_tour_trip_ref
FROM tb_facilities f INNER JOIN
tb_master_facilities
ON f.id = mf.id
WHERE f.name IN ('WiFi', 'AC')
GROUP BY f.id_tour_trip_ref
HAVING COUNT(*) = 2;
I would join all of the tables together and just do 'where condition1 and condition2. This is propably less efficient but imho more comprehensive.
select distinct tb_tour_trip.id from tb_tour_trip
inner join tb_facilities on tb_facilities.id_tour_trip_ref = tb_tour_trip.id
inner join tb_master_facilities on tb_master_facilities.id = tb_facilities.id_master_facilities_ref
where tb_master_facilities.name = 'WiFi'
and tb_master_facilities.name = 'AC'
i've found the answer from forum on Facebook. thanks for all your advice :)
This is the exactly i need.
SELECT id_tour_trip_ref, GROUP_CONCAT(id_master_facilities_ref) AS facilities FROM tb_facilities GROUP BY id_tour_trip_ref HAVING FIND_IN_SET('1', facilities) > 0 AND FIND_IN_SET('2', facilities) > 0

mysql inner join 3 tables get last record from max date

I have 3 tables which i would like joined but with some addition to summing certain columns
I need to display every user_id and user name in the database but also joining their corresponding accounts tables with some addition and date based query. Here is an example of what im trying to say.
Table Users
user_id || user_name || user_dob
301 || john doe || 1955-01-01
312 || Bill Gates || 1976-01-01
Table accounts
id || child_id || inv_date || inv_total || inv_funding
38 || 301 || 2018-05-03 12:56:38 || 486.5 || 45.55
39 || 301 || 2018-08-03 14:56:38 || 222.5 || 118.5
40 || 312 || 2018-04-03 11:56:38 || 26.23 || 318.5
41 || 312 || 2018-05-03 12:56:38 || 223.22 || 238.5
42 || 312 || 2018-06-03 13:56:38 || 486.5 || 258.5
Table accounts_balance
id || child_id || balance
1 || 301 || 302.00
2 || 312 || 43.33
The resulting table needs to look like this for every user in Table Users ordered by user_name
User Name || Last Invoiced Date || Last Funding Amount || Last Invoice Amount || Total Funding To Date || Total Invoiced To Date || Balance
john doe || 2018-08-03 14:56:38|| 118.5 || 222.5 || 164.05 || 712.00 || 302.00
here is some of my code excuse the c# formating clearly not working well.
invoiceTable.Query = "select " +
"(" +
"max(users.user_id), " +
"users.user_name, " +
"max(accounts.inv_date), " +
"max(accounts.inv_funding) as Last_funding, " +
"max(accounts.inv_total) as Last_Invoice, " +
"sum(accounts.inv_funding) as Total_Funding, " +
"sum(accounts.inv_total) as Total_Invoiced from users " +
")" +
"left join accounts on accounts.child_id = users.user_id group by users.user_name";
you can try by using sub-query
select t1.*,t2.inv_funding as Last_funding,t2.inv_total as
Last_Invoice,ab.balance from
(
select users.user_id,
users.user_name,
max(accounts.inv_date) as inv_date,
sum(inv_funding) as Total_Funding,
sum(inv_total) as Total_Invoiced
from users
left join accounts on accounts.child_id = users.user_id
group by users.user_name,users.user_id
) as t1
left join accounts t2 on t1.inv_date=t2.inv_date
left join accounts_balance ab on t1.user_id =ab.user_id

find the manager of sales employee

my table is 'DESIGNATION'
ID || DEPT_ID || E_NAME || DESIGNATION
1 || 12 || A || EMPLOYEE
2 || 12 || B || MANAGER
3 || 12 || C || EMPLOYEE
4 || 14 || D || MANGER
5 || 14 || E || EMPLOYEE
6 || 14 || F || EMPLOYEE
I want the manager name through their DEPT_NAME....
Mean result will look like
ID || DEPT_ID || E_NAME || DESIGNATION || MANAGER
1 || 12 || A || EMPLOYEE || B
2 || 12 || B || MANAGER || B
3 || 12 || C || EMPLOYEE || B
4 || 14 || D || MANGER || D
5 || 14 || E || EMPLOYEE || D
6 || 14 || F || EMPLOYEE || D
My query is
SELECT `ID`,`DEPT_ID`,`ENAME`,`DESIGNATION`,
(select `ENAME` from `DESIGNATION` where
(select `E_NAME` from `DESIGNATION` where
(SELECT `DEPT_ID` FROM `DESIGNATION` WHERE `DESIGNATION` = 'EMPLOYEE')
=
(SELECT `DEPT_ID` FROM `DESIGNATION` WHERE `DESIGNATION` = 'MANAGER') and `DESIGNATION`='MANAGER')
AS MANAGER
from `DESIGNATION`
but its not working...
You just need a JOIN operation. This is a basic concept when working with database. You should take some time reading about it.
Something like that?
SELECT A.*, B.E_NAME
FROM DESIGNATION AS A, DESIGNATION AS B
WHERE B.DESIGNATION = "MANAGER"
AND A.DEPT_ID = B.DEPT_ID
Or using explicit JOIN syntax:
SELECT A.*, B.E_NAME
FROM DESIGNATION AS A JOIN DESIGNATION AS B USING (DEPT_ID)
WHERE B.DESIGNATION = "MANAGER"
EDIT:
If you could have multiple managers, you could use the GROUP_CONCAT aggregate functions with explicit group by E_NAME (assuming this is an unique key):
SELECT A.*, GROUP_CONCAT(B.E_NAME)
FROM DESIGNATION AS A, DESIGNATION AS B
WHERE B.DESIGNATION = "MANAGER"
AND A.DEPT_ID = B.DEPT_ID
GROUP BY(A.E_NAME)
Old-school join syntax - sorry - but your sub queries don't make much sense.
Select d.ID,
d.DEPT_ID,
d.E_NAME,
d.DESIGNATION
m.MANAGER
from designation d,
designation m
where d.dept_id = m.dept_id
and m.designation = 'MANAGER'
This looks like a better job for whatever server-side script you're running. For example, in PHP, you can do this:
$managers = []; // array() before version 5.4
$employees = []; // see above comment
$query = "SELECT * FROM `DESIGNATION`";
$result = mysql_query($query); // adjust according to extension of choice
while($row = mysql_fetch_assoc($result)) { // same as previous comment
$employees[] = $row;
if( $row['DESIGNATION'] == "MANAGER") $managers[$row['DEPT_ID']] = $row['E_NAME'];
}
foreach($employees as $i=>$e) {
$employees[$i]['MANAGER'] = $managers[$e['DEPT_ID']] ?: "Nobody";
}
A simple JOIN will help, I prefer an explicit JOIN instead of the implicit comma notation:
SELECT `ID`,`DEPT_ID`,`E_NAME`,`DESIGNATION`, m.e_name, AS `MANAGER`
FROM `DESIGNATION` e
INNER JOIN `DESIGNATION` m
ON e.dept_id = m.dept_id
WHERE m.designation = 'MANAGER'
Can't we simply do this, in more simpler way ?
SELECT ID,DEPT_ID,ENAME,DESIGNATION,
case
when DEPT_ID=12 then 'B'
when DEPT_ID=14 then 'D'
end "MANAGER"
from DESIGNATION
Try the following
SELECT t.id
,t.dept_id
,t.e_name
,t.designation
,ta.e_name As Manager FROM Table1 t JOIN (SELECT e_name,dept_id
FROM Table1 WHERE designation = 'MANAGER'
GROUP BY dept_id,e_name) ta ON ta.dept_id = t.dept_id
SQLFiddle Demo

leaderboard sql query

I'm implementing a leaderboard for our group project.
this is my query for counting the number of status userid 2 have in the table.
$query = "SELECT COUNT(content) FROM status WHERE userid=2 ";
now, how could i query the userid with most number of status?
example table
|| content || userid ||
|| x || 1 ||
|| xx || 1 ||
|| y || 2 ||
|| yy || 2 ||
|| yyy || 2 ||
|| z || 3 ||
with result
1st = 2 with 3 status
2nd = 1 with 2 status
3rd = 3 with 1 status
what will i do to limit the result? what if i only need the top 5?
in relation to this problem
i was thinking of adding another column(number_of_status) in the user table instead of this multiple queries... which is better?
THANK YOU!
Should be able to do this with a GROUP BY:
SELECT userid, COUNT(*) AS status_count
FROM status
GROUP BY userid
ORDER BY status_count DESC
LIMIT 5
You can achieve this with a GROUP BY query, and LIMIT.
SELECT userid, COUNT(content) as statuses
FROM status
GROUP BY userid
ORDER BY statuses DESC
LIMIT 5

SQL query. Need to GROUP BY the combination of values in two columns

So I have two tables. One with amount of shares that people have:
Person_ID || Shares
1 || 100
2 || 150
3 || 315
4 || 75
5 || 45
...
and another with info whether a person is present (there are two columns specifying that) and what group does he belongs to:
Person_id || barcode_presence || manual_presence || group_id
1 || NULL || 1 || NULL
2 || 1 || NULL || 1
3 || 1 || 0 || 2
4 || 0 || NULL || 1
5 || 1 || 0 || 1
...
I need to find out the sum of all present and sum of all absent shares in every single group (NULL group is also a group).
The thing I cannot figure out is that whether the person is present or absent is figured out by the combination of two columns (barcode_presence and manual_presence):
if manual_presence = 1 --> present
else if (manual_presence = NULL and barcode_presence = 1) --> present
everything else is absent.
Thats what i've got so far.
SELECT presence_list.Person_id, presence_list.person_name,
presence_list.barcode_presence, presence_list.manual_presence,
presence_list.group_id, people_data.Share
, sum(people_data.Share)
FROM presence_list
INNER JOIN people_data
ON presence_list.Person_id = people_data.Person_ID
WHERE meeting_id = 1 AND voting_id = 0
GROUP BY presence_list.group_id,
barcode_presence,
manual_presence
ORDER BY presence_list.group_id
;
But obviously that does not give me the desired result. it groups by every possible combination of group_id, barcode_presence and manual_presence like following:
Person_id || barcode_presence || manual_presence || group_id || share || sum(shares)
1 || NULL || 1 || NULL || 100 || 100
2 || 1 || NULL || 1 || 150 || 150
4 || 0 || NULL || 1 || 75 || 75
5 || 1 || 0 || 1 || 45 || 45
3 || 1 || 0 || 2 || 315 || 315
So i'm looking for a way to (in this case) connect person with id 4 and 5, because they are both absent and they are from the same group, like this:
Person_id || barcode_presence || manual_presence || group_id || share || sum(shares)
1 || NULL || 1 || NULL || 100 || 100
2 || 1 || NULL || 1 || 150 || 150
5 || 1 || 0 || 1 || 45 || **120**
3 || 1 || 0 || 2 || 315 || 315
I've tried using HAVING clause but I didn't found a way for it to handle complex expressions like manual_presence = 1 OR (manual_presence is null AND barcode_presence = 1)
Any ideas or suggestions would be appreciated! thanks beforehand!
if only one of the two columns can ever be populated, Use coalesce...
Group By group_id, Coalesce(barcode_presence, manual_presence)
if not, (can, say, the barcode_presence = 1 and the presence = 0?), then yu need to decide what groups of possible combinations of values you want to group by, and just group by an expression that does that... drawing a chart of value combinations may help...
barcode value
manual Null 0 1
Null GrpA GrpA GrpB
0 GrpC GrpC GrpB
1 GrpB GrpB GrpB
say you want to group by those where both are 0, and all the others, then you would write...
Group By group_id, case When isnull(barcode_presence, 0) = 0
And isnull(manual_presence, 0) = 0 Then 0 Else 1 End
You can use a case to make a group for present and non-present people:
select group_id
, case
when barcode_presence = 1 and manual_presence = 1 then 1
else 0
end
, sum(share)
from presence_list pl
join people_data pd
on pl.person_id = pd.person_id
group by
group_id
, case
when barcode_presence = 1 and manual_presence = 1 then 1
else 0
end
order by
group_id
If I understand correctly, I think producing two columns is the way to go:
select pd.group_id,
SUM(isPresent * pd.shares) as PresentShares,
SUM((1 - isPresent)*pd.shares) as NotPresentShares
from (select pl.*,
(case when manual_presence = 1 or
manual_precence is null and barcode_presence = 1
then 1 else 0
end) as IsPresent
end
from presence_list pl
) pl join
people_data pd
on pl.Person_id = pd.Person_ID
group by pd.group_id
The subquery just applies your logic to create a flag that is 1 for present and 0 for not-present. Conveniently, when coded this way, "1 - IsPresent" is 1 for not-present and 0 for present.
This value then used in the aggregation to aggregate the shares. You could have the outer query be:
select group_id, IsPresent, sum(pd.shares)
from . . .
group by group_id, IsPresent
If you really wanted the values on separate rows.