SQL request for report - mysql

There is a problem with the writing of the request for the report, which reflects the number of events is created and updated every day.
The table includes:
FieldTypeComment
id int (11) NOT NULL
name varchar (255) NULL
created_on datetime NOT NULL
updated_on datetime NOT NULL
Created and updated date may not coincide.
request:
SELECT
z.date_created,
SUM (IF (z.date_created = z.date_updated, 1, 0)) AS created,
SUM (IF (z.date_created! = Z.date_updated, 1, 0)) AS updated
FROM
(
SELECT
SUBSTRING (e.updated_on, 1, 10) AS date_updated,
SUBSTRING (e.created_on, 1, 10) AS date_created
FROM
event e
) z
GROUP BY
z.date_created
I need the following result:
Does not give the desired result, because does not show all dates.

Assuming you want
see all dates;
count create in fact;
count update only if it is other date than create:
http://www.sqlfiddle.com/#!2/56254/39
SELECT A.*
,(SELECT count(created_on)
FROM event WHERE date=created_on) created
,(SELECT sum(created_on<>updated_on)
FROM event WHERE date=updated_on) updated
FROM (
SELECT created_on date FROM event
UNION SELECT updated_on FROM event ) A

To get number of events created and updated on a particular day, do something like this.
select count(*) events
from event
where
(created_on >= {d '2013-02-10'} and created_on < {d '2013-02-11'})
or
(updated_on >= {d '2013-02-10'} and updated_on < {d '2013-02-11'})
This approach takes the time portion of those fields into account.

Related

MySQL how to query data with time now to start date and end date

i want to get data from table, and example of data like this:
Event Name
Start Date
End Date
Event 1
2022-07-30 00:00:00
2022-08-06 23:59:59
Event 2
2022-08-08 00:00:00
2022-08-15 23:59:59
value of example is "2022-08-07 00:00:00", what i want is get data "Event 1" as the latest event because there is no event starting in "2022-08-07".
and when the value of example "2022-08-08 12:12:12", what i want is get data "Event 2" because there is a event starting from that date. And when the value is "2022-08-09 08:00:00" i want to still get the data "Event 2", because the date is still lower than end date.
How to query in MySQL, so i can result like that?
Assuming proper column names, it would be as below for your initial query (swap out the date for each of your examples):
SELECT *
FROM table
WHERE StartDate <= "2022-08-07 00:00:00"
ORDER BY StartDate desc
LIMIT 1;
Of course if you're basing it of when you run the SQL:
SELECT *
FROM table
WHERE StartDate <= now()
ORDER BY StartDate desc
LIMIT 1;
My approach would be the following nested SELECTs - replace the mentioned timestamp '2022-08-16 12:12:12' with NOW() or any timestamp you need in your case
SELECT * FROM
(
(
SELECT a.*,-1 AS mynumber
FROM `testtest2` AS a
WHERE start<='2022-08-16 12:12:12' AND end >='2022-08-16 12:12:12'
)
UNION
(
SELECT a.*,b.mynumber
FROM `testtest2` AS a,
(SELECT COUNT(*) AS mynumber FROM `testtest2` WHERE start<='2022-08-16 12:12:12' AND end >='2022-08-16 12:12:12') AS b
WHERE end<='2022-08-16 12:12:12' ORDER BY end DESC LIMIT 1
)
) c
WHERE mynumber<=0
Explanation
One SELECT retrieves all events currently active (start <= timestamp AND end >= timestamp) and sets the column mynumber to the fixed value -1 (which clearly is < 0).
The next SELECT retrieves events that have already passed, sorted descending by the column end and only retrieve the first (= newest/last) event. The column mynumber is filled with the numer of events that are currently running - so this column contains either a 0 (if there are no events currently running) or a positive number (> 0) if there are other events running.
These two SELECTs are combined using UNION and used as source for the outer SELECT which only retrieves events with a value <=0 for the column mynumber.
This way you get either the active events OR (in case there are no active events) the last/newest event which has already passed.

MySQL. I want to get a percentage of emails, which were clicked not later than 10 min after user received an email

I have 2 tables in MySQL:
emails_sent(
id int primary key,
id_type int,
date_sent datetime)
emails_clicks(
id int primary key,
id_email int,
date_click datetime,
foreign key(id_email) references emails_sent(id))
I want to write a query, that will return a percentage of emails, that were clicked not later than 10 min after user received an email. Grouped by each type of email (id_type). How can i do this?
You can do a conditional average.
In MySQL, that would be:
select s.id_type, avg(c.date_click < s.date_sent + interval 10 minute) res
from email_sent s
left join email_clicks c on c.id_email = s.id
group by id_type
For each each row in email_sent, the query recovers the corresponding row in email_clicks; if the email was clicked less than 10 minutes, the condition within avg() returns 1, else 0. The average gives you the ratio of emails that were clicked less than 10 minutes after being sent (as a decimal value between 0 and 1).
Note: This assumes SQL Server (one of the original tags).
You can use a subquery to get the first click and then aggregate:
select s.id_type,
avg(case when first_click_date < dateadd(minute, 10, date_sent)
then 1.0 else 0
end) as num_clicks
from (select s.*,
(select min(date_click)
from email_clicks c
where c.id_email = s.id
) as first_click_date
from email_sent s
) s
group by id_type;
This handles users that click multiple times, 10 minutes later or not.

