Compute outstanding amounts in MySQL - mysql

I am having an issue with a SELECT command in MySQL. I have a database of securities exchanged daily with maturity from 1 to 1000 days (>1 mio rows). I would like to get the outstanding amount per day (and possibly per category). To give an example, suppose this is my initial dataset:
DATE VALUE MATURITY
1 10 3
1 15 2
2 10 1
3 5 1
I would like to get the following output
DATE OUTSTANDING_AMOUNT
1 25
2 35
3 15
Outstanding amount is calculated as the total of securities exchanged still 'alive'. That means, in day 2 there is a new exchange for 10 and two old exchanges (10 and 15) still outstanding as their maturity is longer than one day, for a total outstanding amount of 35 on day 2. In day 3 instead there is a new exchange for 5 and an old exchange from day 1 of 10. That is, 15 of outstanding amount.
Here's a more visual explanation:
Monday Tuesday Wednesday
10 10 10 (Day 1, Value 10, matures in 3 days)
15 15 (Day 1, 15, 2 days)
10 (Day 2, 10, 1 day)
5 (Day 3, 5, 3 days with remainder not shown)
-------------------------------------
25 35 15 (Outstanding amount on each day)
Is there a simple way to get this result?

First of all in the main subquery we find SUM of all Values for current date. Then add to them values from previous dates according their MATURITY (the second subquery).
SQLFiddle demo
select T1.Date,T1.SumValue+
IFNULL((select SUM(VALUE)
from T
where
T1.Date between
T.Date+1 and T.Date+Maturity-1 )
,0)
FROM
(
select Date,
sum(Value) as SumValue
from T
group by Date
) T1
order by DATE

I'm not sure if this is what you are looking for, perhaps if you give more detail
select
DATE
,sum(VALUE) as OUTSTANDING_AMOUNT
from
NameOfYourTable
group by
DATE
Order by
DATE
I hope this helps

Each date considers each row for inclusion in the summation of value
SELECT d.DATE, SUM(m.VALUE) AS OUTSTANDING_AMOUNT
FROM yourTable AS d JOIN yourtable AS m ON d.DATE >= m.MATURITY
GROUP BY d.DATE
ORDER BY d.DATE

A possible solution with a tally (numbers) table
SELECT date, SUM(value) outstanding_amount
FROM
(
SELECT date + maturity - n.n date, value, maturity
FROM table1 t JOIN
(
SELECT 1 n UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5
) n ON n.n <= maturity
) q
GROUP BY date
Output:
| DATE | OUTSTANDING_AMOUNT |
-----------------------------
| 1 | 25 |
| 2 | 35 |
| 3 | 15 |
Here is SQLFiddle demo

Related

How to make time buckets with a start and end time column?

I have 3 columns, employee_id, start_time and end_time I want to make bucks of 1 hour to show me how many employees were working in each hour. For example, employee A worked from 12 pm to 3 pm and employee B worked from 2 pm to 4 pm so, at 12 pm (1 employee was working) 1 pm (1 employee) 2 pm (2 employees were working) 3 pm (2 employees) and 4 pm (1 employee), how can I make this in SQL? Let me show you a picture of the start and end time columns.
Sample input would be:
Expected outcome would be something like
I want to create a bucket in order to know how many people were working in each hour of the day.
SELECT
Employee_id,
TIME(shift_start_at,timezone) AS shift_start,
TIME(shift_end_at,timezone) AS shift_end,
FROM
`employee_shifts` AS shifts
WHERE
DATE(shifts.shift_start_at_local) >= "2022-05-01"
GROUP BY
1,
2,
3
Assuming you are on mysql version 8 or above generate all the buckets , left join to shifts to infill times in start-endtime ranges , filter out those that are not applicable then count eg:-
DROP TABLE IF EXISTS t;
create table t (id int, startts datetime, endts datetime);
insert into t values
(1,'2022-06-19 08:30:00','2022-06-19 10:00:00'),
(2,'2022-06-19 08:30:00','2022-06-19 08:45:00'),
(3,'2022-06-19 07:00:00','2022-06-19 07:59:00');
with cte as
(select 7 as bucket union select 8 union select 9 union select 10 union select 11),
cte1 as
(select bucket,t.*,
floor(hour(startts)) starthour, floor(hour(endts)) endhour
from cte
left join t on cte.bucket between floor(hour(startts)) and floor(hour(endts))
)
select bucket,count(id) nof from cte1 group by bucket
;
+--------+-----+
| bucket | nof |
+--------+-----+
| 7 | 1 |
| 8 | 2 |
| 9 | 1 |
| 10 | 1 |
| 11 | 0 |
+--------+-----+
5 rows in set (0.001 sec)
If you have a limited number of time bucket maybe you can use it this way
WITH CTE AS
(SELECT
COUNTRY,
MONTH,
TIMESTAMP_DIFF(time_b, time_a, MINUTE) dt,
METRIC_a,
METRIC_b
FROM
TABLE_NAME)
SELECT
CASE
WHEN dt BETWEEN 0 AND 10 THEN "0-10"
WHEN dt BETWEEN 10 AND 20 THEN "11-20"
WHEN dt BETWEEN 20 AND 30 THEN "21-30"
WHEN dt BETWEEN 30 AND 40 THEN "31-40"
WHEN dt > 40 THEN ">40"
END as time_bucket,
AVG(METRIC_a),
SUM(METRIC_b)
FROM CTE
Althought, I should emphasize that this solution works if you have a limited bucket. If you have a lot of buckets, you can create a base table with your buckets then LEFT JOIN it to get your results.
Just use a subquery for each column mentioning the required timestamp in between, also make sure your start_time and end_time columns are timestamp types. For more information, please share the table structure, sample data, and expected output
If I understood well, this would be
SELECT HOUR, (SELECT COUNT(*)
FROM employee
WHERE start_time <= HOUR
AND end_time >= HOUR) AS working
FROM schedule HOUR
Where schedule is a table with employee schedules.

