Optimise SQL query for the report - mysql

This is SQL query I wrote, it work fine but it is slow.
SELECT D.Username,
SUM(CASE WHEN D.type = 'Yes' THEN 1 ELSE 0 END) as Yes,
SUM(CASE WHEN D.type = 'No' THEN 1 ELSE 0 END) as No,
SUM(CASE WHEN D.type = '' THEN 1 ELSE 0 END) as Other,
SUM(CASE WHEN S.mobile IS NULL THEN 0 ELSE 1 END) as Sales,
COUNT(*) as TOTAL FROM dairy as D
LEFT JOIN (SELECT DISTINCT mobile FROM sales) as S on D.MobileNo = S.mobile
WHERE source = 'Network' AND UNIX_TIMESTAMP(CheckDate) >= 1309474800 AND UNIX_TIMESTAMP(CheckDate) <= 1309561200
group by D.Username order by TOTAL DESC
As you can see it count number of Yes, No, Other and the matching MobileNo (D.MobileNo = S.mobile) sale.
I have tried adding index to type, username, mobile, MobileNO, CheckDate and source - the performance did not improve much.

Three points to notice in your query:
1. There's a chance the `LEFT JOIN` is giving you performance issues.
However, you need it, since it is possible that there are D.MobileNo values that will not be present in SELECT DISTINCT mobile FROM sales. Any other work around (yes, there are options) will most likely decrease performance. But your performance might be improved by observing the next items.
2. Make sure you have indexes in the key columns:
D.type
S.mobile
D.MobileNo
D.Username
D.Source
D.CheckDate
3. You might be having problems with filtering by `UNIX_TIMESTAMP(CheckDate)`
This might be the key issue. You might be having problems with filtering by UNIX_TIMESTAMP(CheckDate) instead of CheckDate, specially if Dairy has a large amount of records. The problem is that even if you have an index for CheckDate, it will probably not be used because of the function. Try to filter by CheckDate itself.

If this is time critical it can also make sense to store more data.
Here this means that you add INT columns for YesValue, NoValue, OtherValue and fill them with 0 or 1 in your application. By doing this you can remove the case calculations from your SELECT-part.
Also please post all indexes currently available and as the comments say the CREATE-statement for the table.

Related

How do I calculate the difference of two alias for sorting

Considering the following code:
SELECT SUM(w.valor),
SUM(CASE WHEN w.tipo = '+' THEN w.valor ELSE 0 END) AS total_credit,
SUM(CASE WHEN w.tipo = '-' THEN w.valor ELSE 0 END) AS total_debit,
w.clientUNIQUE,
c.client as cclient
FROM wallet AS w
LEFT JOIN clients AS c ON w.clientUNIQUE = c.clientUNIQUE
WHERE w.status='V'
GROUP BY w.clientUNIQUE
ORDER BY total_credit-total_debit
I'm trying to calculate the difference of two aliased calculated values for sorting purposes, but I'm getting the following error:
Reference 'total_credit' not supported (reference to group function)
What am I doing wrong and how can I order results by using the difference value between the two aliases?
You can't refer to columns by their alias in the same select expression, so there are 2 options...
Repeat the expressions in the order by (yuk):
ORDER BY
SUM(CASE WHEN w.tipo = '+' THEN w.valor ELSE 0 END) AS total_credit -
SUM(CASE WHEN w.tipo = '-' THEN w.valor ELSE 0 END) AS total_debit
Or easier on the brain and easier to maintain (DRY), order via a sub query:
select * from (
<your query without the ORDER BY>
) q
ORDER BY total_credit - total_debit

Difference between these two mysql queries

