Join with aggregate function on empty resultset - mysql

I have a requirement to get sum, avg, count values as 0 following institution_name when lr.proccessed_on date is not matched with the input date. how can I achieve that in the following query
SELECT bd.id as institutionId,
bd.institution_name as institution,
count(lr.id) as numberOfTransaction,
sum(lr.amount) as totalTransactionAmount,
avg(lr.amount) as averageTransactionAmount,
sum(lr.processing_fee) as totalProcessingFee
from daily_transaction lr
join
(
select s.id
from status s
where s.name not in (:statusName)) s on
lr.customer_status = s.id
join account sa on
lr.account = sa.id
join branch b on
sa.branch = b.id
right join institution_details bd on
b.institution = bd.id
join status s2 on
bd.status = s2.id
where (:institutionIdParam is null
or bd.id = :institutionIdParam)
and (:instutionStatus is null
or s2.description = :instutionStatus)
and (:fromDate is null
and :toDate is null
or cast(lr.processed_on as date) <= :toDate
and cast(lr.processed_on as date) >= :fromDate)
group by bd.id , bd.institution_name
order by bd.institution_name asc;

The purpose of using RIGHT JOIN to institution_details seems to be that you want all institutions listed irrespective of their daily transactions. For those institutions where there are no daily transactions processed_on is treated as having NULL value (that is how the right join would behave), and a WHERE condition like yours would return false, and the institution_details are then filtered out.
You can overcome this by moving your parameter checking clause to the join condition, like so:
right join
institution_details bd on
b.institution = bd.id
and
((:fromDate is null and :toDate is null)
or
(cast(lr.processed_on as date) <= :toDate and cast(lr.processed_on as date) >= :fromDate))
and removing it from the WHERE clause.
Note the brackets I added to your expression; in the presence of both OR and AND operators we need to consider the operator precedence and order of evaluation, it would be incorrect (in my view) not to have these brackets. Even with these, you need to prevent a situation where only one (but not both) :fromDate and :toDate parameters is supplied.

Related

Group By Error In MySQL with sql_mode=only_full_group_by

Tring the following query but i get; Error Code: 1055. Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'PDC.PLG.LogDateTime' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
I know that this is because of only full group by mode; how can I refactor this sort of query?
SELECT
SUM(PLG.Qty) AS TotQty,
SUM(PLG.ScrapQty) AS ScrpQty,
(
SELECT SUM(PLL.Qty)
FROM ProductionLog AS PLL
INNER JOIN ProductionPlan PPP ON PPP.PlanId = PLL.PlanId
WHERE
DATE(LogDateTime) = DATE(PLG.LogDateTime) AND
LogType = 8 AND
PPP.StationId = PP.StationId
) AS RwrkQty,
DATE(PLG.LogDateTime) AS LogDate,
S.StationName
FROM ProductionLog AS PLG
INNER JOIN ProductionPlan AS PP ON PLG.PlanId = PP.PlanId
INNER JOIN Station AS S ON S.StationId = PP.StationId
WHERE PLG.Logtype IN (4)
GROUP BY S.StationId,DATE(PLG.LogDateTime)
First, a proper query using a GROUP By means whatever the result fields you are trying to return, your GROUP by should include ALL fields that do not have any aggregation applied (min, max, sum, avg, etc.) So what is missing is that an extra column is in the list that is not aggregated, but also not part of the group by. So either add this non-aggregate field to the group by (even if the last field in group by), OR apply some aggregation to it.
Now, a cleanup for readability of your original query for readability of what is where and/or subquery of the next.
SELECT
SUM(PLG.Qty) AS TotQty,
SUM(PLG.ScrapQty) AS ScrpQty,
( SELECT SUM(PLL.Qty)
FROM ProductionLog AS PLL
INNER JOIN ProductionPlan PPP
ON PPP.PlanId = PLL.PlanId
WHERE
DATE(LogDateTime) = DATE(PLG.LogDateTime)
AND LogType = 8
AND PPP.StationId = PP.StationId ) AS RwrkQty,
DATE(PLG.LogDateTime) AS LogDate,
S.StationName
FROM
ProductionLog AS PLG
INNER JOIN ProductionPlan AS PP
ON PLG.PlanId = PP.PlanId
INNER JOIN Station AS S
ON S.StationId = PP.StationId
WHERE
PLG.Logtype IN (4)
GROUP BY
S.StationId,
DATE(PLG.LogDateTime)
In your scenario, your 3rd column which is based on a query has already been aggregated INSIDE it, but for respect to the OUTER query, it is NOT aggregated. To simplify this, just wrap IT in a MIN() such as
MIN( ( SELECT SUM(PLL.Qty)
FROM ProductionLog AS PLL
INNER JOIN ProductionPlan PPP
ON PPP.PlanId = PLL.PlanId
WHERE
DATE(LogDateTime) = DATE(PLG.LogDateTime)
AND LogType = 8
AND PPP.StationId = PP.StationId ) ) AS RwrkQty,
Since the inner query is only ever returning 1 row, summing 1 row will return the same value anyhow.