mysql find count of records each month and by referencing two dates

update: this can be done with python. here
i have a table like this:
event_id vendor_id start_date end_date
1 100 2021-01-01 2021-01-31
2 101 2021-01-15 2021-02-15
3 102 2021-02-01 2021-02-31
4 103 2021-02-01 2021-03-31
5 104 2021-03-01 2021-03-31
6 105 2021-03-01 2021-04-31
7 100 2021-04-01 2021-04-31
i would like an output like this: number of events based on month. but if the event between two or more months, it must be included in the count for each month. For example, The event in the second row (event_id=2) takes place in both January and February. Therefore, this event should be included in the total both in January and February.
output:
month total_event
2021-01 2 ---->> event_id=(1,2)
2021-02 3 ---->> event_id=(2,3,4)
2021-03 3 ---->> event_id=(4,5,6)
2021-04 2 ---->> event_id=(6,7)
Note: I wrote it to make the " --->> event_id= : " part better understood. i dont needed. i just need the month and the total_event.
i tried this query:
select date_format(start_date,'%Y-%m') as month,count(event_id) as total_event
group by date_format(start_date,'%Y-%m')
month total_event
2021-01 2
2021-02 2
2021-03 2
2021-04 1
but it counts only by start_date, so the numbers are missing.
Idea
To get the valid months list from the table
To calculate the event counts by event table's joining with the months
MySQL 8.0+
We can get the valid months list by Recursive.
Here is a full SQL. Assumed that your event table is c!
WITH RECURSIVE all_dates(dt) AS (
-- anchor
SELECT MIN(c.`start_date`) AS dt FROM c
UNION ALL
-- recursion with stop condition
SELECT dt + INTERVAL 1 MONTH
FROM all_dates WHERE dt + INTERVAL 1 MONTH <= (SELECT MAX(c.end_date) FROM c)
)
SELECT LEFT(dt, 7) AS `month`, COUNT(d.dt) AS total_event, GROUP_CONCAT(DISTINCT c.`event_id`) AS event_ids FROM all_dates d
INNER JOIN c ON LEFT(d.dt, 7) >= LEFT(c.start_date, 7) AND LEFT(d.dt, 7) <= LEFT(c.end_date, 7)
GROUP BY LEFT(dt, 7);

How to SELECT the last 30 days records from SQL, including days with zero?

I want to SELECT from my table the last 30 day records. My queries looks like this:
SELECT DATE(o_date) as date, count(id) AS sum FROM customers WHERE o_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 30 DAY) AND NOW() GROUP BY o_date
Or this:
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE o_date >= DATE(NOW()) + INTERVAL -30 DAY GROUP BY DATE(o_date)
I want to create a list with dates and count of id-s.
But where I dont have any records in exact day, the query just skip that date. But I want to insert there a zero.
Example:
id
o_date
1
2021-11-23
2
2021-11-22
3
2021-11-20
4
2021-11-20
5
2021-11-19
6
2021-11-18
7
2021-11-18
The result will be this:
date
sum
2021-11-23
1
2021-11-22
1
2021-11-20
2
2021-11-19
1
2021-11-18
2
But where I dont have records like in this example in 2021-11-21 how can I insert to the sum 0?
Thank you!
UPDATE:
I need this query for MariaDB.
For MariaDB,
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE o_date BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY)
AND NOW();
For SQL,
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE DATEDIFF(day,o_date,GETDATE()) < 31
or
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE DATEDIFF(day,o_date,GETDATE()) between 0 and 30
From what I could gather, it should be :
SELECT * FROM customers WHERE o_date BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW();
Link to almost 10 year old post:
MySQL Query - Records between Today and Last 30 Days
Try this query:
SELECT DATE(o_date) AS date, COUNT(id) AS sum FROM customers WHERE o_date >= DATE_ADD(NOW(), INTERVAL -30 DAY)
Your real question seems to be about how to show all 30 days, even days with a zero value.
Since you are using MariaDB 10.0 or newer, there is a nifty trick to give all the days in a range:
MariaDB [test]> SELECT '2019-01-01' + INTERVAL seq-1 DAY AS dates FROM seq_1_to_31;
+-----------------------------------+
| dates |
+-----------------------------------+
| 2019-01-01 |
| 2019-01-02 |
| 2019-01-03 |
| 2019-01-04 |
| 2019-01-05 |
| 2019-01-06 | etc.
So, what you do is
SELECT ...
FROM ( select using seq table ) AS dates
JOIN ( your table ) AS yours ON dates.dy = yours.o_date
WHERE ...
Your secondary question about how to ask for a date range -- both of your attempts give the same result with the same performance.