I want to know the difference between these two queries: The first query is giving me all the records and its just fine.
Select * from table1 where tender_id='$tender_id' group by supplier_name
But in the following query I have added a sum(case), but I am not getting the desired output. The first query is showing all the records, but the second query is not showing all the records. What mistake am I making?
select cs.*, tender_id,
SUM(CASE WHEN ifmain = 'Yes' THEN total_inr ELSE 0 END) AS maintotal,
SUM(CASE WHEN ifmain = 'No' THEN total_inr ELSE 0 END) AS subtotal
from table1 cs
where cs.tender_id='$tender_id'
group by cs.supplier_name
I want to know if the second query can display all the records with conditions (tender_id)? or its iterating more?
In standard SQL, a query that includes a GROUP BY clause cannot refer
to nonaggregated columns in the select list that are not named in the
GROUP BY clause.
see (for example) https://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html
MySQL has a an unfortunate (and in my opinion incorrect) default behavior when using a GROUP BY clause. This query would NOT be valid in most SQL databases:
Select * from table1 where tender_id='$tender_id' group by supplier_name
and it would not be valid in MySQL either if the ONLY_FULL_GROUP_BY SQL mode has been enabled.
I strongly recommend you treat all queries using GROUP BY as needing ALL non-aggregating columns in that clause. e.g.
select
cs.supplier_name
, SUM(CASE WHEN ifmain = 'Yes' THEN total_inr ELSE 0 END) AS maintotal
, SUM(CASE WHEN ifmain = 'No' THEN total_inr ELSE 0 END) AS subtotal
from table1 cs
where cs.tender_id='$tender_id'
group by
cs.supplier_name
If you need extra columns then e.g.
select
cs.supplier_name
, tender_id
, SUM(CASE WHEN ifmain = 'Yes' THEN total_inr ELSE 0 END) AS maintotal
, SUM(CASE WHEN ifmain = 'No' THEN total_inr ELSE 0 END) AS subtotal
from table1 cs
where cs.tender_id='$tender_id'
group by
cs.supplier_name
, tender_id
and so on. Of course as you include more columns in the group by clause you may increase the number of rows produced, but that is how GROUP BY should work.

MySQL Attendance System

I have made an attendance system for a project, However I'm stuck right now by trying to create a query.
SELECT
COUNT(Students.idStudents) total,
SUM(case when Attendance.status LIKE 'present' then 1 else 0 end) present,
SUM(case when Attendance.status LIKE 'late' then 1 else 0 end) late,
SUM(case when Attendance.status is null then 1 else 0 end) absents
FROM Students, Schools, Tags LEFT JOIN Attendance
ON Attendance.tagCode = Tags.tagCode
WHERE Schools.idSchools = Students.idSchools
AND Tags.idStudents = Students.idStudents
This code works and generates an attendance. However this will show all the dates.
When I add in another line to specify date
AND Attendance.date = DATE(NOW());
It will not show anything..
There's 'Present', 'Late' status for the attendance however if the student's record in that table doesn't exist, it is considered as absent.
How do I do that?
Assuming you're using a case insensitive collation, your purported solution can be rewritten as follows:
SELECT COUNT(p.idStudents) total
, SUM(CASE WHEN a.status = 'present' THEN 1 ELSE 0 END) present -- [or just SUM(a.status = 'present')]
, SUM(CASE WHEN a.status = 'late' THEN 1 ELSE 0 END) late
, SUM(CASE WHEN a.status IS NULL THEN 1 ELSE 0 END) absents
FROM Students p
JOIN Schools s
ON s.idSchools = p.idSchools
JOIN Tags t
ON t.idStudents = p.idStudents
LEFT
JOIN Attendance a
ON a.tagCode = t.tagCode
AND a.date = CURDATE() ;
For next time: Your ERD shows 10 tables, but only 4 feature in this problem. If a table isn't likely to be part of the proposed solution, don't show it. Don't provide pictures. Instead, where possible, provide proper DDLs (and/or an sqlfiddle), TOGETHER WITH THE DESIRED RESULT SET based upon a minimal, but properly representative data set.
Welcome to SO.
Figured it out..
SELECT
COUNT(Students.idStudents) total,
SUM(case when Attendance.status LIKE 'present' then 1 else 0 end) present,
SUM(case when Attendance.status LIKE 'late' then 1 else 0 end) late,
SUM(case when Attendance.status is null then 1 else 0 end) absents
FROM Students, Schools, Tags LEFT JOIN (SELECT * FROM Attendance WHERE Attendance.date = NOW()) AS Attendance
ON Attendance.tagCode = Tags.tagCode
WHERE Schools.idSchools = Students.idSchools
AND Tags.idStudents = Students.idStudents
The problem with the original query was you were asking for students where their attendance was "NOW" where no student was ever now but they are current attending classes. They may have signed in at 8am and you run the query at 10am. You'd need to manipulate the datetime to choose your start time and end time based on the current day.
timestampadd(HOUR, 08, CURDATE()) - this will give you 8am, you'd then query for when attendance.date is greater than or equal to 8am and then potentially less than or equal to 4pm?