MySQL left join missing results

I'm trying to see if I have some unused SIP numbers, so I have a list of numbers in the table tmp_numbers
0203XXXXX00
0203XXXXX01
0203XXXXX02
0203XXXXX03
...
The query I use is:
SELECT
n.number,
COUNT(c.did) AS count
FROM tmp_numbers n
LEFT JOIN cdr c
ON n.number = c.did
WHERE c.calldate >= '2017-01-01'
GROUP BY n.number
ORDER BY n.number
I get results from the above query, but it omits numbers it can't match on c.did.
I was under the impression that a LEFT JOIN would match/display everything on left table (tmp_numbers), regardless if there is a value on the right side (so it will show NULL?)
What am I missing here?
Put the where condition in the left join. Otherwise it turns implicitly into an inner join because conditions in the where clause filter on all data and if calldate is NULL then the condition is FALSE
LEFT JOIN cdr c ON n.number = c.did
AND c.calldate >= '2017-01-01'
You are querying on a column from the JOINED table which maybe null if there is no corresponding entry, so change the WHERE condition from:
WHERE c.calldate >= '2017-01-01'
to
WHERE c.calldate >= '2017-01-01' OR c.did IS NULL

Query gets very slows if i add a where

SELECT a.emp_id,s.name, s.department,s.register, z.Compoff_Count as Extra, ifnull(COUNT(DISTINCT TO_DAYS(a.punchdate)),0) as Monthly_Count
FROM machinedata a left join
(SELECT a.emp_id, ifnull(COUNT(DISTINCT TO_DAYS(a.punchdate)),0) as Compoff_Count
FROM machinedata a
RIght JOIN time_dimension c on c.db_date = a.punchdate
where ( year(c.db_date) = 2016 and month(c.db_date) = 8 and (c.holiday_flag = 't' or c.weekend_flag ='t' ))
GROUP BY a.emp_id) Z
on z.emp_id = a.emp_id
RIght JOIN time_dimension c on c.db_date = a.punchdate
left join emp s on s.emp_id = a.emp_id
where (year(c.db_date) = 2016 and month(c.db_date) = 8 and c.holiday_flag = 'f' and c.weekend_flag ='f' )
GROUP BY emp_id
The above query works fine.. but if i add s.department='yes' in the last where the query takes more than 40 seconds.
What shall i do to improve the query performance ?
Your initial query can be simplified I believe by using "conditional aggregates" which places case expressions inside the count() function. This avoids repeated sans of data and unnecessary joins to derived tables.
You should also avoid using functions on data to suit where clause conditions i.e. Instead of YEAR() and MONTH() simply use date boundaries. This allows an index on the date column to be used in the query execution.
I'm not sure if you really need to use TO_DAYS() but I suspect it isn't needed either.
SELECT
a.emp_id
, s.name
, s.department
, s.register
, COUNT(DISTINCT CASE WHEN (c.holiday_flag = 't' OR
c.weekend_flag = 't') THEN c.db_date END) AS Compoff_Count
, COUNT(DISTINCT CASE WHEN NOT (c.holiday_flag = 't' OR
c.weekend_flag = 't') THEN a.punchdate END) AS Monthly_Count
FROM time_dimension c
LEFT JOIN machinedata a ON c.db_date = a.punchdate
LEFT JOIN emp s ON a.emp_id = s.emp_id
WHERE c.db_date >= '2016-08-01'
AND c.db_date < '2016-09-01'
GROUP BY
a.emp_id
, s.name
, s.department
, s.register
If this re-write produces correct results then you could try adding and s.department='yes' into the where clause to assess the impact. If it is still substantially slower then get an explain plan and add it to the question. The most likley cause of slowness is lack of an index but without an explain plan it's not possible to be certain.
Please note that this suggestion is just that; and is prepared without sample data and expected results.

MySQL Help: Return invoices and payments by date

