automaticly change variable in where clause - mysql

The code i use gives me the correct information based on a date in a where clause. I want to have the same information on other dates. So now I have to change the date myself and run the code, copy/paste it somewhere else and start over again with a new date. That takes a lot of work if i want the information for every day of the year. Is it possible to automaticly change the date in the where clause and what is the best or easiest way to do that?
Select t4.Count, t4.Status
From(
SELECT count(l.VoerID) as Count, l.Datum, l.Status, l.LogID
FROM (
SELECT k.VoerID, k.Datum, MAX(k.LogID) AS LogID
FROM DB.LogStatus k
Where Datum < '2013-07-01'
GROUP BY k.VoerID
) m
JOIN DB.LogStatus l
ON l.VoerID = m.VoerID AND l.LogID = m.LogID
Where status in ('B','IN1','IN2''V','Reserv')
Group by Status
)t4
EDIT:
original table (selected on one VoerID) (table consist of thousands of VoerID's)
LogID Datum UserID Status Time VoerID
1299772 2013-04-17 259 N 14:09:11 50174
1319774 2013-05-23 68 B 11:19:17 50174
1320038 2013-05-23 197 IN1 16:53:30 50174
1322002 2013-05-28 68 IN2 09:22:32 50174
1325052 2013-05-31 161 G 09:00:59 50174
1325166 2013-05-31 10 400 09:15:12 50174
1325182 2013-05-31 10 V 09:30:07 50174
1325208 2013-05-31 10 V 09:45:06 50174
1325406 2013-05-31 10 Reserv 11:45:06 50174
1325522 2013-05-31 10 Reserv 12:15:06 50174
1325954 2013-05-31 10 Reserv 15:15:13 50174
1328474 2013-06-05 10 Reserv 13:15:06 50174
1329230 2013-06-06 10 Reserv 09:45:03 50174
1329244 2013-06-06 10 Archived 10:00:08 50174
1329268 2013-06-06 10 Archived 10:15:08 50174
1330286 2013-06-07 10 Archived 10:15:06 50174
I want to now what was the status of the VoerID on all first of months. so on 2013-05-01 status = N, on 2013-06-01 status = Reserv and from 2013-07-01 it is Archived.
So above is for one VoerID. I want to count the number of VoerID's per first of month, per last LOGID before the first of next month and per status
Finally if I get the information i want to edit it in MSExcel to a crosstable and Chart:
1-1-2013 1-2-2013 1-3-2013 1-4-2013 1-5-2013
N 20 22 24 26 28
B 23 21,5 20 18,5 17
IN1 12 15 18 21 24
IN2 15 7 14 18 25
V 800 1000 1200 1400 1600
Reserv 50 63 76 89 102
Archived 100000 101220 102440 103660 104880

Doing a cross join of all the days of the year, then grouping by that day.
Something like this:-
SELECT COUNT(l.VoerID) as COUNT, m.aDate, l.Status
FROM
(
SELECT Sub1.aDate, k.VoerID, MAX(k.LogID) AS LogID
FROM DB.LogStatus k
CROSS JOIN
(
SELECT DATE_ADD('2013-01-01', INTERVAL units.i + tens.i * 10 + hundreds.i * 100 DAY) AS aDate -- return the first day of the year + all the numbers from 0 to 999
FROM (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units -- Select units of days
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens -- select tens
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) hundreds -- select hundreds
WHERE DATE_ADD('2013-01-01', INTERVAL units.i + tens.i * 10 + hundreds.i * 100 DAY) <= '2013-12-31' -- limit the dates to the days of the specific year
) Sub1
WHERE k.Datum < Sub1.aDate -- This should give up multiple copies of record, one for each date where the d Datum is less that that date
GROUP BY Sub1.aDate, k.VoerID -- GRoup by date and id, so getting the max log id for each date and id
) m
JOIN DB.LogStatus l
ON l.VoerID = m.VoerID AND l.LogID = m.LogID -- Join where log it is the max log id
WHERE status in ('x','y','z')
GROUP BY m.aDate, Status
EDIT - or for each month:-
SELECT COUNT(l.VoerID) as COUNT, m.aDate, l.Status
FROM
(
SELECT Sub1.aDate, k.VoerID, MAX(k.LogID) AS LogID
FROM DB.LogStatus k
CROSS JOIN
(
SELECT DATE_ADD('2013-01-01', INTERVAL units.i MONTH) AS aDate -- return the first day of each month of the year
FROM (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11) units -- Select units of days
) Sub1
WHERE k.Datum < Sub1.aDate -- This should give up multiple copies of record, one for each date where the d Datum is less that that date
GROUP BY Sub1.aDate, k.VoerID -- GRoup by date and id, so getting the max log id for each date and id
) m
JOIN DB.LogStatus l
ON l.VoerID = m.VoerID AND l.LogID = m.LogID -- Join where log it is the max log id
WHERE status in ('x','y','z')
GROUP BY m.aDate, Status

Related

MySQL query for records that existed at any point each week

I have a table with created_at and deleted_at timestamps. I need to know, for each week, how many records existed at any point that week:
week
records
2022-01
4
2022-02
5
...
...
Essentially, records that were created before the end of the week and deleted after the beginning of the week.
I've tried various variations of the following but it's under-reporting and I can't work out why:
SELECT
DATE_FORMAT(created_at, '%Y-%U') AS week,
COUNT(*)
FROM records
WHERE
deleted_at > DATE_SUB(deleted_at, INTERVAL (WEEKDAY(deleted_at)+1) DAY)
AND created_at < DATE_ADD(created_at, INTERVAL 7 - WEEKDAY(created_at) DAY)
GROUP BY week
ORDER BY week
Any help would be massively appreciated!
I would create a table wktable that looks like so (for the last 5 weeks of last year):
yrweek | wkstart | wkstart
-------+------------+------------
202249 | 2022-11-27 | 2022-12-03
202250 | 2022-12-04 | 2022-12-10
202251 | 2022-12-11 | 2022-12-17
202252 | 2022-12-18 | 2022-12-24
202253 | 2022-12-25 | 2022-12-31
To get there, find a way to create 365 consecutive integers, make all the dates of 2022 out of that, and group them by year-week.
This is an example:
CREATE TABLE wk AS
WITH units(units) AS (
SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
)
,tens AS(SELECT units * 10 AS tens FROM units )
,hundreds AS(SELECT tens * 10 AS hundreds FROM tens )
,
i(i) AS (
SELECT hundreds +tens +units
FROM units
CROSS JOIN tens
CROSS JOIN hundreds
)
,
dt(dt) AS (
SELECT
DATE_ADD(DATE '2022-01-01', INTERVAL i DAY)
FROM i
WHERE i < 365
)
SELECT
YEAR(dt)*100 + WEEK(dt) AS yrweek
, MIN(dt) AS wkstart
, MAX(dt) AS wkend
FROM dt
GROUP BY yrweek
ORDER BY yrweek;
With that table, go:
SELECT
yrweek
, COUNT(*) AS records
FROM wk
JOIN input_table ON wk.wkstart < input_table.deleted_at
AND wk.wkend > input_table.created_at
GROUP BY
yrweek
;
I first build a list with the records, their open count, and the closed count
SELECT
created_at,
deleted_at,
(SELECT COUNT(*)
from records r2
where r2.created_at <= r1.created_at ) as new,
(SELECT COUNT(*)
from records r2
where r2.deleted_at <= r1.created_at) as closed
FROM records r1
ORDER BY r1.created_at;
After that it's just adding a GROUP BY:
SELECT
date_format(created_at,'%Y-%U') as week,
MAX((SELECT COUNT(*)
from records r2
where r2.created_at <= r1.created_at )) as new,
MAX((SELECT COUNT(*)
from records r2
where r2.deleted_at <= r1.created_at)) as closed
FROM records r1
GROUP BY week
ORDER BY week;
see: DBFIDDLE
NOTE: Because I use random times, the results will change when re-run. A sample output is:
week
new
closed
2022-00
31
0
2022-01
298
64
2022-02
570
212
2022-03
800
421

Take count based on rage of date SQL

I have below mentioned sql query which gives me simple count as on that date or if i use where and and condition for date it gives me consolidated count between the date.
select count(*) from A where date(date_1) = '2017-06-01';
I want count for a range of date for separate count for each date.
select count(*) from A where date(date_1) >= '2017-06-01' and date(date_1)<='2017-06-30';
Desired output:
Date Count
2017-06-01 25
2017-06-02 20
2017-06-03 15
- -
- -
2017-06-30 10
Something like the following might work:
SELECT DATE(date_1), COUNT(*)
FROM A
GROUP BY DATE(date_1)
COUNT, being an aggregate function, is often combined with the GROUP BY clause in order to get aggregate results per group (in this case, counts per date).
WHERE clauses can of course be added as desired, right before the GROUP BY clause.
All you need is to use group by clause like:
SELECT date(date_1),Count(*) as [Count]
FROM A
WHERE date(date_1) >= '2017-06-01' and date(date_1)<='2017-06-30'
GROUP BY date(date_1)
The following solution also reports days with no noted incidents. The example is devised for the month of February:
SELECT (DATE'2018-01-31' + INTERVAL x.n DAY) dd
, COALESCE(sqt.c, 0) incidents
FROM (select 1 n union all select 2 n union all select 3 n union all select 4 n union all select 5 n union all select 6 n union all select 7 n union all select 8 n union all select 9 n union all select 10 n union all select 11 n union all select 12 n union all select 13 n union all select 14 n union all select 15 n union all select 16 n union all select 17 n union all select 18 n union all select 19 n union all select 20 n union all select 21 n union all select 22 n union all select 23 n union all select 24 n union all select 25 n union all select 26 n union all select 27 n union all select 28 n) x
LEFT JOIN (
SELECT date1 d
, count(*) c
FROM t
GROUP BY date1
) sqt
ON sqt.d = (DATE'2018-01-31' + INTERVAL x.n DAY)
;
The standalone demo can be viewed here.
The sequence generator has been shamelessly adopted from this SO answer.

MySQL query - convert to left join to show results with zero?

I'm not very good when it comes to using joins - so I have a single table where I'm counting the number of records that meet certain conditions, and returns those counts by week. The problem is, I need the weeks that have a zero count too....I tried to get this to work with a left join, but I'm struggling...any help appreciated: (Stamp is a datetime field)
Query:
SELECT week(stamp), count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp)
This returns:
week(stamp) | mycount | theyear
15 | 21 |2017
17 | 10 |2017
18 | 62 |2017
19 | 13 |2017
20 | 76 |2017
21 | 22 |2017
Notice week 16 is missing? I need to have this result included in the above, like:
16 | 0 |2017
I appreciate any help - I know this isn't too difficult, but I'm pulling my hair out trying to understand how to do this while I read other posts....
select weekValue, yearValue, coalesce(mycount,0)
from
( SELECT distinct week(#startDate := #startDate + Interval 1 day) as weekValue,
year(#startDate := #startDate + Interval 1 day) as yearValue
FROM
(select 0 union all select 1 union all select 3 union all select 4
union all select 5 union all select 6 union all select 6 union all select 7
union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3
union all select 4 union all select 5 union all select 6
union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4,
(SELECT #startDate := '2017-03-31 00:00:00' ) as g
where
#startDate < '2017-06-01 00:00:00' ) as generateWeekYear left join
(SELECT week(stamp) as theweek, count(*) AS mycount, YEAR(stamp) as theyear
FROM merges
WHERE completed = 1
AND stamp BETWEEN '2017/4/1 00:00:00' AND '2017/6/1 00:00:00' GROUP BY week(stamp) ) as actualQuery
on generateWeekYear.weekValue = actualQuery.theweek
and generateWeekYear.yearValue = actualQuery.theyear
Let me explain the above query,
Sub Query generateWeekYear = This is used to genearate distinct week and year based on two inputs
lets say startDate and endDate. startDate should be 1 day less to actual startDate. Because if you do not
subtract 1 day then there might chance to loose one week.
Now you have all week and year which needs to be displayed.
Now you are thinking generateWeekYear is going to be more time to execute but this is not case. You can
check this generate an integer sequence in MySQL.
After that you simply join your table with above table and you can get your required result.

non available row in result group by

I used the following query
select month(SubmittedDate), count(policyid) from tblpolicy p join tlkppolicystatus s on p.StatusID=s.StatusID where SubmittedDate between
'2017-01-01' and sysdate() and s.StatusID=1 group by month(SubmittedDate);
This returns the following output which is correct as month number 3 and 4 don't have any data.
Month Total
-----|-----
1 | 62
2 | 34
5 | 1
But I want the output to be like
Month Total
-----|-----
1 | 62
2 | 34
3 | 0
4 | 0
5 | 1
So that means if any month do have any data then also it will show with a value 0
Thanks
If you have data for all months, but none of the data has a status of 1, then the simplest method is probably to use conditional aggregation:
select month(SubmittedDate), sum(s.StatusID = 1)
from tblpolicy p join
tlkppolicystatus s
on p.StatusID=s.StatusID
where SubmittedDate between '2017-01-01' and sysdate()
group by month(SubmittedDate);
Of course, if those conditions don't hold, then the left join with a derived table is the best solution.
Try this
select coalesce(t1.month,t2.month) as month, coalesce(ct1.count,0) as count from
(
select month(SubmittedDate) as month, count(policyid) as count
from tblpolicy p join tlkppolicystatus s on p.StatusID=s.StatusID
where SubmittedDate between
'2017-01-01' and sysdate() and s.StatusID=1 group by month(SubmittedDate)
) as t1 right join
(
select 1 as month union all
select 2 as month union all
select 3 as month union all
select 4 as month union all
select 5 as month union all
select 6 as month union all
select 7 as month union all
select 8 as month union all
select 9 as month union all
select 10 as month union all
select 11 as month union all
select 12 as month
) as t2 on t1.month <= t2.month;

Count the number of records per month

I have records with a start and end date, like so:
id start_date end_date
1 2016-01-01 2016-10-31
2 2016-06-01 2016-12-31
3 2016-06-01 2016-07-31
I have to know the number of records that were active per month (or better put: on the first day of all months in a given period). The counts would look like this when calculated for 2016:
jan: 1
feb: 1
mar: 1
apr: 1
may: 1
jun: 3
jul: 3
aug: 2
sep: 2
oct: 2
nov: 1
dec: 1
The solution I came up with, is to create a TEMP TABLE with all applicable dates for the given period:
date
2016-01-01
2016-02-01
...
Which makes the query very easy:
SELECT
COUNT(*),
m.date
FROM
months m
INNER JOIN table t
ON m.date BETWEEN t.start_date AND t.end_date
GROUP BY
m.date
This produces exactly the results I'm looking for. However; I do feel as if this could be done easier. I just don't know how.
Any suggestions?
You can do it the following way, even if it looks ugly:
Assuming you want to run a report and you are only interested in "months of a certain year", the following query might work:
select m,Count(id) FROM (
SELECT 1 as m UNION
SELECT 2 as m UNION
SELECT 3 as m UNION
SELECT 4 as m UNION
SELECT 5 as m UNION
SELECT 6 as m UNION
SELECT 7 as m UNION
SELECT 8 as m UNION
SELECT 9 as m UNION
SELECT 10 as m UNION
SELECT 11 as m UNION
SELECT 12 as m) AS tabseq
CROSS JOIN x WHERE
(year (start_date) = 2016 AND year (end_date) = 2016 AND m >= month(start_date) AND m <= month(end_date)) -- starts abd ends this year
or
(year (start_date) < 2016 AND year (end_date) = 2016 AND m <= month(end_date)) -- ends this year, consider months until end of contract
or
(year (start_date) < 2016 AND year (end_date) > 2016) -- spans the year, ignore month,
or
(year (start_date) = 2016 AND year (end_date) > 2016 AND m >= month(start_date)) -- starts this year, consider months until end of year
GROUP BY m;
result:
m count(id)
1 1
2 1
3 1
4 1
5 1
6 3
7 3
8 2
9 2
10 2
11 1
12 1
As suggested in the comments, I replaced the temp table with a permanent table called 'calendar'.
CREATE TABLE `calendar` (
`date` date NOT NULL,
PRIMARY KEY (`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
I filled this table with all dates from 2000-01-01 until 2100-12-31. I rewrote my query to this:
SELECT
COUNT(*),
c.date
FROM
calendar c
INNER JOIN table t
ON c.date BETWEEN t.start_date AND t.end_date
WHERE
DAYOFMONTH(c.date) = 1
AND
c.date BETWEEN '2016-01-01' AND '2016-12-31'
GROUP BY
c.date