Timetable Stats PHP Page - php

I have a field called dPostTime, which has the date and time stored of an entry.
If I wanted to create a page that showed me the number of entries per hour, would I need to have 24 queries, one for each hour, or what's the best way to do this.
I'd like the chart to appear as follows:
5-6pm - 24 posts
6-7pm - 56 posts
7-8pm - 34 posts
8-9pm - 35 posts
etc......

MySQL doesn't have recursive functionality, so you're left with using the NUMBERS table trick -
Create a table that only holds incrementing numbers - easy to do using an auto_increment:
DROP TABLE IF EXISTS `example`.`numbers`;
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Populate the table using:
INSERT INTO NUMBERS
(id)
VALUES
(NULL)
...for as many values as you need.
Use DATE_ADD to construct a list of dates, increasing the days based on the NUMBERS.id value. Replace "2010-01-01" and "2010-01-02" with your respective start and end dates (but use the same format, YYYY-MM-DD HH:MM:SS) -
SELECT x.dt
FROM (SELECT DATE_FORMAT(DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR), '%H:%i') AS dt
FROM numbers n
WHERE DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR) <= '2010-01-02' ) x
LEFT JOIN onto your table of data based on the datetime portion.
SELECT x.dt,
COALESCE(COUNT(a.dPostTime), 0) AS numPerHour
FROM (SELECT DATE_FORMAT(DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR), '%H') AS dt
FROM numbers n
WHERE DATE_ADD('2010-01-01', INTERVAL (n.id - 1) HOUR) <= '2010-01-02' ) x x
LEFT JOIN YOUR_TABLE a ON DATE_FORMAT(a.dPostTime, '%H') = x.dt
GROUP BY x.dt
ORDER BY x.dt
Why Numbers, not Dates?
Simple - dates can be generated based on the number, like in the example I provided. It also means using a single table, vs say one per data type.

SELECT COUNT(*),
HOUR(`dPostTime`) AS `hr`
FROM `table`
GROUP BY `hr`
After that in php format hr to be equal 'hr - 1' - hr

Related

check dates on database

