I am working on a php script, I have an admin control panel to add users, and I need to add a few options like user monthly posts - user daily posts - user hourly posts, let's say I set user monthly post to 30 and user daily posts is 10 and user hourly post is 5, that will be:
The user can post only 5 posts per 1 hour and 10 posts per day from the monthly 30 posts limit, if user monthly post is used, he can't add posts in this month and the next month i want to automatically add another 30 posts!!
My user table name is (user):
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`monthly` int(2) unsigned NOT NULL,
`daily` int(10) unsigned NOT NULL,
`hourly` int(10) unsigned NOT NULL,
And my post table name is user_post:
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`created_dt` datetime NOT NULL,
`user` int(10) unsigned NOT NULL,
I need to know :
how to make the monthly user column updated monthly to 30 if I registered the user with 30 monthly post limit.
when user is logged in and he want to post, how to check if he have more monthly, daily, hourly posts remaining!
Can anyone help me to see how I can do that, thank you my friends
Add a "datetime posted" column to your "user posts" table. For this example, we'll call the new column `created_dt` (with a dataype of DATETIME. We'll also assume that the name of the table is `user_post`.
When a row is inserted, populate the new column with the current date and time.
When a user attempts to post another row, you could perform a check whether any limit has been exceeded.
SELECT SUM(1) AS cnt_past_month
, SUM(p.created_dt >= NOW() + INTERVAL -7 DAY) AS cnt_past_week
, SUM(p.created_dt >= NOW() + INTERVAL -1 HOUR) AS cnt_past_hour
FROM user_post p
WHERE p.user_id = ?
AND p.created_dt >= NOW() - INTERVAL 1 MONTH
You can then compare the values returned to the limits for the user, to see if any limits have been exceeded, or would be exceeded if another post is added.
For optimal performance of this query, you will want an index
ON user_post(userid, created_dt)
You could get the limits for the user within the query itself...
SELECT q.count_past_month
, m.limit_past_month
, q.count_past_week
, m.limit_past_week
, q.count_past_hour
, m.limit_past_hour
FROM ( SELECT p.userid
, SUM(1) AS cnt_past_month
, SUM(p.created_dt >= NOW() - INTERVAL 7 DAY) AS cnt_past_week
, SUM(p.created_dt >= NOW() - INTERVAL 1 HOUR) AS cnt_past_hour
FROM user_post p
WHERE p.user_id = ?
AND p.created_dt >= NOW() - INTERVAL 1 MONTH
) q
CROSS
JOIN ( SELECT MIN(l.limit_per_month)
, MIN(l.limit_per_week)
, MIN(l.limit_per_hour)
FROM user_limit l
WHERE l.user_id = ?
) m
With this approach, you won't need a bunch of unnecessary DML to increment counters, and reset counters. Any change you make to the limits for user would could take effect immediately.
And you could use a value of "0" to specify "no limit". Your logic for doing comparisons would need to take that into account.
That's how I would do it.
I could also do the comparisons of the count to the limit in the query itself, returning the "number of posts remaining" until the limit is exceeded.
SELECT m.limit_past_month-IFNULL(q.count_past_month,0) AS remaining_past_month
, m.limit_past_week -IFNULL(q.count_past_week ,0) AS remaining_past_week
, m.limit_past_hour -IFNULL(q.count_past_hour ,0) AS remaining_past_hour
FROM (
The mechanics of the "ban" (no posts allowed) and "unlimited" (no limits on posts) would need to be worked out. For example using 0 to represent a ban, and a NULL to represent "no limit".
With that, we'd know that when the query returns a column with value less than or equal to zero, it would mean that a limit has been exceeded (or would be exceeded by another post.) All other values (NULL or positive integer) in the column would mean a "next post" would be allowed.
before inserting a posts you can run a similar query like this query to find out your posts for hourly,daily,and monthly and check that in code and decide to allow/not allow user to add posts.
without knowing your schema i just created some possible names for columns from your post table. and of course your userid (1) should be replaced with the userid of the user you're trying to prevent adding posts.
SELECT SUM(CASE WHEN created_at > CURRENT_TIMESTAMP() - INTERVAL 1 HOUR THEN 1
ELSE 0
END) AS hourly_posts,
SUM(CASE WHEN created_at > CURRENT_TIMESTAMP() - INTERVAL 1 DAY THEN 1
ELSE 0
END) AS daily_posts,
SUM(CASE WHEN created_at > CURRENT_TIMESTAMP() - INTERVAL 1 MONTH THEN 1
ELSE 0
END) AS monthly_posts
FROM posts
WHERE userid = 1
I have updated Tin Tran's code:
SELECT SUM(CASE WHEN (created_at > CURRENT_TIMESTAMP() - INTERVAL 1 HOUR) and date_format(CURRENT_TIMESTAMP(),'%H') = date_format(created_at,'%H')) THEN 1
ELSE 0
END) AS hourly_posts,
SUM(CASE WHEN (created_at > CURRENT_TIMESTAMP() - INTERVAL 1 DAY and date_format(CURRENT_TIMESTAMP(),'%D') = date_format(created_at,'%D')) THEN 1
ELSE 0
END) AS daily_posts,
SUM(CASE WHEN (created_at > CURRENT_TIMESTAMP() - INTERVAL 1 MONTH and date_format(CURRENT_TIMESTAMP(),'%m') = date_format(created_at,'%m')) THEN 1
ELSE 0
END) AS monthly_posts
FROM posts
WHERE userid = 1
Related
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;
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)
I have a small self made gallery which im still working on it:
http://springbreak.enteratenorte.com
And the SQL structure is the following:
CREATE TABLE IF NOT EXISTS 'en_albums' (
'id' int(11) NOT NULL auto_increment,
'name' text,
'folder' text,
'description' text,
'hits' int(11) default NULL,
'datecreated' date default NULL,
PRIMARY KEY ('id')
) ENGINE=MyISAM AUTO_INCREMENT=372 DEFAULT CHARSET=latin1 AUTO_INCREMENT=372 ;
And I have around 300 albums, so I would to sort the most popular which is simple with this query:
SELECT * FROM en_albums ORDER BY hits DESC
But I would like to know how can I do the same thing, but only sort album not older than 6 month, or 1 month, or 1 year, this is a sample of the lines in my albums:
INSERT INTO 'en_albums'
('id', 'name', 'folder',
'description', 'hits', 'datecreated')
VALUES
(1, 'Friends Picture', 'en-fotosdeamigos',
'Pictures sent by our visitors', 1514, '2005-07-19');
Thanks for the help! :D
Assuming you only want to get albums created within a time period you can use
SELECT * FROM en_albums WHERE datecreated > DATE_SUB(NOW(),INTERVAL 6 MONTH) ORDER BY hits DESC
This will only return items which have a datecreated greater than 6 months ago. You can change the 6 and MONTH part to be what you want. You can replace MONTH with any of the following:
MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR
There seem to be two answers to you question. #Tim took one answer (exclude albums older than your specified limit). This gives an alternative...
First, list all albums less than 6 months old
(For those albums, list those with the most hits first)
After those albums, cary on the list with all albums over 6 months old
(For those albums, list the most recent album first)
This does just that...
SELECT
*
FROM
en_albums
ORDER BY
CASE WHEN datecreated > DATE_SUB(NOW(), INTERVAL 6 MONTH) THEN 0 ELSE 1 END ASC,
CASE WHEN datecreated > DATE_SUB(NOW(), INTERVAL 6 MONTH) THEN hits ELSE 0 END DESC,
datecreated DESC
Any album less than 6 months old will be sorted by...
0, hits, datecreated
Any album more than 6 months old will be sorted by...
1, 0, datecreated
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
Simplified Table structure:
CREATE TABLE IF NOT EXISTS `hpa` (
`id` bigint(15) NOT NULL auto_increment,
`core` varchar(50) NOT NULL,
`hostname` varchar(50) NOT NULL,
`status` varchar(255) NOT NULL,
`entered_date` int(11) NOT NULL,
`active_date` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `hostname` (`hostname`),
KEY `status` (`status`),
KEY `entered_date` (`entered_date`),
KEY `core` (`core`),
KEY `active_date` (`active_date`)
)
For this, I have the following SQL query which simply totals up all records with the defined status.
SELECT core,COUNT(hostname) AS hostname_count, MAX(active_date) AS last_active
FROM `hpa`
WHERE
status != 'OK' AND status != 'Repaired'
GROUP BY core
ORDER BY core
This query has been simplified to remove the INNER JOINS to unrelated data and extra columns that shouldn't affect the question.
MAX(active_date) is the same for all records of a particular day, and should always select the most recent day, or allow an offset from NOW(). (it's a UNIXTIME field)
I want both the count of: (status != 'OK' AND status != 'Repaired')
AND the inverse... count of: (status = 'OK' OR status = 'Repaired')
AND the first answer divided by the second, for 'percentage_dead' (Probably just as fast to do in post processing)
FOR the most recent day or an offset ( - 86400 for yesterday, etc..)
Table contains about 500k records and grows by about 5000 a day so a single SQL query as opposed to looping would be real nice..
I imagine some creative IF's could do this. You expertise is appreciated.
EDIT: I'm open to using a different SQL query for either todays data, or data from an offset.
EDIT: Query works, is fast enough, but I currently can't let the users sort on the percentage column (the one derived from bad and good counts). This is not a show stopper, but I allow them to sort on everything else. The ORDER BY of this:
SELECT h1.core, MAX(h1.entered_date) AS last_active,
SUM(CASE WHEN h1.status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS good_host_count,
SUM(CASE WHEN h1.status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS bad_host_count
FROM `hpa` h1
LEFT OUTER JOIN `hpa` h2 ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date)
WHERE h2.hostname IS NULL
GROUP BY h1.core
ORDER BY ( bad_host_count / ( bad_host_count + good_host_count ) ) DESC,h1.core
Gives me:
#1247 - Reference 'bad_host_count' not supported (reference to group function)
EDIT: Solved for a different section. The following works and allows me to ORDER BY percentage_dead
SELECT c.core, c.last_active,
SUM(CASE WHEN d.dead = 1 THEN 0 ELSE 1 END) AS good_host_count,
SUM(CASE WHEN d.dead = 1 THEN 1 ELSE 0 END) AS bad_host_count,
( SUM(CASE WHEN d.dead = 1 THEN 1 ELSE 0 END) * 100/
( (SUM(CASE WHEN d.dead = 1 THEN 0 ELSE 1 END) )+(SUM(CASE WHEN d.dead = 1 THEN 1 ELSE 0 END) ) ) ) AS percentage_dead
FROM `agent_cores` c
LEFT JOIN `dead_agents` d ON c.core = d.core
WHERE d.active = 1
GROUP BY c.core
ORDER BY percentage_dead
If I understand, you want to get a count of the status of OK vs. not OK hostnames, on the date of the last activity. Right? And then that should be grouped by core.
SELECT core, MAX(active_date)
SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS OK_host_count,
SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS broken_host_count
FROM `hpa` h1 LEFT OUTER JOIN `hpa` h2
ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date)
WHERE h2.hostname IS NULL
GROUP BY core
ORDER BY core;
This is a variation of the "greatest-n-per-group" problem that I see a lot in SQL questions on StackOverflow.
First want to choose only the rows that have the latest activity date per hostname, which we can do by doing an outer join for rows with the same hostname and a greater active_date. Where we find no such match, we already have the latest rows for each given hostname.
Then group by core and count the rows by status.
That's the solution for today's date (assuming no row has an active_date in the future). To restrict the result to rows N days ago, you have to restrict both tables.
SELECT core, MAX(active_date)
SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS OK_host_count,
SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS broken_host_count
FROM `hpa` h1 LEFT OUTER JOIN `hpa` h2
ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date
AND h2.active_date <= CURDATE() - INTERVAL 1 DAY)
WHERE h1.active_date <= CURDATE() - INTERVAL 1 DAY AND h2.hostname IS NULL
GROUP BY core
ORDER BY core;
Regarding the ratio between OK and broken hostnames, I'd recommend just calculating that in your PHP code. SQL doesn't allow you to reference column aliases in other select-list expressions, so you'd have to wrap the above as a subquery and that's more complex than it's worth in this case.
I forgot you said you're using a UNIX timestamp. Do something like this:
SELECT core, MAX(active_date)
SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS OK_host_count,
SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS broken_host_count
FROM `hpa` h1 LEFT OUTER JOIN `hpa` h2
ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date
AND h2.active_date <= UNIX_TIMESTAMP() - 86400)
WHERE h1.active_date <= UNIX_TIMESTAMP() - 86400 AND h2.hostname IS NULL
GROUP BY core
ORDER BY core;