I am having trouble getting a MySQL query to work for me. Here is the setup.
A customer has asked me to compile a report from some accounting data. He wants to select a date (and possibly other criteria) and have it return all of the following (an OR statement):
1.) All invoices that were inserted on or after that date
2.) All invoices regardless of their insert date that have corresponding payments in a separate table whose insert dates are on or after the selected date.
The first clause is basic, but I am having trouble pairing it with the second.
I have assembled a comparable set of test data in an SQL Fiddle. The query that I currently have is provided.
http://www.sqlfiddle.com/#!2/d8d9c/3/2
As noted in the comments of the fiddle, I am working with July 1, 2013 as my selected date. For the test to work, I need invoices 1 through 5 to appear, but not invoice #6.
Try this: http://www.sqlfiddle.com/#!2/d8d9c/9
Here are the summarized changes
I got rid of your GROUP BY. You did not have any aggregate functions. I used DISTINCT instead to eliminate duplicate records
I removed your implicit joins and put explicit joins in their place for readability. Then I changed them to LEFT JOINs. I am not sure what your data looks like but at a minimum, I would assume you need the payments LEFT JOINed if you want to select an invoice that has no payments.
This will probably get you the records you want, but those subselects in the SELECT clause may perform better as LEFT JOINs then using the SUM function
Here is the query
SELECT DISTINCT
a.abbr landowner,
CONCAT(f.ForestLabel, '-', l.serial, '-', l.revision) leasenumber,
i.iid,
FROM_UNIXTIME(i.dateadded,'%M %d, %Y') InvoiceDate,
(SELECT IFNULL(SUM(ch.amount), 0.00) n FROM test_charges ch WHERE ch.invoiceid = i.iid) totalBilled,
(SELECT SUM(p1.amount) n FROM test_payments p1 WHERE p1.invoiceid = i.iid AND p1.transtype = 'check' AND p1.status = 2) checks,
(SELECT SUM(p1.amount) n FROM test_payments p1 WHERE p1.invoiceid = i.iid AND p1.transtype = 'ach' AND p1.status = 2) ach,
CASE WHEN i.totalbilled < 0 THEN i.totalbilled * -1 ELSE 0.00 END credits,
CASE WHEN i.balance >= 0 THEN i.balance ELSE 0.00 END balance,
t.typelabel, g.groupname
FROM test_invoices i
LEFT JOIN test_contracts c
ON i.contractid = c.cid
LEFT JOIN test_leases l
ON c.leaseid = l.bid
LEFT JOIN test_forest f
ON l.forest = f.ForestID
LEFT JOIN test_leasetypes t
ON l.leasetype = t.tid
LEFT JOIN test_accounts a
ON l.account = a.aid
LEFT JOIN test_groups g
ON c.groupid = g.gid
LEFT JOIN test_payments p
ON p.invoiceid = i.iid
WHERE (i.dateadded >= #startdate) OR (p.dateadded >= #startdate)
Try this.
http://www.sqlfiddle.com/#!2/d8d9c/11/2
TL;DR:
… AND (i.dateadded > #startdate
OR EXISTS (
SELECT * FROM test_payments
WHERE test_payments.invoiceid = i.iid
AND test_payments.dateadded >= #startdate))

Mysql set label to Rollup or a similar GROUPING function like sql server

I have a problem with the ROLLUP, I have rows with null values, and the ROLLUP also returns null, how do I difference between the null values of the ROLLUP and the null values of the row?
The null in the rows exist because the column (group_name) is associated with a left join.
Here is my query:
SELECT gr.info,
HOUR(cdr.calldate) AS hour,
DATE(cdr.calldate) AS date,
COUNT(DISTINCT cdr.uniqueid) AS count,
pl.number,
IFNULL(ugr.group_name, "") AS group_name
FROM cdr
INNER JOIN callinfo AS ci
ON ci.uniqueid = cdr.uniqueid
LEFT JOIN users AS usr
ON usr.username = ci.agent
LEFT JOIN groups AS ugr
ON ugr.group_id = usr.group_id
INNER JOIN pstnline AS pl
ON ci.line = pl.number
INNER JOIN hunt_line AS gri
ON gri.pstnline_id = pl.pstnline_id
INNER JOIN hunt AS gr
ON gri.hunt_number = gr.number
WHERE cdr.calldate >='2012-12-01 00:00'
AND cdr.calldate <='2013-01-24 10:45'
GROUP BY group_name WITH ROLLUP
I see that in SQL Server exist a function called GROUPING, but in MySql doesn't exist, how can i achieve the same effect?
I think you can also do this in the query that you have, by changing the group by argument to:
group by ifnull(ugr.group_name, '')
Now, blanks will indicate NULLs from the outer join and NULLs will indicate rollup.