Given a table with time periods, query for a list of sum per day

Let's say I have a table that says how many items of something are valid between two dates.
Additionally, there may be multiple such periods.
For example, given a table:
itemtype | count | start | end
A | 10 | 2014-01-01 | 2014-01-10
A | 10 | 2014-01-05 | 2014-01-08
This means that there are 10 items of type A valid 2014-01-01 - 2014-01-10 and additionally, there are 10 valid 2014-01-05 - 2014-01-08.
So for example, the sum of valid items at 2014-01-06 are 20.
How can I query the table to get the sum per day? I would like a result such as
2014-01-01 10
2014-01-02 10
2014-01-03 10
2014-01-04 10
2014-01-05 20
2014-01-06 20
2014-01-07 20
2014-01-08 20
2014-01-09 10
2014-01-10 10
Can this be done with SQL? Either Oracle or MySQL would be fine
The basic syntax you are looking for is as follows:
For my example below I've defined a new table called DateTimePeriods which has a column for StartDate and EndDate both of which are DATE columns.
SELECT
SUM(NumericColumnName)
, DateTimePeriods.StartDate
, DateTimePeriods.EndDate
FROM
TableName
INNER JOIN DateTimePeriods ON TableName.dateColumnName BETWEEN DateTimePeriods.StartDate and DateTimePeriods.EndDate
GROUP BY
DateTimePeriods.StartDate
, DateTimePeriods.EndDate
Obviously the above code won't work on your database but should give you a reasonable place to start. You should look into GROUP BY and Aggregate Functions. I'm also not certain of how universal BETWEEN is for each database type, but you could do it using other comparisons such as <= and >=.
There are several ways to go about this. First, you need a list of dense dates to query. Using a row generator statement can provide that:
select date '2014-01-01' + level -1 d
from dual
connect by level <= 15;
Then for each date, select the sum of inventory:
with
sample_data as
(select 'A' itemtype, 10 item_count, date '2014-01-01' start_date, date '2014-01-10' end_date from dual union all
select 'A', 10, date '2014-01-05', date '2014-01-08' from dual),
periods as (select date '2014-01-01' + level -1 d from dual connect by level <= 15)
select
periods.d,
(select sum(item_count) from sample_data where periods.d between start_date and end_date) available
from periods
where periods.d = date '2014-01-06';
You would need to dynamically set the number of date rows to generate.
If you only needed a single row, then a query like this would work:
with
sample_data as
(select 'A' itemtype, 10 item_count, date '2014-01-01' start_date, date '2014-01-10' end_date from dual union all
select 'A', 10, date '2014-01-05', date '2014-01-08' from dual)
select sum(item_count)
from sample_data
where date '2014-01-06' between start_date and end_date;

MySQL Query to select last record for each of the past 7 days

I'm trying to come up with a MySQL query to select the last record from each of the previous 7 days. If 1 of the previous 7 days is missing data, I would only get back 6 records. Here's what I have:
SELECT tracking.* FROM tracking
INNER JOIN
(SELECT MAX(lastChecked) AS maxLastChecked, id FROM tracking
WHERE lastChecked >= DATE_SUB(lastChecked, INTERVAL 7 DAY )
GROUP BY DAY(lastChecked)) as Lookup ON Lookup.id = tracking.id
WHERE tracking.propertyID = 1 ORDER BY tracking.lastChecked ASC LIMIT 7
Basically what this should do is select the final recorded entry for propertyID = 1 in the tracking table for each of the past 7 days (starting on today). However, this query is returning this to me (more than ONLY records within the last 7 days):
ID propertyID lastChecked value
2 1 2012-01-25 05:30:00 280
1 1 2012-01-26 12:34:02 268
5 1 2012-01-27 09:51:31 268
83 1 2012-02-13 00:01:07 276
Any help to fix this up would be greatly appreciated!
Try this query:
SELECT tracking.* FROM tracking
INNER JOIN
(SELECT MAX(lastChecked) AS maxLastChecked, id FROM tracking
WHERE DATEDIFF(lastChecked,NOW())<=7
GROUP BY DAY(lastChecked)) as Lookup ON Lookup.id = tracking.id
WHERE tracking.propertyID = 1 ORDER BY tracking.lastChecked ASC
I believe you should have a system date instead of "lastChecked" in this part:
DATE_SUB(lastChecked, INTERVAL 7 DAY )
Should be:
DATE_SUB(SYSDATE(), INTERVAL 7 DAY )
WHERE lastChecked >= DATE_SUB(lastChecked, INTERVAL 7 DAY )
This condition of your code is going to be true for every record as lastChecked is always greater than lastChecked -7.
So, if you need data of last 7 days replace it with
WHERE lastChecked >= DATE_SUB(SYSDATE(), INTERVAL 7 DAY )