I have a problem with an SQL statement not returning any result from a specific result.
SELECT statementbalance AS 'BringForwardFromPreviousDay',
IFNULL((SELECT SUM(statementdebit) FROM statement WHERE merchantid = '4' AND statementdate = '2018-01-08'),0) AS 'TotalDebit',
IFNULL((SELECT SUM(statementcredit) FROM statement WHERE merchantid = '4' AND statementdate = '2018-01-08'),0) AS 'TotalCredit',
IFNULL((SELECT statementbalance FROM statement WHERE merchantid = '4' AND statementdate <= '2018-01-08' ORDER BY transactionid DESC LIMIT 1),0) AS 'TotalBalance'
FROM statement
WHERE merchantid = '4' and statementdate <= '2018-01-07'
ORDER BY transactionid DESC LIMIT 1
The SQL statement is to capture data dated from today '2018-01-08'and from previous days '2018-01-07' and grab the last recorded data hence the '<='
Because merchantid = '4' is a newly added merchant, it does not have any data to grab from <= 2018-01-07 hence i want it to return 0 instead of null and prevent other data from returning null.
I tried adding ifnull on the statementnbalance but it still returns null and i can only think of including ifnull on the where clause but i tried to no avail.
Here is the sqlfiddle of using merchantid '2' that works fine.
http://sqlfiddle.com/#!9/7cae3e0/1
I think this is what you want:
select
x.merchantid as merchantid,
ifnull(c.previous_days_balance, 0) as BringForwardFormPreviousDay,
ifnull(a.latest_total_debit, 0) as TotalDebit,
ifnull(a.latest_total_credit, 0) as TotalCredit,
ifnull(b.latest_balance, 0) as LatestBalance
from
(
select distinct merchantid from statement
) x
left outer join
(
select
merchantid,
sum(statementdebit) as latest_total_debit,
sum(statementcredit) as latest_total_credit
from
statement
where
statementdate = '2018-01-08'
group by
merchantid
) a
on x.merchantid = a.merchantid
left outer join
(
select
merchantid,
statementbalance as latest_balance
from
statement
where
(merchantid, transactionid) in
(
select
merchantid,
max(transactionid)
from
statement
where
statementdate = '2018-01-08'
group by
merchantid
)
) b
on x.merchantid = b.merchantid
left outer join
(
select
merchantid,
statementbalance as previous_days_balance
from
statement
where
(merchantid, transactionid) in
(
select
merchantid,
max(transactionid)
from
statement
where
statementdate <= '2018-01-07'
group by
merchantid
)
) c
on x.merchantid = c.merchantid;
I added another row to illustrate the extra case:
INSERT INTO statement VALUES ('99', '5', '131', 'Purchase: TopUp Cheezy', '2018-01-05', '23:35:31', '38.20', '0.00', '5000.00');
The results are:
+------------+-----------------------------+------------+-------------+----------------+
| merchantid | BringForwardFormPreviousDay | TotalDebit | TotalCredit | LatestBalance |
+------------+-----------------------------+------------+-------------+----------------+
| 1 | 35 | 15 | 0 | 5 |
| 2 | 182.33 | 4.9 | 0 | 177.43 |
| 4 | 0 | 95.48 | 200 | 104.52 |
| 5 | 5000 | 0 | 0 | 0 |
+------------+-----------------------------+------------+-------------+----------------+
4 rows in set (0.00 sec)
This assumes that transactionId keeps on increasing with time. That is not an entirely safe assumption. It would be better to use timestamps rather than dates for the transaction so you can find the latest one (or the latest one that is before today). I see that you do have statementtime but a separate column...
The query doesn't return anything because there are no data to return that fit your where clause. What you could do is using an IFNULL on your whole select statement:
SELECT ifnull((SELECT statementbalance AS 'BringForwardFromPreviousDay'
FROM statement
WHERE merchantid = '4'
AND statementdate <= '2018-01-07'
ORDER BY transactionid DESC LIMIT 1), 0)
and work from there. However, your statement can only contain one column, so you either have to work this into a stored procedure, or you have to deal with this issue at a different place (maybe in your code?)
You need to use a LEFT JOIN to allow for rows that don't exist.
SELECT IFNULL(t3.statementbalance, 0) AS BringForwardFromPreviousDay,
t1.TotalDebit, t1.TotalCredit, t2.TotalBalance
FROM (SELECT SUM(statementdebit) AS TotalDebit,
SUM(statementcredit) AS TotalCredit
FROM statement
WHERE merchantId = '4' AND statementdate = '2018-01-08') AS t1
CROSS JOIN (
SELECT statementbalance AS TotalBalance
FROM statement
WHERE merchantId = '4' AND statementdate <= '2018-01-08'
ORDER BY statementdate DESC
LIMIT 1) AS t2
LEFT JOIN (
SELECT statementbalance
FROM statement
WHERE t3.merchantID = '4' AND t3.statementdate <= '2018-01-07'
ORDER BY statementdate DESC
LIMIT 1) ON 1=1
Related
I would like to get the count of a column based on its recent status.
Please see table structure below:
id | visible | date
1 | 1 | 2021-07-22
2 | 1 | 2021-07-23
3 | 0 | 2021-07-24
4 | 1 | 2021-07-25
5 | 0 | 2021-07-26
6 | 0 | 2021-07-27
For example, if I query
SELECT COUNT(visible) AS latest_not_visible WHERE date = '2021-07-26'
then it should return
latest_not_visible
1
Since it only counts that date as not visible, it disregarded the count on 07/24 since 07/25 is visible
But if I query
SELECT COUNT(visible) AS latest_not_visible WHERE date = '2021-07-27'
latest_not_visible
2
since 07/26 and 07/27 are both non-visible and no date in between is visible
I already had the solution to the problem, but I would need help in optimizing this function:
IIF(datediff
(day,
(SELECT MAX(date) FROM t1 WHERE (visible = 0 OR visible = '-1' OR visible = '-3') AND item_id = vp.item_id AND [date] <= vp.date),
(SELECT MAX(date) FROM t1 WHERE visible = 1 AND item_id = vp.item_id AND [date] <= vp.date)) IS NULL
OR datediff(day,
(SELECT MAX(date) FROM t1 WHERE (visible = 0 OR visible = '-1' OR visible = '-3') AND item_id = vp.item_id AND [date] <= vp.date),
(SELECT MAX(date) FROM t1 WHERE visible = 1 AND item_id = vp.item_id AND [date] <= vp.date)) < 0,
(SELECT COUNT(1) FROM t1 WHERE (visible = 0 OR visible = '-1' OR visible = '-3') AND item_id = vp.item_id AND [date] <= vp.date), 0)
AS times_not_visible,
table vp is the original table same with t1
Rather than counting them, you're better to calculate the number of days between the last visible and not visible on or before that date. So something like this...
SELECT DATEDIFF(
SELECT MAX(date) FROM YourTable WHERE visible = 0 AND date <= '2021-07-27'),
SELECT MAX(date) FROM YourTable WHERE visible = 1 AND date <= '2021-07-27')
) as latest_not_visible;
Find the latest visible date earlier than the given date and count all rows in between those two dates:
SET #dt = '2021-07-25';
SELECT COUNT(*)
FROM t
WHERE date <= (SELECT date FROM t WHERE date = #dt AND visible = 0)
AND date > (SELECT date FROM t WHERE date < #dt AND visible = 1 ORDER BY date DESC LIMIT 1)
SQL Fiddle
A solution with GROUP_CONCAT():
SELECT CHAR_LENGTH(visible) - CHAR_LENGTH(TRIM(TRAILING '0' FROM visible)) latest_not_visible
FROM (
SELECT GROUP_CONCAT(visible ORDER BY date SEPARATOR '') visible
FROM tablename
WHERE date <= ?
) t
Change ? to the date you want.
See the demo.
You can use window functions:
select count(*)
from (select t.*,
max(case when visible = 1 then date end) over (order by date) as max_visible_date
from t
where date <= '2021-07-26' -- or whatever
) t
where visible = 0 and
(date > max_visible_date or max_visible_date is null);
Note that this version only mentions the date once. It also works for if there are no rows with visible = 1, and it works on any number of rows.
I have the following table :
--------------------------------------------------------------------------------
| id | pack_id | user_id | start_date | end_date | runtime |
--------------------------------------------------------------------------------
| 1 | 52 | 9 | 2019-04-09 11:53:00 | 2019-04-09 11:54:00 | 60 |
| 2 | 52 | 9 | 2019-04-09 11:58:00 | NULL | NULL |
I would like to update the end_date and runtime of the last id of the same pack_id and user_id.
end_date have to take the current datetime.
Here is my query :
UPDATE
myTable
SET
end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())
WHERE
id = (
SELECT
max(id)
FROM
myTable
WHERE
user_id = '9'
AND
pack_id = '52'
)
And I get the following error :
ERROR 1093 (HY000) at line 1: You can't specify target table
'myTable' for update in FROM clause
You can't do this in MySQL, so use a JOIN instead:
UPDATE myTable t JOIN
(SELECT max(id) as max_id
FROM myTable
WHERE user_id = '9' AND pack_id = '52'
) tt
ON t.id = tt.max_id
SET t.end_date = NOW(),
t.runtime = TIMESTAMPDIFF(SECOND, t.start_date, NOW()) ;
use subquery deeper into a from clause
UPDATE
myTable
SET
end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())
WHERE
id = (
select id from (
SELECT
max(id) as id
FROM
myTable
WHERE
user_id = '9'
AND
pack_id = '52'
) a
)
You need a subquery for create a temp set of value and avoid the use of the same table for updated and select at same level.
You can do thi using a nested select eg:
UPDATE myTable
SET end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())
WHERE id = (
select max_id from (
SELECT
max(id) max_id
FROM myTable
WHERE user_id = '9'
AND pack_id = '52'
) t )
or an inner join on the subquery
UPDATE myTable m
INNER JOIN (
SELECT
max(id) max_id
FROM myTable
WHERE user_id = '9'
AND pack_id = '52'
) t ON t.max_id = m.id
SET end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())
How to do running total or cumulative sum from this query? Is it possible to run by correlated subquery? The cumulative sum results will be show as 'New value'.
SELECT
sum(data2.quantity/1000) AS UnitMT
FROM
data2
INNER JOIN itmnocate ON data2.item = itmnocate.ItemNumber
and Source in ('imported','local','by product')
WHERE date1 >= DATE_FORMAT('2018-04-12', '%Y-01-01')- INTERVAL 1 YEAR
AND date1 <= DATE_FORMAT('2018-04-12', '%Y-12-31') - INTERVAL 1 YEAR
AND data2.unit = 'KG'
and data2.customeracc not in (select Customeraccount from custlist WHERE Custcat = 'bcsb')
GROUP BY month(date1)
This is a cumulative problem If your mysql version support window function you can use SUM with window function to do cumulative.
The DATE1 column can be used as the basis for order by to do cumulative.
SELECT *,
sum(UnitMT) over (order by month(date1)) 'New value'
FROM T t1
sqlfiddle
If your mysql version didn't support window function, you can try to use subquery in select to do cumulative.
CREATE TABLE T( date1 DATE,UnitMT int);
INSERT INTO T VALUES ('2017-01-01',66535);
INSERT INTO T VALUES ('2017-02-01',108337);
INSERT INTO T VALUES ('2017-03-01',132767);
INSERT INTO T VALUES ('2017-04-01',100687);
INSERT INTO T VALUES ('2017-05-01',125151);
Query 1:
SELECT *,
(SELECT SUM(UnitMT) FROM T tt WHERE month(tt.date1) <= month(t1.date1)) 'New value'
FROM T t1
Results:
| date1 | UnitMT | New value |
|------------|--------|-----------|
| 2017-01-01 | 66535 | 66535 |
| 2017-02-01 | 108337 | 174872 |
| 2017-03-01 | 132767 | 307639 |
| 2017-04-01 | 100687 | 408326 |
| 2017-05-01 | 125151 | 533477 |
Note
T symbol your current result set data.
You could try this query, it's efficient and works an all MySQL versions:
select #cumSum := 0;
select UnitMT, #cumSum := #cumSum + UnitMT
from tbl
order by date1;
Demo
For your specific problem, you can use variables and a subquery:
SELECT mon, UnitMT,
(#sum := #sum + UnitMT) as running_sum
FROM (SELECT month(date1) as mon, sum(data2.quantity/1000) AS UnitMT
FROM data2 INNER JOIN
itmnocate
ON data2.item = itmnocate.ItemNumber AND
Source IN ('imported', 'local', 'by product')
WHERE date1 >= DATE_FORMAT('2018-04-12', '%Y-01-01') - INTERVAL 1 YEAR AND
date1 <= DATE_FORMAT('2018-04-12', '%Y-12-31') - INTERVAL 1 YEAR AND
data2.unit = 'KG' AND
data2.customeracc not in (select Customeraccount from custlist WHERE Custcat = 'bcsb')
GROUP BY month(date1)
ORDER BY month(date1)
) m CROSS JOIN
(SELECT #sum := 0) params;
I have a table that has records for multiple session events. Each row is an event in a session, and a session can have multiples of the same event. Those are basically game sessions and each event is round start or round end. My data looks something like
Session_id | Event_type | Event_Time
1 | round_start | 12:01:00
1 | round_end | 12:02:00
1 | round_start| 12:05:00
1 | round_end | 12:7:00
2 | round_start | 14:11:00
2 | round_end | 14:12:00
3 | round_start| 15:09:00
3 | round_end | 15:13:00
I am trying to find the average round duration. I tried the following SQL
select
RS.session_id,
RS.Event_Time as StartTime,
RE.EndTime,
TIMESTAMPDIFF(MINUTE,RE.EndTime,RS.Event_Time) as duration
from amp_event_mamlaka as RS
left join
(
select session_id, min(event_time) as EndTimd from amp_event_mamlaka
where Event_Type = "Round End" and session_id = RS.session_id and event_time>RS.Event_Time
) RE
on RE.session_id = RS.session_id
The issue is that I can't reference RS.session_id and RS.event_time in the joined table.
I am using MySQL. Any suggestions on how to accomplish this?
Thanks
I would suggest that you approach this with a correlated subquery:
select RS.session_id, RS.Event_Time as StartTime,
(select smin(event_time)
from amp_event_mamlaka em
where em.session_id = RS.session_id and
em.Event_Type = 'Round End' and
em.event_time > RS.Event_Time
) as EndTime,
from amp_event_mamlaka RS;
You can do the timestamp difference using a subquery:
select RS.*, TIMESTAMPDIFF(MINUTE, EndTime, Event_Time) as duration
from (select RS.session_id, RS.Event_Time as StartTime,
(select min(event_time)
from amp_event_mamlaka em
where em.session_id = RS.session_id and
em.Event_Type = 'Round End' and
em.event_time > RS.Event_Time
) as EndTime
from amp_event_mamlaka RS
) RS
A subquery, as opposed to a nested query, should only return one value. Your requirement is an example where you want data from pairs of rows. The subquery is only used to connect the pair, not supply data. Fiddle
select e1.SessionID, e1.EventType, e1.EventTime, e2.EventType, e2.EventTime, TimeStampDiff( minute, e1.EventTime, e2.EventTime ) Duration
from Events e1
join Events e2
on e2.SessionID = e1.SessionID
and e2.EventType = 'end'
and e2.EventTime =(
select Min( EventTime )
from Events
where SessionID = e1.SessionID
and EventType = 'end'
and EventTime > e1.EventTime )
where e1.EventType = 'start';
Instead of keeping it in where clause of subquery you can keep the condition in Join On clause. Try this.
SELECT RS.session_id,
RS.Event_Time AS StartTime,
RE.EndTime,
Timestampdiff(MINUTE, RE.EndTime, RS.Event_Time) AS duration
FROM amp_event_mamlaka AS RS
LEFT JOIN (SELECT session_id,
Min(event_time) AS EndTimd
FROM amp_event_mamlaka
WHERE Event_Type = "Round End") RE
ON RE.session_id = RS.session_id
AND RE.event_time > RS.Event_Time
I have a MySQL table of the following form
account_id | call_date
1 2013-06-07
1 2013-06-09
1 2013-06-21
2 2012-05-01
2 2012-05-02
2 2012-05-06
I want to write a MySQL query that will get the maximum difference (in days) between successive dates in call_date for each account_id. So for the above example, the result of this query would be
account_id | max_diff
1 12
2 4
I'm not sure how to do this. Is this even possible to do in a MySQL query?
I can do datediff(max(call_date),min(call_date)) but this would ignore dates in between the first and last call dates. I need some way of getting the datediff() between each successive call_date for each account_id, then finding the maximum of those.
I'm sure fp's answer will be faster, but just for fun...
SELECT account_id
, MAX(diff) max_diff
FROM
( SELECT x.account_id
, DATEDIFF(MIN(y.call_date),x.call_date) diff
FROM my_table x
JOIN my_table y
ON y.account_id = x.account_id
AND y.call_date > x.call_date
GROUP
BY x.account_id
, x.call_date
) z
GROUP
BY account_id;
CREATE TABLE t
(`account_id` int, `call_date` date)
;
INSERT INTO t
(`account_id`, `call_date`)
VALUES
(1, '2013-06-07'),
(1, '2013-06-09'),
(1, '2013-06-21'),
(2, '2012-05-01'),
(2, '2012-05-02'),
(2, '2012-05-06')
;
select account_id, max(diff) from (
select
account_id,
timestampdiff(day, coalesce(#prev, call_date), call_date) diff,
#prev := call_date
from
t
, (select #prev:=null) v
order by account_id, call_date
) sq
group by account_id
| ACCOUNT_ID | MAX(DIFF) |
|------------|-----------|
| 1 | 12 |
| 2 | 4 |
see it working live in an sqlfiddle
If you have an index on account_id, call_date, then you can do this rather efficiently without variables:
select account_id, max(call_date - prev_call_date) as diff
from (select t.*,
(select t2.call_date
from table t2
where t2.account_id = t.account_id and t2.call_date < t.call_date
order by t2.call_date desc
limit 1
) as prev_call_date
from table t
) t
group by account_id;
Just for educational purposes, doing it with JOIN:
SELECT t1.account_id,
MAX(DATEDIFF(t2.call_date, t1.call_date)) AS max_diff
FROM t t1
LEFT JOIN t t2
ON t2.account_id = t1.account_id
AND t2.call_date > t1.call_date
LEFT JOIN t t3
ON t3.account_id = t1.account_id
AND t3.call_date > t1.call_date
AND t3.call_date < t2.call_date
WHERE t3.account_id IS NULL
GROUP BY t1.account_id
Since you didn't specify, this shows max_diff of NULL for accounts with only 1 call.
SELECT a1.account_id , max(a1.call_date - a2.call_date)
FROM account a2, account a1
WHERE a1.account_id = a2.account_id
AND a1.call_date > a2.call_date
AND NOT EXISTS
(SELECT 1 FROM account a3 WHERE a1.call_date > a3.call_date AND a2.call_date < a3.call_date)
GROUP BY a1.account_id
Which gives :
ACCOUNT_ID MAX(A1.CALL_DATE - A2.CALL_DATE)
1 12
2 4