Laravel group by and SUM

Having trouble getting my head around this one, it likely doesn't help I don't have any SQL 'group by' experience.
I have a table that has a transaction_type column and an amount column. Basically I am trying to use SQL/Eloquent to get the following in a single query (if possible, I know this isn't sql):
add = SUM(amount) where transaction_type == 1
delete = SUM(amount) WHERE transaction_type == 2
return from MySQL: add-delete
I'm assuming this would be done using groupby, but despite my best efforts I haven't be able to find a solution by reading the sql documentation.
I think I have got it using plain SQL, but how would I convert this to Eloquent:
SELECT `transaction_type`, SUM(`amount`) FROM `credit_logs` WHERE `to_group_id` = '1'
I can't help you with eloquent, but your query should be something like this:
SELECT
SUM(CASE WHEN transaction_type = 1 THEN amount ELSE NULL END)
-
SUM(CASE WHEN transaction_type = 2 THEN amount ELSE NULL END)
FROM credit_logs
WHERE to_group_id = 1;
or for all id's you wish to group by:
SELECT
SUM(CASE WHEN transaction_type = 1 THEN amount ELSE NULL END)
-
SUM(CASE WHEN transaction_type = 2 THEN amount ELSE NULL END)
FROM credit_logs
GROUP BY to_group_id;
Easiest would be, if you'd find a way to just execute a query with eloquent. Never understood why someone wants to transform a nice readable query into some "improved" syntax of a framework.

MySQL sum on condition does not work in a view

In MySQL (v5.5), the value of numNotCast, numInFavor is always 0 when selecting all rows of the view. If the select statement is executed alone (not in the view), it works as expected returning the correct count of the the number of rows in the vote column equal to value 'notcast' and 'infavor'.
CREATE OR REPLACE
VIEW `Stats` AS
select
sum(case when `p`.`vote` = 'notcast' then 1 else 0 end) AS `numNotCast`,
sum(case when `p`.`vote` = 'infavor' then 1 else 0 end) AS `numInFavor`
from
(`Debate` `d`
join `Participant` `p` ON ((`d`.`debateId` = `p`.`debateId`)))
group by `d`.`debateId`
Is this a limitation on MySQL views? How do you accomplish this conditional summing function in the view?
Then your statement is correct as SQLFiddle confirms, so your problem is probably somewhere else
CREATE OR REPLACE VIEW Stats AS
select
Debate.debateId,
sum(case when Participant.vote='notcast' then 1 else 0 end) as numNotCast,
sum(case when Participant.vote='infavor' then 1 else 0 end) as numInFavor
from
Debate
inner join
Participant on Participant.debateId = Debate.debateId
group by
Debate.debateId
Updated after the clarification
There are many limitations in MySQL views, but I don't think this is one of them.
I think it is strange that the name of the view is in single quotes. That might be allowed, but the view may not be doing what you want. Try this:
CREATE OR REPLACE VIEW `Stats` AS
select sum(case when `p`.`vote` = 'notcast' then 1 else 0 end) AS `numNotCast`
from `Debate` `d` join
`Participant` `p`
ON `d`.`debateId` = `p`.`debateId`
group by `d`.`debateId`;
By the way, in MySQL, you can simplify the select to:
select sum(p.vote = 'notcast') as numNotCast