MySql Query Optimization for parent child relationship - mysql

I am in trouble with large database with one of my query which is obvious not correct and I am not good in database side so I am not sure what I can do to optimize this query. Below is my table structure and details what data I need to fetch.
User Table
UserID UserName UserRole ParentID
1 ABC1 2 0
2 ABC2 2 0
3 ABC3 2 1
4 ABC4 2 1
5 ABC5 2 2
Survey Table Structure
SurveryID SurveyTitle UserID
1 S1 3
2 S2 3
3 S3 4
4 S4 4
3 S3 4
4 S4 5
3 S3 3
4 S4 5
3 S3 3
4 S4 4
The users which don't have parentID are Supervisors and who has parentID are Sales persons under that Parent supervisor.
So, I want to get list of Supervisor with their sales person with number os surveys they did. Something like below.
SuperVisorName SurveyCount
ABC1 10
ABC3 4
ABC4 6
ABC2 18
ABC5 18
In our current system, we first get all supervisors and then look through all supervisors to get their sales person with their surveys.
This make query very slow thus, resulting timeout error. We have 40k records for survey right now and hoping to grow more than 100k.
I searched little on that and we found Union can help in this but I am not sure how to apply in my scenario ? I guess this should be a single query to accomplish said result ?
Please let me know if you have any questions. I would clarify those.
Will appreciate your help on this. Thanks for your time.

Try this query
select
if(a.username is null, b.username, a.username) as username,
a.parentid, -----if(a.username is null, 0, a.parentid) parentid,
a.cnt -----Replace above line if you want value to be 0 of parentId
from
(select
parentid,
username,
count(*) cnt
from
tbl1 a
inner join
tbl2 b
on
a.userid = b.userid
group by
parentid,
username
with rollup) a
inner join
tbl1 b
on
a.parentid = b.userid
Results:
| USERNAME | PARENTID | CNT |
-----------------------------
| ABC3 | 1 | 4 |
| ABC4 | 1 | 4 |
| ABC1 | 1 | 8 |
| ABC5 | 2 | 2 |
| ABC2 | 2 | 2 |
SQL FIDDLE

Related

The best way to Find entries missing after inner-join?

Lets suppose that we have the following 3 tables
Animal
id name
1 dog
2 cat
3 crow
Actions
id name
1 run
2 walk
3 jump
4 fly
5 puppy_eyes
6 swim
Animal_Actions
id Animal_id action_id
1 1 1
2 1 2
3 1 3
4 1 5
5 2 1
6 2 2
7 2 3
8 3 2
9 3 4
I would like to find all the missing animal actions for certain animals
For example if we input dog and cat( id 1 and 2) we should get the following (1,4),(1,6),(2,4),(2,5), (2,6)
and if we input crow (3) we get the following (3,1),(3,3),(3,5), (3,6) .
Currently I'm doing an inner join between Animal and Animal_Actions table based on animal ID and importing this into a SET in my code and checking if every possible permutation is present in this set and collecting the missing ones. I'm not sure if the process I follow is the most efficient one, is there a better way to do this when the data is at a large scale ? Thanks in advance.
If you'll be filtering on a small number of Animal records, one approach is to do a CROSS JOIN with the Actions table. That will give you all action combinations for each Animal record. Then do an OUTER JOIN to Animal_Actions to identify which ones are missing.
For example, using cat = 2 and dog = 1
SELECT ani.id AS Animal_Id
, ani.Name AS Animal_Name
, act.id AS Action_Id
, act.Name AS Action_Name
FROM Animal ani
CROSS JOIN Actions act
LEFT JOIN Animal_Actions aa ON ani.id = aa.Animal_id
AND aa.Action_Id = act.id
WHERE ani.id IN (1,2)
AND aa.id IS NULL
ORDER BY ani.Name, act.Name
;
Results:
Animal_Id | Animal_Name | Action_Id | Action_Name
--------: | :---------- | --------: | :----------
2 | cat | 4 | fly
2 | cat | 5 | puppy_eyes
2 | cat | 6 | swim
1 | dog | 4 | fly
1 | dog | 6 | swim
db<>fiddle here