Count Distinct Date MySQL returning one row

Ok so be ready I'm working on a weird base :
Every table has 3 column only : varchar('Object'),varchar('Property'),varchar('Value')
Here is a fiddle I've build with examples of my tries
http://sqlfiddle.com/#!9/de22eb/1
I need to extract the last time a server was update. But i'm not interested in the server itself it's more about the date. Once I know that there was an update for a date I'm looking to count every updates on that day.
To do so I'm using 2 tables : the server table
CREATE TABLE IF NOT EXISTS `server` (
`name` varchar(80) NOT NULL,
`field` varchar(80) NOT NULL,
`value` varchar(200) NOT NULL
);
And the event table :
CREATE TABLE IF NOT EXISTS `event` (
`name` varchar(80) NOT NULL,
`field` varchar(80) NOT NULL,
`value` varchar(80) NOT NULL
);
Please go watch the fiddle to have an idea of the content.
I want to have a result like this (based on my example) :
Date Number patched
2017-11-14 2
2017-11-04 1
The problem is that I don't know where I'm wrong on my query (I've separated the step for better understanding inside the fiddle) :
Select date_format(d.val, '%Y-%m-%d') as 'Date', COUNT(distinct
date_format(d.val, '%Y-%m-%d')) as 'Number'
FROM (
Select b.serv,b.val
FROM (
Select serv,val FROM (
Select name as serv, value as val FROM event
where field='server_vers' and
value!='None'
order by serv ASC,
val DESC LIMIT 18446744073709551615) a
group by a.serv) b,
server c
where b.serv = c.name and c.field = 'OS' and c.value = 'Fedora'
) d group by date_format(d.val, '%Y-%m-%d');
It's giving me only one row. Adding group by date_format(d.val, '%Y-%m-%d') at the end makes the Count useless. How can I fix that ?
I want to have for each server for a given OS type the last patch date and then sum the result by date.
Is that what you needed ?
SELECT dates.date, COUNT(dates.date) as patch_count
FROM (
SELECT MAX(date_format(event.value, '%Y-%m-%d')) as date
FROM event
JOIN server ON (event.name = server.name)
WHERE (server.field = 'OS' AND server.value = 'Fedora')
GROUP BY event.name ) as dates
GROUP BY 1
ORDER BY 2 DESC
Here's the fiddle :
http://sqlfiddle.com/#!9/de22eb/37/0
Explanation : We get the last date for every server name. That gives a list of last dates. Then we use this as a table, that we can group on to count each different value.
The datetimes are stored as strings. The first ten characters of that string represent the date. So you get the date with left(value, 10).
You get the last update per server by grouping by server and retrieving max(left(value, 10)), because alphabetic order works on 'yyyy-mm-dd'.
select name, max(left(value, 10))
from event
where field = 'server_vers'
and value <> 'None'
group by name
Build up on this to get the count of updates on those last-update dates:
select left(value, 10), count(*)
from event
where field = 'server_vers'
and left(value, 10) in
(
select max(left(value, 10))
from event
where field = 'server_vers'
and value <> 'None'
group by name
)
group by left(value, 10)
order by left(value, 10);

Generating complex sql tables

I currently have an employee logging sql table that has 3 columns
fromState: String,
toState: String,
timestamp: DateTime
fromState is either In or Out. In means employee came in and Out means employee went out. Each row can only transition from In to Out or Out to In.
I'd like to generate a temporary table in sql to keep track during a given hour (hour by hour), how many employees are there in the company. Aka, resulting table has columns HourBucket, NumEmployees.
In non-SQL code I can do this by initializing the numEmployees as 0 and go through the table row by row (sorted by timestamp) and add (employee came in) or subtract (went out) to numEmployees (bucketed by timestamp hour).
I'm clueless as how to do this in SQL. Any clues?
Use a COUNT ... GROUP BY query. Can't see what you're using toState from your description though! Also, assuming you have an employeeID field.
E.g.
SELECT fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable
INNER JOIN (SELECT employeeID AS 'empID', MAX(timestamp) AS 'latest' FROM StaffinBuildingTable GROUP BY employeeID) AS LastEntry ON StaffinBuildingTable.employeeID = LastEntry.empID
GROUP BY fromState
The LastEntry subquery will produce a list of employeeIDs limited to the last timestamp for each employee.
The INNER JOIN will limit the main table to just the employeeIDs that match both sides.
The outer GROUP BY produces the count.
SELECT HOUR(SBT.timestamp) AS 'Hour', SBT.fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable AS SBT
INNER JOIN (
SELECT SBIJ.employeeID AS 'empID', MAX(timestamp) AS 'latest'
FROM StaffinBuildingTable AS SBIJ
WHERE DATE(SBIJ.timestamp) = CURDATE()
GROUP BY SBIJ.employeeID) AS LastEntry ON SBT.employeeID = LastEntry.empID
GROUP BY SBT.fromState, HOUR(SBT.timestamp)
Replace CURDATE() with whatever date you are interested in.
Note this is non-optimal as it calculates the HOUR twice - once for the data and once for the group.
Again you are using the INNER JOIN to limit the number of returned row, this time to the last timestamp on a given day.
To me your description of the FromState and ToState seem the wrong way round, I'd expect to doing this based on the ToState. But assuming I'm wrong on that the following should point you in the right direction:
First, I create a "Numbers" table containing 24 rows one for each hour of the day:
create table tblHours
(Number int);
insert into tblHours values
(0),(1),(2),(3),(4),(5),(6),(7),
(8),(9),(10),(11),(12),(13),(14),(15),
(16),(17),(18),(19),(20),(21),(22),(23);
Then for each date in your employee logging table, I create a row in another new table to contain your counts:
create table tblDailyHours
(
HourBucket datetime,
NumEmployees int
);
insert into tblDailyHours (HourBucket, NumEmployees)
select distinct
date_add(date(t.timeStamp), interval h.Number HOUR) as HourBucket,
0 as NumEmployees
from
tblEmployeeLogging t
CROSS JOIN tblHours h;
Then I update this table to contain all the relevant counts:
update tblDailyHours h
join
(select
h2.HourBucket,
sum(case when el.fromState = 'In' then 1 else -1 end) as cnt
from
tblDailyHours h2
join tblEmployeeLogging el on
h2.HourBucket >= el.timeStamp
group by h2.HourBucket
) cnt ON
h.HourBucket = cnt.HourBucket
set NumEmployees = cnt.cnt;
You can now retrieve the counts with
select *
from tblDailyHours
order by HourBucket;
The counts give the number on site at each of the times displayed, if you want during the hour in question, we'd need to tweak this a little.
There is a working version of this code (using not very realistic data in the logging table) here: rextester.com/DYOR23344
Original Answer (Based on a single over all count)
If you're happy to search over all rows, and want the current "head count" you can use this:
select
sum(case when t.FromState = 'In' then 1 else -1) as Heads
from
MyTable t
But if you know that there will always be no-one there at midnight, you can add a where clause to prevent it looking at more rows than it needs to:
where
date(t.timestamp) = curdate()
Again, on the assumption that the head count reaches zero at midnight, you can generalise that method to get a headcount at any time as follows:
where
date(t.timestamp) = "CENSUS DATE" AND
t.timestamp <= "CENSUS DATETIME"
Obviously you'd need to replace my quoted strings with code which returned the date and datetime of interest. If the headcount doesn't return to zero at midnight, you can achieve the same by removing the first line of the where clause.

SQL - add days to a date in a loop

I have a table with this fields:
Id int(11) pk,
Start_date date,
End_date date,
type tinyint(1),
Frequency int.
I want to do a select on this table where start_date+frequency = #date(a variable date) until end_date(loop).
How do this with sql?
Thanks in advance.
EDIT:
Variable date is (for example):
SET #date = '2017-03-30'
type can be 0 or 1:
if type = 0 my query is :
select * from table
where type = 0 and start_date <= #date AND end_date>=#date
if type = 1, frequency is a field with an integer number(a interval of days). So I have to check if adding this value to start_date is equals to #date.
if yes, I have to return the current record
if no, I have to iterate this operation
Date current = start_date + interval of 'frequency' days
while(current < end_date){
if(current == #date)
(this is the record I want)
else
current+=frequency
}
The result of query of type 1 can be more than one record. And finally I want to UNION the result of type 0 and 1 in unique select.
Based on a comment/confirmation below the question:
So, to put it in less focussed on the wrong solution terms, you want to determine whether the difference between start_date and #date, in days, is an integer multiple of frequency? –
Looks like you want something like:
select * from table
where
start_date <= #date AND
end_date>=#date AND
(
type = 0 OR
(
type = 1 AND
mod(datediff(#date,start_date),frequency) = 0
)
)
Once we determined the actual requirement, above, and it was clear we just need to find out if one number is a multiple of another, we use mod to compute that. The rest of the structure of the WHERE clause essentially follows the bullet-pointed section of the question.