I have a complicated query. I have a bunch of users. I have a ledger of debits and credits. I want to filter a user's subscribers by how long the subscribers have been negative. To do that I find negative subscribers for a given user, get each subscriber's transactions in reverse order, and work backwards until the subscriber's balance is ≥ 0 to find how long ago that was.
The query works only if I take the list of subscriber IDs the query returns and add them into the WHERE
clause.
The Query
-- Return just the info we want
SELECT subscriber_id,
balance,
days_negative,
DATE_SUB(CURRENT_DATE, INTERVAL days_negative DAY) AS days_since
FROM (
-- Reset @balanced for each subscriber
SELECT r.subscriber_id,
balance,
(@balanced := FALSE),
(
-- get date of last transaction before balance >= 0
SELECT DATEDIFF(CURRENT_DATE, MIN(transaction_time))
FROM (
-- work backwards until balance >= 0
SELECT *,
ROUND(balance, 2),
ROUND(balance - running_total, 2),
(@balanced := @balanced OR balance - running_total + amount >= 0) AS balance_reached
FROM (
-- get transactions with running total
SELECT t.transaction_id,
t.transaction_time,
t.subscriber_id,
t.amount,
t.category,
SUM(t.amount) OVER w AS running_total
FROM transactions t
WHERE t.subscriber_id = r.subscriber_id
AND `t`.`void` = 0
WINDOW w AS (ORDER BY transaction_time DESC, transaction_id DESC)
) with_running_total
) with_balance_reached
WHERE balance_reached = 0
) AS days_negative
FROM subscribers r
INNER JOIN user_subscriber_linker usl USING (subscriber_id)
INNER JOIN subscriber_balance b USING (subscriber_id)
WHERE usl.user_id = 12345
AND r.subscriber_id IN (987, 654, 321)
AND r.void = 0
AND b.balance < 0
GROUP BY subscriber_id
) with_each;
This query gives me a result like this:
renter_id | balance | days_negative | negative_since |
---|---|---|---|
987 | -5998.90 | 98 | 2024-08-01 |
654 | -2000.00 | 62 | 2024-09-06 |
321 | -3774.20 | 79 | 2024-08-20 |
When I remove the line AND r.subscriber_id IN (987, 654, 321)
, I get this:
renter_id | balance | days_negative | negative_since |
---|---|---|---|
987 | -5998.90 | 98 | 2024-08-01 |
654 | -2000.00 | null |
null |
321 | -3774.20 | null |
null |
The fact that the first row is correct leads me to believe @balanced
isn't being set for each row. I tried using joins (JOIN (SELECT @balanced := FALSE)
) to set the var but that hasn't worked either.
How do I get the query to work?