On my database table I have 2 columns, start_date and end_date.
Sample data would be:
-------------------------------
start_date | end_date
-------------------------------
2017-11-01 2017-11-02
2017-11-03 2017-11-07
2017-11-20 2017-11-28
2017-11-13 2017-12-02
-------------------------------
I need to find if there are 5 consecutive days that are not yet used, which in this case, there is:
(2017-11-08 to 2017-11-13)
I'm using PHP and MySQL.
Thanks in advance!
You'd need to check for edge cases depending on your actual data and if there were no overlap dates, but this is a good start for the provided data.
Assuming table and data as defined as below:
CREATE TABLE
`appointments`
(
`appointment_id` INT PRIMARY KEY AUTO_INCREMENT,
`start_date` DATE,
`end_date` DATE
);
INSERT INTO
`appointments`
(`start_date`, `end_date`)
VALUES
('2017-11-01', '2017-11-02'),
('2017-11-03', '2017-11-07'),
('2017-11-20', '2017-11-28'),
('2017-11-13', '2017-12-02');
If you order the rows, and take the lag from the end date before it, and take any gaps of 5 or more. In SQL Server there are LAG functions, but here's a way of doing the same. Then once you have a table of all rows and their corresponding gaps, you take the start date of that period, and create the gap period from the number of days between. Since TIMESTAMPDIFF is inclusive, you need to subtract a day.
SET #end_date = NULL;
SELECT
DATE_ADD(`start_date`, INTERVAL -(`gap_from_last`-1) DAY) AS `start_date`,
`start_date` AS `end_date`
FROM
(
SELECT
`appointment_id`,
CASE
WHEN #end_date IS NULL THEN NULL
ELSE TIMESTAMPDIFF(DAY, #end_date, `start_date`)
END AS `gap_from_last`,
`start_date`,
#end_date := `end_date` AS `end_date` -- Save the lag date from the row before
FROM
`appointments`
ORDER BY
`start_date`,
`end_date`
) AS `date_gap` -- Build table that has the dates and the number of days between
WHERE
`gap_from_last` > 5;
Provides:
start_date | end_date
------------------------
2017-11-08 | 2017-11-13
Edit: Oops! Forgot the SQLFiddle (http://sqlfiddle.com/#!9/09cfce/16)
SELECT x.end_date + INTERVAL 1 DAY unused_start
, MIN(y.start_date) unused_end
FROM appointments x
JOIN appointments y
ON y.start_date >= x.end_date
GROUP
BY x.start_date
HAVING DATEDIFF(MIN(y.start_date),unused_start) >= 5;

postgresql max(count(*)) - php

I have a problem in postgresql.
I have one cohorte (gathering of people) and i would like counting the persons in this cohorte.
Begin date : "2014-09-01", End date : "2014-11-30".
I have 5 persons between 09/01 and 09/22
I have 5 persons between 09/20 and 09/25
I have 5 persons between 09/26 and 10/05
I have 5 persons between 10/01 ans 11/30
I want to have the max of accommodation for each month between the begin date and the end date in SQL (or PHP). Expected max person count:
September(09) => 10
October(10) => 10
November(11) => 5
Find the maximum of simultaneously present persons on a single day for every month in a given period.
I suggest generate_series() to produce the series of days in your period. Then aggregate twice:
First to get a count for each day. A single day can be dealt with plain BETWEEN. Your ranges are obviously meant to be with include borders.
Second to get the maximum per month.
SELECT date_trunc('month', day)::date AS month, max(ct) AS max_ct
FROM (
SELECT g.day, count(*) AS ct
FROM cohorte
,generate_series('2014-09-01'::date -- first of Sept.
,'2014-11-30'::date -- last of Nov.
,'1 day'::interval) g(day)
WHERE g.day BETWEEN t_begin AND t_end
GROUP BY 1
) sub
GROUP BY 1
ORDER BY 1;
Returns:
month | max_ct
-----------+--------
2014-09-01 | 10
2014-10-01 | 10
2014-11-01 | 5
Use to_char() to prettify the month output.
SQL Fiddle .. is down ATM. Here is my test case (that you should have provided):
CREATE TEMP TABLE cohorte (
cohorte_id serial PRIMARY KEY
,person_id int NOT NULL
,t_begin date NOT NULL -- inclusive
,t_end date NOT NULL -- inclusive
);
INSERT INTO cohorte(person_id, t_begin, t_end)
SELECT g, '2014-09-01'::date, '2014-09-22'::date
FROM generate_series (1,5) g
UNION ALL
SELECT g+5, '2014-09-20', '2014-09-25'
FROM generate_series (1,5) g
UNION ALL
SELECT g+10, '2014-09-26', '2014-10-05'
FROM generate_series (1,5) g
UNION ALL
SELECT g+15, '2014-10-01', '2014-11-30'
FROM generate_series (1,5) g;
For more complex checks I'd suggest the OVERLAPS operator:
Find overlapping date ranges in PostgreSQL
For more complex scenarios I'd also consider range types:
Preventing adjacent/overlapping entries with EXCLUDE in PostgreSQL
can't you use window function?
I'd try something like this (I've not tested this code, just exposed my thoughts)
SELECT max(count) FROM (
SELECT count(*) OVER (PARTITION BY ???) as count
FROM contract
WHERE daterange(dateStart, dateEnd, '[]') && daterange('2014-09-01', '2014-10-01', '[)')
) as max
Here, my problem remains that I can't find a way to partition for each day of the interval. Maybe this is a wrong approach, but I would be interested by a solution based on windows.
edit: with this request, you have the max of simultaneous present, but over all the time, not only a given month
with presence as (
SELECT id, generate_series(begin_date, end_date, '1 day'::interval) AS date
FROM test
),
presents as (
SELECT count(*) OVER (PARTITION BY date) AS count
FROM presence
)
SELECT max(count) from presents;
Here we come, I think
Imagine your person table has 3 columnsĀ :
id
entrance_date
leaving_date
the request would look like
WITH presents as (
SELECT id,
daterange(entrance_date, leaving_date, '[]') * daterange('2014-09-01', '2014-11-30', '[]') as range
FROM person
WHERE daterange(entrance_date, leaving_date, '[]') && daterange('2014-09-01', '2014-11-30', '[]')
),
present_per_day as (
SELECT id,
generate_series(lower(range), upper(range), '1 day'::interval) AS date
FROM presents
),
count_per_day as (
SELECT count(*) OVER (PARTITION BY date) AS count,
date
FROM present_per_day
),
SELECT max(count) OVER (PARTITION BY date_part('year', date), date_part('month', date)) as max,
date_part('year', date),
date_part('month', date)
FROM count_per_day;
(I have to leave, I hope I'll have time to test it later)
In fact, #erwin solution is much much more easy and efficient than this one.

Creating hour groups for time series data MySQL

I have a MySQL database with data recorded every 15 minutes. For simplicity, lets assume there are 2 fields:
DATETIME Created
Double Value
I would like to draw a chart which needs for each hour the opening, min, max, and closing values for an hour. To do this I need to return results from my MySQL query to my PHP to create a JSON. I would like to do this in the MySQL query so that the response is cached.
Here is an example of the problem, given 9 data points trying to get 2 hour groups:
Creation Value
2014-03-25 12:15:00 413.17011
2014-03-25 12:00:00 414
2014-03-25 11:45:00 415
2014-03-25 11:30:00 415
2014-03-25 11:15:00 415.5
2014-03-25 11:00:00 415.5
2014-03-25 10:45:00 416
2014-03-25 10:30:00 416
2014-03-25 10:15:00 415.99
I would need:
Hour 1 (11:15:00 to 12:15:00)
Open: 415.5
Close: 413.17011
High: 415.5
Low: 413.17011
Hour 2 (10:15:00 to 11:15:00)
Open: 415.99
Close: 415.5
High: 416
Low: 415.5
Of course for the full 24 hours this would need repeating, this is just an example.
Any help is really appreciated!
Here is the current MySQL dump for the example (Using MySQL version 2.6.4-pl3):
--
-- Table structure for table `exampleTable`
--
CREATE TABLE `exampleTable` (
`created` datetime NOT NULL,
`value` double NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
--
-- Dumping data for table `exampleTable`
--
INSERT INTO `exampleTable` VALUES ('2014-03-25 12:15:00', 413.17011);
INSERT INTO `exampleTable` VALUES ('2014-03-25 12:00:00', 414);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:45:00', 415);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:30:00', 415);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:15:00', 415.5);
INSERT INTO `exampleTable` VALUES ('2014-03-25 11:00:00', 415.5);
INSERT INTO `exampleTable` VALUES ('2014-03-25 10:45:00', 416);
INSERT INTO `exampleTable` VALUES ('2014-03-25 10:30:00', 416);
INSERT INTO `exampleTable` VALUES ('2014-03-25 10:15:00', 415.99);
Get it to work
You might try
SELECT
DATE(created) AS day,
HOUR(created) AS hour,
(
SELECT Value FROM `table` AS b
WHERE DATE(a.created) = DATE(b.created)
AND HOUR(a.created) = HOUR(b.created)
ORDER BY created ASC LIMIT 1
) AS Open,
(
SELECT Value FROM `table` AS b
WHERE DATE(a.created) = DATE(b.created)
AND HOUR(a.created) = HOUR(b.created)
ORDER BY created DESC LIMIT 1
) AS Close,
MIN(value) AS Low,
MAX(value) AS High
FROM `table` AS a
GROUP BY DATE(created), HOUR(created)
this groups all your rows by DATE+HOUR and computes the MIN respectively MAX as Low or High. To find the first and last row for Open and Close, the easiest in SQL syntax is a subselect. It selects all rows which are relevant for the current row, and sorts them ascending or descending. Then selects the first row.
Please consider that this groups only by hour. Instead of
Hour 1 (11:15:00 to 12:15:00)
Hour 2 (10:15:00 to 11:15:00)
this groups like
Hour 1 (11:00:00 to 11:59:00)
Hour 2 (10:00:00 to 10:59:00)
If you want to keep the 15 minutes offset, you may subtract this from your created timestamp (created - INTERVAL 15 MINUTE) at all occurrences of created in the sql query above.
I created a working sqlfiddle for you.
Performance
Just as hint: If you can, you might want to split date and time into two columns (of types date and time). This way you do not need to cast DATE() on created everytime, but can use the new date column instead. You can then add a combined index to this new columns too, which speeds up your query. See this sqlfiddle for an example.
To get your grouping right, you can use
FLOOR(( UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600)
where 3600 sets the interval at 1 hour and the - 900 sets the offset at 00:15
Since you need the MIN() and MAX for each of your four values, you'll need to JOIN the main table to itself but grouped by the min or max (based on the column).
finally, you have each sub-query (joined table) calculate the grouping hour above so you can use that to join them. Here's what I cam up with (with slightly different column names and
SELECT openDate,Open,Close,High,Low
FROM (SELECT FLOOR(( UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600)
AS
theHour,
myTable.value AS Open,myTable.dateCreated openDate
FROM myTable
JOIN (SELECT value,MIN(dateCreated) AS dateCreated
FROM myTable
GROUP BY FLOOR(( UNIX_TIMESTAMP(dateCreated) - 900 )
/ 3600)
) AS
aggTable
ON aggTable.dateCreated = myTable.dateCreated) AS
openTable
LEFT JOIN (SELECT FLOOR(( UNIX_TIMESTAMP(myTable.dateCreated) - 900
) /
3600) AS
theHour
,
myTable.value AS Close,myTable.dateCreated closeDate
FROM myTable
JOIN (SELECT value,MAX(dateCreated) AS dateCreated
FROM myTable
GROUP BY FLOOR(( UNIX_TIMESTAMP(dateCreated) - 900 ) / 3600)
) AS
aggTable
ON aggTable.dateCreated = myTable.dateCreated) AS closeTable
ON openTable.theHour = closeTable.theHour
LEFT JOIN (SELECT
FLOOR((
UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600) AS
theHour,
MAX(
value)
AS High
FROM myTable
GROUP BY theHour) AS highTable
ON closeTable.theHour = highTable.theHour
LEFT JOIN (SELECT
FLOOR((
UNIX_TIMESTAMP(myTable.dateCreated) - 900 ) / 3600) AS
theHour,
MIN(
value)
AS Low
FROM myTable
GROUP BY theHour) AS lowTable
ON highTable.theHour = lowTable.theHour

MySQL: Count no of consecutive week days from datetime column

I have the below table:
studentid VARCHAR(12)
latetime DATETIME
attendance CHAR(1)
latetime only have weekdays.
Some of the days the students will have "Parents letter" indicated by V for attendance column.
I need to group these attendance column V by consecutive week days.
Then count these occurrences.
Each group of consecutive days are counted as 1 letter.
My SQLFIDDLE: http://sqlfiddle.com/#!2/55d5b/1
This SQLFIDDLE sample data should return
STUDENTID LETTERCOUNT
a1111 3
b2222 2
a1111 - 3 counts
-----
1. 2014-01-02
2. 2014-01-27
2. 2014-01-29 and 2014-01-30
b2222 - 2 counts
-----
1. 2014-01-02 and 2014-01-03
2. 2014-01-24, 2014-01-27 and 2014-01-28
I tried various methods from the below SO without any proper result yet:
How to GROUP BY consecutive data (date in this case)
MySQL: group by consecutive days and count groups
I can do this programatically in PHP by looping through the results and manually checking for each record + its next date. But i was trying to acheive the same with SQL.
Any help / direction towards finding a solution will be much appreciated.
This is derived from one of the answers in MySQL: group by consecutive days and count groups. I added the WITH ROLLUP option to get the letter count into the same query, and used GROUP_CONCAT to show all the dates. I made the INTERVAL conditional on the weekday, to skip over weekends; holidays aren't taken into account, though.
In my version of the fiddle I changed the latetime column to date, so I could remove all the DATE() functions from the SQL.
SELECT studentid, IFNULL(dates, '') dates, IF(dates IS NULL, lettercount, '') lettercount
FROM (
SELECT studentid, dates, COUNT(*) lettercount
FROM (
SELECT v.studentid,
GROUP_CONCAT(latetime ORDER BY latetime SEPARATOR ', ') dates
FROM
(SELECT studentid, latetime,
#start_date := IF(#last_student IS NULL OR #last_student <> studentid,
1,
IF(#last_latetime IS NULL
OR (latetime - INTERVAL IF(WEEKDAY(latetime) = 0, 3, 1) DAY) > #last_latetime, latetime, #start_date)) AS start_date,
#last_latetime := latetime,
#last_student := studentid
FROM
studentattendance, (SELECT #start_date := NULL, #last_latetime := NULL, #last_student := NULL) vars
WHERE attendance = 'V'
ORDER BY
studentid, latetime
) v
GROUP BY
v.studentid, start_date) x
GROUP BY studentid, dates WITH ROLLUP) y
WHERE studentid IS NOT NULL
ORDER BY studentid, dates
http://sqlfiddle.com/#!2/6c944/12

How would one delete any SQL row that is older than x amount of days while (column)Rank is x?

I have a rank system set up, where 5 is admin,4 lifetime donor, 3 is donor, and 2 is advertiser, 1 is user.
I cant figure out how to flush all rows that are:
-one week old if they are user (rank:1)
-two weeks old if they are advertiser (rank:2)
-2 months old if they are donor (rank:3)
While leaving all admins and lifetime donors in the table.
Any help would be greatly appreciated.
Something like this would do the trick:
DELETE
FROM Test
WHERE (TestDate < NOW() - INTERVAL 1 WEEK AND Rank = 1)
OR (TestDate < NOW() - INTERVAL 2 WEEK AND Rank = 2)
OR (TestDate < NOW() - INTERVAL 2 MONTH AND Rank = 3)
havent tested yet. Before running this query to test that it deletes correct data instead of DELETE put SELECT and see which rows come up and then run this query.
SELECT * FROM Test
WHERE (TestDate < NOW() - INTERVAL 1 WEEK AND Rank=1)
OR (TestDate < NOW() - INTERVAL 2 WEEK AND Rank = 2)
OR (TestDate < NOW() - INTERVAL 2 MONTH AND Rank = 3)
I would probably use three separate queries to query the database and delete the rows.
The first query - for rank 1 (User):
$query = "DELETE FROM table WHERE rank=1 AND time > UNIX_TIMESTAMP()-604800";
The second query - for rank 2 (Advertiser):
$query = "DELETE FROM table WHERE rank=2 AND time > UNIX_TIMESTAMP()-1209600";
The third query - for rank 3 (Donor):
$query = "DELETE FROM table WHERE rank=3 AND time > UNIX_TIMESTAMP()-2592000";
I hope that helped.
you need to place a column in the table as value datetime and use following wuery to delete the record based on condition
CREATE TABLE IF NOT EXISTS `test` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`rank` int(10) NOT NULL,
`timestamp` datetime NOT NULL,
PRIMARY KEY (`id`)
)
DElETE
FROM test
WHERE (week( `timestamp` ) >= 1 AND Rank = 1)
OR (week( `timestamp` ) >= 2 AND Rank = 2)
OR (week( `timestamp` ) >= 4 AND Rank = 3)

Categories