Mysql subtract from another table

I have tried to search this forum for an answer but can't seem to find one.
My problem: I have 3 tables. One with items, one with in-going deliveries and one with outgoing deliveries.
Table called items:
id | item_name
----------------
1 | Bike
2 | Helmet
3 | Pedal
4 | Light
Table called ingoing:
id | item_id | quantity
-----------------------
1 | 2 | 5
2 | 3 | 2
3 | 4 | 1
4 | 1 | 5
5 | 2 | 4
6 | 1 | 6
7 | 3 | 5
Table called outgoing:
id | item_id | quantity
-----------------------
1 | 3 | 2
2 | 1 | 1
3 | 2 | 3
4 | 3 | 4
5 | 1 | 2
6 | 2 | 1
7 | 4 | 1
What I want to do is get the total amount in stock by subtracting the outgoing deliveries from the in-going and order it by the item with the least amount.
Maybe there is a better way to do this?
This is the query that I got, but the SUM amounts isn't correct. Can someone help me and explain why the SUM amounts isn't correct and how should I solve this with the best way?
SELECT items.id AS ID,
items.item_name,
Sum(ingoing.quantity) - Sum(outgoing.quantity) AS InStock
FROM items
LEFT JOIN ingoing
ON ingoing.item_id = items.id
LEFT JOIN outgoing
ON outgoing.item_id = items.id
GROUP BY ID
ORDER BY InStock ASC
This is the result I want from this:
ID | item_name | InStock
---------------------------
4 | Light | 0
3 | Pedal | 1
2 | Helmet | 5
1 | Bike | 8
What I Get:
ID | item_name | InStock
---------------------------
4 | Light | 0
3 | Pedal | 2
2 | Helmet | 10
1 | Bike | 16
It can be easy to forget the multiplicative effect of a join. When you encounter problems like this, check the result of the join, prior to filtering/grouping:
SELECT items.id,
items.item_name,
ingoing.id AS ingoing,
outgoing.id AS outgoing
FROM items
LEFT JOIN ingoing
ON ingoing.item_id = items.id
LEFT JOIN outgoing
ON outgoing.item_id = items.id
See it on sqlfiddle.
As you can see, the resultset contains multiple records with the same ingoing.id values, and multiple records with the same outgoing.id values. This is because each ingoing record for a particular item has joined with every outgoing record for that same item: thus there are 4 pedals in the resultset (2 incoming x 2 outgoing), etc.
Aggregating each table by item prior to joining (and thus ensuring that there is only 1 record per item on each side of the join) will achieve what you're after:
SELECT items.id AS ID,
items.item_name,
ingoing.quantity - outgoing.quantity AS InStock
FROM items
LEFT JOIN (
SELECT item_id AS id, SUM(quantity) AS quantity
FROM ingoing
GROUP BY item_id
) AS ingoing USING (id)
LEFT JOIN (
SELECT item_id AS id, SUM(quantity) AS quantity
FROM outgoing
GROUP BY item_id
) AS outgoing USING (id)
ORDER BY InStock ASC
See it on sqlfiddle.
Note that your problem would have been greatly simplified by only having a single underlying table of stock movements, with positive quantities indicating movements in one direction and negative quantities indicating movements in the opposite direction: then a simple groupwise summation of the whole table would yield your desired results.
Try this query:
SELECT items.id AS ID, items.item_name, (SELECT SUM(quantity) from ingoing
WHERE ingoing.item_id = items.id) - (SELECT SUM(quantity) from outgoing WHERE
outgoing.item_id = items.id) AS InStock FROM items ORDER BY InStock ASC;

Best way to limit results in MySQL with user subcategories

