mysql avg value with specified number of rows - mysql

i have a simple table with
value (int), created (timestamp)
i would like to do a query that return an arbitrary number of rows with avg (value) and avg(created). The grouping function is the order by of created, means that if i ask for 2 rows, i should obtain a set based on the first rows ordered by created.
i have the following table:
create table log(value int,created timestamp);
insert into log values
(1,'2016-01-01 00:00:00'),
(2,'2016-01-01 01:00:00'),
(3,'2016-01-01 02:00:00'),
(4,'2016-01-01 03:00:00'),
(5,'2016-01-01 04:00:00'),
(6,'2016-01-01 05:00:00'),
(7,'2016-01-01 06:00:00'),
(8,'2016-01-01 07:00:00'),
(9,'2016-01-01 08:00:00');
http://sqlfiddle.com/#!9/b9a94
i want to retrive 3 rows that should be
2-2016-01-01 01:00:00
5-2016-01-01 04:00:00
8-2016-01-01 07:00:00
it's possible to do it in a single query without using java or php processing?

For example when you group by 3, ordered by creation date. You can change 3 to the size you want to group by.
SELECT avg(sub.value), avg(sub.created)
from (
SELECT #row_number:=#row_number+1 AS row_number,value, created
FROM log, (SELECT #row_number:=0) AS t
ORDER BY created
) sub
group by floor((sub.row_number-1)/3)
UPDATE
if you always want it to be divided in 3 groups based on the number of rows you can do the following:
SELECT avg(sub.value), avg(sub.created)
from (
SELECT #row_number:=#row_number+1 AS row_number, floor((#row_number-1)/c.num) groupid,value, created
FROM log,
(SELECT #row_number:=0) AS t,
(SELECT ceil(COUNT(*)/3) num FROM log) c
ORDER BY log.created
) sub
GROUP BY sub.groupid

Related

find first transaction after created date and added to a column MySQL

I am using MySQL version 8.0
MRE:
create table users(
user varchar(5),
work_type varchar(20),
time datetime
);
insert into users(user, work_type, time)
Values ("A", "create", "2020-01-01 11:11:11")
, ("A", "bought", "2020-01-04 16:11:11")
, ("A", "bought", "2020-01-07 18:10:10")
, ("A", "bought", "2020-01-08 12:00:11")
, ("A", "create", "2020-02-02 15:17:11")
, ("A", "bought", "2020-02-02 16:11:11");
In my table for each user there is a "work_type" column which specifies what user does.
user work_type time
A create 2020-01-01 11:11:11
A bought 2020-01-04 16:11:11
A bought 2020-01-07 18:10:10
A bought 2020-01-08 12:00:11
A create 2020-02-02 15:17:11
A bought 2020-02-02 16:11:11
Since after user A "create" their account I want to find only first bought time and add it to new column
user work_type time bought_time
A create 2020-01-01 11:11:11 2020-01-04 16:11:11
A create 2020-02-02 15:17:11 2020-02-02 16:11:11
Notice that user A can have multiple create work_type. Above is the desired output however there will be multiple user as well.
A correlated subquery in the select list can retrieve a single value. I use the order by time asc limit 1 clauses to limit the number of returned rows to 1:
select t.*, (select t2.`time` from yourtable t2 where t2.user=t.user and t2.`time` > t.`time` and t2.work_type='bought' order by t2.`time` asc limit 1) as bought_time
from yourtable t
where work_type='create'
The above query is fine, as long as you have at least 1 bought record after each create one. If you cannot guarantee this and you have no other fields to link a create with the subsequent bought, then you have to complicate things to check for the type of the next record after the create. Note: I do not filter on the work_type field in the subquery any longer:
select t.*, (select if(t2.work_type='bought',t2.`time`,null) from yourtable t2 where t2.user=t.user and t2.`time` > t.`time` order by t2.`time` asc limit 1) as bought_time
from yourtable t
where work_type='create'
If the create and subsequent bought records form part of a set, then I would definitely create a field that links them together, meaning that this field would have the same value for all records belonging to the same set. This way it would be really easy to identify which records form part of the set.
Solution for your problem:
SELECT * FROM
(
SELECT
user
,work_type
,CASE WHEN UPPER(work_type) = 'CREATE' THEN time END time
,CASE WHEN UPPER(work_type) = 'CREATE'
THEN LEAD(time) OVER(PARTITION BY user ORDER BY time) END bought_time
FROM
Table1) A
WHERE UPPER(work_type) = 'CREATE';
Link for demo:
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ac0cf9375025b964769fd28514db0ce1

Inserting using a select with or

I am trying to to perform an insert using the following:
INSERT INTO stock_withholds (s_id, mob, paddock, product, completed_date, withhold_until_date, ESI_withhold_until_date)
SELECT stk_id AS s_id,
mob,
paddock,
product,
completed_date,
withhold_until_date,
ESI_withhold_until_date
FROM stock_current
WHERE (withhold_until_date >=CURDATE()
OR (ESI_withhold_until_date >= CURDATE()
AND stock_current.stk_id NOT IN
(SELECT s_id
FROM stock_withholds)
If I rerun the query it duplicates the records.
Desired result is if either the withhold_until_date or ESI_withhold_until_date are greater then current date the records would inserted.
Thanks

insert row per week in date period

I have a very big table of the kind: user_id, started_at, ends_at, group_id
I need to do some analytics on this so I'm trying to pre-calculate some values, in this specific case I'm looking to create a table like:
active_in_week with id, user_id, active_week where active_week is every week between started_at and ends_at
So for a row with started_at 2017-01-01 and ends_at 2017-01-31 the result would be 4 rows:
id user_id, active_week
1, 1, 1
2, 1, 2
3, 1, 3
4, 1, 4
I would prefer to do this at the query level instead of on a programming language due to the size/speed of this table. The purpose is to do additional queries after that will be aggregating values per week.
Right now if I do those queries in a normalized state they run for up to 8hrs with proper indexes.
You maybe can work with the away like this (attn: it is getting a bit tricky):
CREATE TABLE weeks AS (
SELECT weekId, MIN(date) as starts_at, MAX(date) as ends_at
FROM (
SELECT
YEARWEEK(started_at) AS weekId,
started_at AS date,
FROM srctable
UNION
SELECT
YEARWEEK(ends_at) AS weekId,
ends_at AS date,
FROM srctable
)
GROUP BY weekId
)
Then you should have a table that knows all weeks, start- and end-dates of your data.
The you can do a join on the weeks table.

Count number of days an employee missed work

I have a table which stores information about employees and one of those fields is Date. I want to make a query that returns a count of the number of days they have missed, not including weekends. Date format is '2018-1-1' for example, consecutive days would be '2018-1-2', '2018-1-3', and if next record is '2018-1-5', then count would increase by 1 because 2018-1-4 was a Thursday and they should have a record for that day.
Any ideas on how to best do this?
What I have so far:
SELECT * FROM `time` where name like 'John' AND DayOfWeek(Date) not like 7
and dayofweek(Date) not like 1
ORDER BY `time`.`Date` ASC
This is giving me all of the records for John excluding Saturdays and Sundays. What I want to do now is somehow find gaps between the dates that the records have for workdays. For example, consecutive days would be '2018-1-2', '2018-1-3', and if next record is '2018-1-5', then count would increase by 1 because 2018-1-4 was a Thursday
To make this possible you'll need a helper table, which can be useful also for many other purposes: a table with one column that has natural numbers starting from 0 up to some large n. You could create it like this:
create table nums (i int);
insert into nums values (0), (1), (2), (3);
insert into nums select i+4 from nums;
insert into nums select i+8 from nums;
insert into nums select i+16 from nums;
insert into nums select i+32 from nums;
insert into nums select i+64 from nums;
insert into nums select i+128 from nums;
insert into nums select i+256 from nums;
You can see how you double the number of records by adding a similar insert statement, but this will generate 512 records, which would be enough for your purposes.
Then you can use this query to answer your question:
SELECT ref_date
FROM (
SELECT date_add('2018-01-01', interval i day) ref_date
FROM nums
) calendar
WHERE ref_date <= curdate()
AND dayofweek(ref_date) not in (1, 7)
AND ref_date NOT IN (
SELECT Date
FROM `time`
WHERE name = 'John'
)
See also SQLfiddle

Keeping the structure of SSRS Table common

I have a dataset which returns the top 5 rows from a table and I display the data in SSRS Table. Now I need to maintain the 5 rows Table structure even if the number of rows returned by the dataset is 0 or less than 5. How can i acheive this.?
Thanks
A couple of possibilities.
1) You could add five Footer rows to the table and set the RowVisibility expression to = CountRows() > 0 through to = CountRows() > 4.
2) You could hack about with the query that populates the dataset such that it always returns exactly 5 rows.
e.g. Suppose your current query is
SELECT TOP (5) name,
create_date
FROM sys.objects
ORDER BY create_date
You could change that to
WITH TopFive
AS (SELECT TOP (5) name,
create_date,
ROW_NUMBER() OVER (ORDER BY create_date) AS RN
FROM sys.objects
ORDER BY create_date)
SELECT TF.name,
TF.create_date
FROM (VALUES(1),
(2),
(3),
(4),
(5)) V(N)
LEFT JOIN TopFive TF
ON TF.RN = V.N
ORDER BY TF.RN