I have this table:
// cookies
+---------+-------------------------+------------------+------------+
| id | email | cookie | date_time |
+---------+-------------------------+------------------+------------+
| int(11) | varchar(50) | varchar(128) | int(11) |
+---------+-------------------------+------------------+------------+
| 1 | jack_2009#gmail.com | ojer0f934mf2... | 1467204523 |
| 2 | peter.zm#yahoo.com | ko4398f43043... | 1467205521 |
| 3 | matrix_john23#gmail.com | 34fjkg3j438t... | 1467205601 |
| 4 | peter.zm#yahoo.com | 0243hfd348i4... | 1467206039 |
+---------+-------------------------+------------------+------------+
And here is my query:
INSERT INTO cookies VALUES(NULL, $email, $hash, unix_timestamp())
Now I need to check following condition before inserting:
The number of rows (for specific user) should be less than:
5 per hour
10 per day
50 per month
100 per total
I just can check the last case:
INSERT INTO cookies(id, email, cookie, date_time)
SELECT NULL, $email, $hash, unix_timestamp()
FROM cookie
WHERE email = $email AND
100 >= ( SELECT count(1) FROM cookies WHERE email = $email )
Well, how can I add other conditions?
I'm not positive on whether the >'s (in the group by) should be >='s, but I think this will do what you are asking.
INSERT INTO cookies(id, email, cookie, date_time)
SELECT NULL, $email, $hash, unix_timestamp()
FROM cookie
WHERE email = $email
AND NOT EXISTS (
SELECT COUNT(CASE WHEN date_time > UNIX_TIMESTAMP(now() - INTERVAL 1 HOUR)
THEN 1 ELSE NULL END) AS rowsInLastHour
, COUNT(CASE WHEN date_time > UNIX_TIMESTAMP(now() - INTERVAL 1 DAY)
THEN 1 ELSE NULL END) AS rowsInLastDay
, COUNT(CASE WHEN date_time > UNIX_TIMESTAMP(now() - INTERVAL 1 MONTH)
THEN 1 ELSE NULL END) AS rowsInLastMonth
, COUNT(1) AS rowsEver
FROM cookie
WHERE email = $email
HAVING rowsInLastHour > 5
OR rowsInLastDay > 10
OR rowsInLastMonth > 50
OR rowsEver > 100
)
;
It counts all the rows (for the email) that had date_time values in the last hour|day|month by using now() - INTERVAL 1 HOUR|DAY|MONTH to find when the last hour|day|month started, and counting those values that occurred after those starting times.
It then uses the HAVING to only yield the singular result (aggregation such as COUNT that does not have an associated GROUP BY clause always results in 1 row), if any of the limits you specified were exceeded.
Then the NOT EXISTS returns true if there were no results (because the limits were not exceeded).
Edit: Updated comparisons to use unit timestamps, as needed by question.
You can use stored procedure and within that you can handle this. Pass your insert values to this stored procedure
DELIMITER $$
CREATE PROCEDURE `sp_test`(id int, email varchar(45), cookie varchar(45), date_time datetime)
BEGIN
DECLARE countval INT;
SET countval = (SELECT sum(1) FROM cookies WHERE email = $email );
IF (countval is null) THEN
// do something
ELSEIF (countval>10) THEN
// do something like that
ELSE
// do something
END IF;
// insert query
END $$
DELIMITER ;
Related
I have a table of questions in mysql with 5 different types of questions like follow :
table decouverte :
id | question | answer | type | date
----------------------------------------------------
1 | txt question1 | theAnswer1 | easy | null
2 | txt question2 | theAnswer2 | normal | null
3 | txt question3 | theAnswer3 | difficult | null
4 | txt question4 | theAnswer4 | hard | null
5 | txt question5 | theAnswer5 | easy | null
.
.
I would like to get everyday at midnight one random question of each type where the date is either null or current date.
For the moment I have this :
$stmt = $this->db->prepare("CREATE EVENT get_5_dailyQuestions
ON SCHEDULE EVERY 1 DAY STARTS (TIMESTAMP(CURRENT_DATE) + INTERVAL 1 DAY + INTERVAL 1 HOUR)
DO SELECT * from decouverte where type = 'easy' AND (date is NULL OR date = CURDATE()) ORDER BY RAND() LIMIT 1 UNION
SELECT * from decouverte where type = 'normal' AND (date is NULL OR date = CURDATE()) ORDER BY RAND() LIMIT 1 UNION
SELECT * from decouverte where type = 'difficult' AND (date is NULL OR date = CURDATE()) ORDER BY RAND() LIMIT 1 UNION
SELECT * from decouverte where type = 'hard' AND (date is NULL OR date = CURDATE()) ORDER BY RAND() LIMIT 1");
The create event works, but not the SELECT thing.
Can anyone help me ? Thank you in advance
Personally, I would just create a result table to store the randomly generated data from the base table . Then we can query the result table. This is of course done in an event on a daily basis. Here is code written and tested in workbench. (I set the interval to 1 minute ) By the way, I made some adjustment to your query. Instead of 4 separate SELECT statements, we can accomplish the job in one.
set ##global.event_scheduler=on;
create table chosen_result like decouverte;
delimiter //
CREATE EVENT get_5_dailyQuestions
ON SCHEDULE EVERY 1 minute STARTS now() DO
BEGIN
truncate chosen_result;
insert chosen_result
select * from decouverte t1
join
(select (select id from decouverte where type=d.type order by rand() limit 1) as id
from decouverte d
where date is null or date=sysdate()
group by type
) t2
using(id);
END//
delimiter ;
I'm trying to improve SQL for our SaaS which supports user cash balance. When user makes an order, the order price should be subtracted from his balance.
The subtraction must only be done if the user has enough balance. If the subtraction is done, the query should return the NEW balance, or NULL if failed to update because not enough balance.
For now I achieved this query:
update usr_account_balance
set balance = #new_balance := balance - AMOUNT_TO_SUBTRACT,
last_updated = UTC_TIMESTAMP()
where user_id = 221
and balance >= AMOUNT_TO_SUBTRACT;
select #new_balance;
The problem with this code is that it will return the balance regardless if it was updated or not. But I'd like to return NULL if nothing was updated in previous query, so I can warn user that his order couldn't proceed because not enough balance available.
EDIT 1:
The SaaS is running on PHP, wordpress backend.
The query should gove you the wanted result
CREATE TABLE usr_account_balance (user_id int,balance DECIMAL (10,2),last_updated timestamp )
INSERT INTO usr_account_balance VALUES(221, 8.0,now())
update usr_account_balance
set balance = IF(balance >= 10,#new_balance := balance - 10, #new_balance := balance),
last_updated = UTC_TIMESTAMP()
where user_id = 221
and balance >= 10;
select #new_balance;
| #new_balance |
| :----------- |
| null |
update usr_account_balance
set balance = IF(balance >= 7,#new_balance := balance - 7, #new_balance := balance),
last_updated = UTC_TIMESTAMP()
where user_id = 221
and balance >= 7;
select #new_balance;
| #new_balance |
| -----------: |
| 1.00 |
db<>fiddle here
I got a table that is being updated every 5 Minutes by a Script Call, triggered by a http request. The table looks like this
MariaDB > select host_id, ping_eu, disk_usage,insert_time from sequencial_host;
+---------+---------+-------------+---------------------+
| host_id | ping | disk_usage | insert_time |
+---------+---------+-------------+---------------------+
| 1 | 35.60 | 10329416704 | 2016-01-20 20:47:51 |
| 2 | 36.57 | 2902848512 | 2016-01-20 20:48:06 |
+---------+---------+-------------+---------------------+
I want to prevent a mess in case the script runs amok or any malicious intent by calling a stored procedure that allows inserting if the last insert_time was at least 4,5 Minutes ago. (30 second script built in time out, so more a less accurate).
I built this Procedure:
CREATE PROCEDURE `update_table`(
IN host_id_a int(11),
IN insert_time timestamp,
IN disk_usage bigint(20) ,
IN ping decimal(10,2)
BEGIN
DECLARE count INT;
SELECT count(insert_time) INTO count FROM db.sequencial_host WHERE insert_time > (NOW() - INTERVAL 270 SECOND) AND host_id = host_id_a ORDER BY insert_time DESC LIMIT 1;
IF count < 1 THEN
INSERT INTO db.sequencial_host ( `host_id`,`insert_time`,`disk_usage`,`ping`)
VALUES( host_id_a, CURRENT_TIMESTAMP, disk_usage, ping);
END IF;
END
If I leave away the If clause, the Insert Statements work by calling the procedure and the Select Query also works for itself if executed in the shell.
I am using MariaDB 5.5.46-1ubuntu0.14.04.2.
I nearly tried any combination of IF count = 0, IF count IS NULL, IF (SELECT ..) IS NULL.
Could anybody take look?
With kind regards Joey
EDIT:
MariaDB [tempdb]> SELECT count(*) FROM tempdb.sequencial_host WHERE insert_time > (NOW() - INTERVAL 270 SECOND) AND host_id = 1 ORDER BY insert_time DESC LIMIT 1;
+--------------------+
| count(insert_time) |
+--------------------+
| 0 |
+--------------------+
1 row in set (0.01 sec)
MariaDB [tempdb]> SELECT count(*) FROM tempdb.sequencial_host WHERE insert_time > (NOW() - INTERVAL 2 HOUR) AND host_id = 1 ORDER BY insert_time DESC LIMIT 1;
+--------------------+
| count(insert_time) |
+--------------------+
| 1 |
+--------------------+
Edit 2:
Pastebin containing all relevant database information in complete. pastebin.com/99eEDLy4
Edit 3:
If I change it to cnt = 1 I can spam the query and it fills everything, but only if there is data in the table. With a freshly truncated one I need to set it to 0 to get values in there.
count cant never < 0 . count is everytime a positive number between 0 and n. So
IF count < 0 THEN
never works
change it to
IF count > 0 THEN
Count on a column that is or has null, will only return count of those values which are not null. If all column values are null then null is returned.
In this case your where clause is ensuring no records return thus all column values for insert_Time will be null; thus null is returned. However, if you had multiple records without null insert_time; then the count would represent those records without a null insert_time.
I think you want count(*) instead of count(insert_time) to ensure 0 is returned when no records are found.
CREATE PROCEDURE `update_table`(
IN host_id_a int(11),
IN insert_time timestamp,
IN disk_usage bigint(20) ,
IN ping decimal(10,2))
BEGIN
DECLARE count INT;
SELECT count(*) INTO count FROM db.sequencial_host WHERE insert_time > (NOW() - INTERVAL 270 SECOND) AND host_id = host_id_a ORDER BY insert_time DESC LIMIT 1;
IF count < 1 THEN
INSERT INTO db.sequencial_host ( `host_id`,`insert_time`,`disk_usage`,`ping`)
VALUES( host_id_a, CURRENT_TIMESTAMP, disk_usage, ping);
END IF;
END
It's not a bug, it's a feature.
Inside of a Stored Procedure Select Exists returns 1 when there is data in the table and null if there is no, when the query is written like that (without specific table set in the where clause):
SELECT EXISTS (SELECT * INTO count FROM db.sequencial_host WHERE insert_time > (NOW() - INTERVAL 270 SECOND) AND host_id = host_id_a ORDER BY insert_time DESC LIMIT 1);
If you append the table to the host id it works as expected. Even counts return the expected value. SELECT EXISTS (SELECT * INTO count FROM db.sequencial_host WHERE insert_time > (NOW() - INTERVAL 270 SECOND) AND sequencial_host.host_id = host_id_a ORDER BY insert_time DESC LIMIT 1);
Background before we begin...
Table schema:
UserId | ActivityDate | Time_diff
where "ActivityDate" is timestamp of activity by user
"Time_diff" is timestampdiff between the next activity and current activity in seconds
in general, but for the last recorded activity of user, since there is no next activity I set the Time_diff to -999
Ex:
UserId | ActivityDate | Time_diff
| 1 | 2012-11-10 11:19:04 | 12 |
| 1 | 2012-11-10 11:19:16 | 11 |
| 1 | 2012-11-10 11:19:27 | 3 |
| 1 | 2012-11-10 11:19:30 | 236774 |
| 1 | 2012-11-13 05:05:44 | 39 |
| 1 | 2012-11-13 05:06:23 | 77342 |
| 1 | 2012-11-14 02:35:25 | 585888 |
| 1 | 2012-11-20 21:20:13 | 1506130 |
...
| 1 | 2013-06-13 06:32:48 | 1616134 |
| 1 | 2013-07-01 23:28:22 | 5778459 |
| 1 | 2013-09-06 20:36:01 | -999 |
| 2 | 2008-08-01 04:59:33 | 622 |
| 2 | 2008-08-01 05:09:55 | 38225 |
| 2 | 2008-08-01 15:47:00 | 31108 |
| 2 | 2008-08-02 00:25:28 | 28599 |
| 2 | 2008-08-02 08:22:07 | 163789 |
| 2 | 2008-08-04 05:51:56 | 1522915 |
| 2 | 2008-08-21 20:53:51 | 694678 |
| 2 | 2008-08-29 21:51:49 | 2945291 |
| 2 | 2008-10-03 00:00:00 | 172800 |
| 2 | 2008-10-05 00:00:00 | 776768 |
| 2 | 2008-10-13 23:46:08 | 3742999 |
I have just added the field "session_id"
alter table so_time_diff add column session_id int(11) not null;
My actual question...
I would like to update this field for each of the above records based on the following logic:
for first record: set session_id = 1
from second record:
if previous_record.UserId == this_record.UserId AND previous_record.time_diff <=3600
set this_record.session_id = previous_record.session_id
else if previous_record.UserId == this_record.UserId AND previous_record.time_diff >3600
set this_record.session_id = previous_record.session_id + 1
else if previous_record.UserId <> this_record.UserId
set session_id = 1 ## for a different user, restart
In simple words,
if two records of the same user are within a time_interval of 3600 seconds, assign the same sessionid, if not increment the sessionid, if its a different user, restart the sessionid count.
I've never written logic in an update query before. Is this possible? Any guidance is greatly appreciated!
Yes, this is possible. It would be easier if the time_diff was on the later record, rather than the previous record, but we can make it work. (We don't really need the stored time_diff.)
The "trick" to getting this to work is really writing a SELECT statement. If you've got a SELECT statement that returns the key of the row to be updated, and the values to be assigned, making that into an UPDATE is trivial.
The "trick" to getting a SELECT statement is to make use of MySQL user variables, and is dependent on non-guaranteed behavior of MySQL.
This is the skeleton of the statement:
SELECT #prev_userid AS prev_userid
, #prev_activitydate AS prev_activitydate
, #sessionid AS sessionid
, #prev_userid := t.userid AS userid
, #prev_activitydate := t.activitydate AS activitydate
FROM (SELECT #prev_userid := NULL, #prev_activitydate := NULL, #sessionid := 1) i
JOIN so_time_diff t
ORDER BY t.userid, t.activitydate
(We hope there's an index ON mytable (userid, activitydate), so the query can be satisfied from the index, without a need for an expensive "Using filesort" operation.)
Let's unpack that a bit. Firstly, the three MySQL user variables get initialized by the inline view aliased as i. We don't really care about what that returns, we only really care that it initializes the user variables. Because we're using it in a JOIN operation, we also care that it returns exactly one row.
When the first row is processed, we have the values that were previously assigned to the user variable, and we assign the values from the current row to them. When the next row is processed, the values from the previous row are in the user variables, and we assign the current row values to them, and so on.
The "ORDER BY" on the query is important; it's vital that we process the rows in the correct order.
But that's just a start.
The next step is comparing the userid and activitydate values of the current and previous rows, and deciding whether we're in the same sessionid, or whether its a different session, and we need to increment the sessionid by 1.
SELECT #sessionid := #sessionid +
IF( t.userid = #prev_userid AND
TIMESTAMPDIFF(SECOND,#prev_activitydate,t.activitydate) <= 3600
,0,1) AS sessionid
, #prev_userid := t.userid AS userid
, #prev_activitydate := t.activitydate AS activitydate
FROM (SELECT #prev_userid := NULL, #prev_activitydate := NULL, #sessionid := 1) i
JOIN so_time_diff t
ORDER BY t.userid, t.activitydate
You could make use of the value stored in the existing time_diff column, but you need the value from previous row when checking the current row, so that just be another MySQL user variable, a check of #prev_time_diff, rather than calculating the timestamp difference (as in my example above.) (We can add other expressions to the select list, to make debugging/verification easier...
, #prev_userid=t.userid
, TIMESTAMPDIFF(SECOND,#prev_activitydate,t.activitydate)
N.B. The ORDER of the expressions in the SELECT list is important; the expressions are evaluated in the order they appear... this wouldn't work if we were to assign the userid value from the current row to the user variable BEFORE we checked it... that's why those assignments come last in the SELECT list.
Once we have a query that looks good, that's returning a "sessionid" value that we want to assign to the row with a matching userid and activitydate, we can use that in a multitable update statement.
UPDATE (
-- query that generates sessionid for userid, activityid goes here
) s
JOIN so_time_diff t
ON t.userid = s.userid
AND t.activitydate = s.activity_date
SET t.sessionid = s.sessionid
(If there's a lot of rows, this could crank a very long time. With versions of MySQL prior to 5.6, I believe the derived table (aliased as s) won't have any indexes created on it. Hopefully, MySQL will use the derived table s as the driving table for the JOIN operation, and do index lookups to the target table.)
FOLLOWUP
I entirely missed the requirement to restart sessionid at 1 for each user. To do that, I'd modify the expression that's assigned to #sessionid, just split the condition tests of userid and activitydate. If the userid is different than the previous row, then return a 1. Otherwise, based on the comparison of activitydate, return either the current value of #sessionid, or the current value incremented by 1.
Like this:
SELECT #sessionid :=
IF( t.userid = #prev_userid
, IF( TIMESTAMPDIFF(SECOND,#prev_activitydate,t.activitydate) <= 3600
, #sessionid
, #sessionid + 1 )
, 1 )
AS sessionid
, #prev_userid := t.userid AS userid
, #prev_activitydate := t.activitydate AS activitydate
FROM (SELECT #prev_userid := NULL, #prev_activitydate := NULL, #sessionid := 1) i
JOIN so_time_diff t
ORDER BY t.userid, t.activitydate
N.B. None of these statements is tested, these statements have only been desk checked; I've successfully used this pattern innumerable times.
Here is what I wrote, and this worked!!!
SELECT #sessionid := #sessionid +
CASE WHEN #prev_userid IS NULL THEN 0
WHEN t.UserId <> #prev_userid THEN 1-#sessionid
WHEN t.UserId = #prev_userid AND
TIMESTAMPDIFF(SECOND,#prev_activitydate,t.ActivityDate) <= 3600
THEN 0 ELSE 1
END
AS sessionid
, #prev_userid := t.UserId AS UserId
, #prev_activitydate := t.ActivityDate AS ActivityDate,
time_diff
FROM (SELECT #prev_userid := NULL, #prev_activitydate := NULL, #sessionid := 1) i
JOIN example t
ORDER BY t.UserId, t.ActivityDate;
thanks again to #spencer7593 for your very descriptive answer giving me the right direction..!!!
I've got a table with some data. It looks like that:
MariaDB [lokatnik]> SELECT id_elixir, start_time FROM elixir ORDER BY start_time ASC;
+-----------+------------+
| id_elixir | start_time |
+-----------+------------+
| 3 | 00:00:00 |
| 1 | 12:30:00 |
| 2 | 13:30:00 |
+-----------+------------+
3 rows in set (0.00 sec)
Now, I need to select exactly one row matching time condition - say - start_time > some_time.
For example:
when some_time is 10:00, then I need a row with id_elixir = 1 (first row with time more than 10:00),
when some_time is 13:00, then I need a row with id_elixir = 2 (like above),
when some_time is 14:00, then I need row with id_elixir = 3.
How can I get this row using SQL (and MySQL/MariaDB DBMS)?
Maybe, something like this will work for you:
SELECT
*
FROM
elixir
WHERE
CASE
WHEN start_time BETWEEN CAST('10:00:00' AS TIME) AND CAST('13:00:00' AS
TIME)
THEN id =1
WHEN start_time BETWEEN CAST('13:00:00' AS TIME) AND CAST('14:00:00' AS
TIME)
THEN id =2
WHEN start_time > CAST('14:00:00' AS TIME)
THEN id =3
ELSE true
But you can actually check that by using simple boolean operators:
SELECT
*
FROM
elixir
WHERE
(
start_time BETWEEN CAST('10:00:00' AS TIME) AND CAST('13:00:00' AS TIME)
AND id =1
)
OR
(
start_time BETWEEN CAST('13:00:00' AS TIME) AND CAST('14:00:00' AS TIME)
AND id =2
)
OR
(
start_time > CAST('14:00:00' AS TIME)
AND id =3