I am trying to essentially solve for the following:
1) Find all users in the system who ONLY have programID 1.
2) Find all users in the system who have programID 1 AND any other active program.
My tables structures (in very simple terms are as follows):
users
userID | Name
================
1 | John Smith
2 | Lewis Black
3 | Mickey Mantle
4 | Babe Ruth
5 | Tommy Bahama
plans
ID | userID | plan | status
---------------------------
1 | 1 | 1 | 1
2 | 1 | 2 | 1
3 | 1 | 3 | 1
4 | 2 | 1 | 1
5 | 2 | 3 | 1
6 | 3 | 1 | 0
7 | 3 | 2 | 1
8 | 3 | 3 | 1
9 | 3 | 4 | 1
10 | 4 | 2 | 1
11 | 4 | 4 | 1
12 | 5 | 1 | 1
I know I can easily find all members with a specific plan with something like the following:
SELECT * FROM users a JOIN plans b ON (a.userID = b.userID) WHERE b.plan = 1 AND b.status = 1
but this will only tell me which users have an 'active' plan 1.
How can I tell who ONLY has plan 1 (in this case only userID 5) and how to tell who has plan 1 AND any other active plan?
Update: This is not to get a count, I will actually need the original member information, including all the plans they have so a COUNT(*) response may not be what I'm trying to achieve.
Okey..., You have mentioned that you don't want a count of the other plans that particular user hold, but according to your requirement you can have optimum output like,
this many user have plan 1 active and this is the count of the other active plan he have exept plan one.
SELECT u.userID,
u.Name,
p.Id AS Plan1ActiveId,
(SELECT COUNT(*) FROM plans sub where sub.userID = u.userID and sub.plan <> 1 and sub.status = 1) AS OtherActivePlanCount
FROM users u JOIN plans p ON (u.userID = p.userID)
where p.plan = 1 AND p.status = 1;
or if you want to drill it down towards the details of the plan you need to apply second query that bring you other plans' detail.
Well you can make use of the COUNT combined with a GROUP BY and the HAVING clause.
For example:
SELECT * FROM users a JOIN plans b ON (a.userID = b.userID) GROUP BY b.user_ID HAVING COUNT(*) = 1 AND b.plan = 1
This will group rows by user id, and only include rows where the count = 1 (only 1 line in the plans table)
Note - I ended up simplifying the overall concept in my head. I simply did a subselect of all members who I knew had the plan I was looking to target. I then did an overall select of all plans by members who either had IDs that were IN or NOT IN the subselect of known ids).
After testing, this was much less resource intensive and a bit easier to use than the suggestions but appreciate the help.

MySQL: Performance Querying Multiple Columns With a Single Row

My tables simplified something like this..
Table Categories
cat_id | cat_name
-------------------
1 | Baseball
2 | Hockey
3 | Football
Table Day
day_id | day_name
--------------------
1 | Friday
2 | Saturday
3 | Sunday
Table Day_Categories
day_id | cat_id
-------------------
1 | 1
1 | 2
2 | 2
2 | 3
3 | 1
3 | 2
3 | 3
And I want my resulting data to look something like this...
"Friday","Baseball, Hockey"
"Saturday","Hockey, Football"
"Sunday", "Baseball, Hockey, Football"
It doesn't necessary need to be in three row format. I just need the same resulting data.
From a performance prospective with lots of data what's likely the best way to achieve this result?
Please try this one
SELECT day_name, GROUP_CONCAT ( cat_name ) as categories
FROM Days as d
LEFT JOIN Days_Categories as dnc ON d.day_id = dnc.day_id
LEFT JOIN Categories as c ON c.cat_id = dnc.cat_id
GROUP BY ( dnc.day_id)

how to select 3 random items of each category?

I need some help writing a select query basically I have the table structured as below:
cat_prod
----------
cid | pid
----------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
1 | 2
2 | 3
3 | 4
4 | 5
5 | 1
1 | 3
2 | 4
3 | 5
4 | 1
5 | 2
Now I would like to select at least 3 random pid's of each cid where it exists or the maximum pid's if less than 3, how would i do that in one query? Baring in mind I would like the query to be as efficent as possible and that the table data is likely to grow considerablly.
Thanks
Although some changes might be needed, the following query is almost appropriate:
select C.cid, C.pid
from cat_prod C
where C.pid in (select c1.pid from cat_prod c1 order by (pid) limit 3);