I have a table which contains a column consisting of timestamps. I want to check if there is a new row within 5 minutes period of time. If not, then insert a new row or update the last row status column.
I tried something like this:
UPDATE table
SET status = 'offline'
WHERE CURRENT_TIMESTAMP() > ((SELECT MAX(dateandtime)) + INTERVAL 5 MINUTE)
or insert into ... but no success so far. I'm using PHP in WordPress if that matters.
I hope this helps you. If you want to add new row, you need to use INSERT command with selection like this...
INSERT INTO sessions(user_id, last_visit)
SELECT 1, CURRENT_TIMESTAMP
WHERE EXISTS (
SELECT 1
FROM sessions
WHERE user_id = 1
HAVING (MAX(last_visit) + INTERVAL 5 MINUTE) < CURRENT_TIMESTAMP
);
If you want to update status column, you need to use simple UPDATE
UPDATE sessions
SET status = 'offline'
WHERE (last_visit + INTERVAL 5 MINUTE) < CURRENT_TIMESTAMP;
Btw, you just need to have a last_visit column, and if it's less than now + 5 min, you already know that the user is offline. And you have to update this value on every authorized request with simple update like.
update users set last_visit = CURRENT_TIMESTAMP where id = ?;
So your User class will be have a function getStatus() like this:
public function getStatus() {
if ($this->lastVisit < (Carbon::now()->subMinutes(5))) {
return 'offline';
}
return 'online';
}
Assuming you have an auto-increment primary key, you can do this with replace:
replace into foo (id,dateandtime,status) values (
(select id from foo f where dateandtime > now()-interval 5 minute order by dateandtime desc limit 1),
now(),
# or if you want to preserve the current timestamp, replace the previous now() with:
#coalesce((select max(dateandtime) from foo f where dateandtime > now()-interval 5 minute), now()),
'offline'
);
This tries to find the id of the row to update, or uses null to trigger a new auto-increment id to be generated.
Note that replace is unsafe (may cause the source and replica to diverge) when using statement-based replication; in that case you would need a stored procedure, or in your code (in a transaction, to prevent a race condition) to do an update, and only if it updated 0 rows, do an insert.
fiddle fiddle
Related
I am trying to insert bulk orders via curl. For each order, the invoice number is generated incrementing the previously inserted invoice number in Order table.
Order Table
Create Table tOrder
(
OrderUID Int NOT NULL AutoIncrement,
OrderNumber Varchar(12) NOT NULL ,
CreatedBy Int,
CreatedOn DateTime,
Primary Key(OrderUID)
);
Sample ordernumber formats:
ULEN21000001
UCMC21000002
The last 6 digits is considered for incrementing the sequence during order generation.
SELECT Right(OrderNumber,6) FROM tOrders ORDER BY tOrders.OrderUID DESC LIMIT 1;
Issue I am facing:
While inserting single order or bulk insert (from single curl request) it is working. But when inserting bulk orders by triggering curl post from various system at same time. Duplicate OrderNumbers are inserted into order table.
For eg.,
30 orders from system A and 15 orders from system B and so on.
Select * from tOrder;
OrderUID
OrderNumber
CreatedBy
1
UABC210001
1
2
UABC210001
1
3
UABD210002
2
4
UABC210003
2
5
UABC210003
3
6
UABE210004
3
7
UABE210004
3
What I have tried:
1)In PHP,
I getting all the request in array and looping through each row and generating OrderNumber and inserting into tOrder table.
foreach($Orders as $order)
{
$this->db->trans_begin();
$insArr =[
'OrderNumber' => $this->GenerateOrderNo(), //Order generation
'CreatedBy' => 1,
];
$this->db->insert('tOrder',$insArr);
$insert_id = $this->db->insert_id();
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
} else {
$this->db->trans_commit();
/* regenerating and updating if already exists */
if($this->OrderExists($insArr['OrderNumber']))
{
$insArr =[
'OrderNumber' => $this->GenerateOrderNo(), //Order re-generation
];
$this->db->where('OrderUID',$insert_id);
$this->db->update('tOrder',$insArr);
}
}
}
In MySql,
I have written a trigger to generate OrderNumber before inserting into table.
CREATE TRIGGER Insert_OrderNumber BEFORE INSERT ON tOrders
FOR
EACH ROW BEGIN
SELECT Right(OrderNumber,6) INTO #LastOrderNo
FROM tOrders ORDER BY tOrders.OrderUID DESC LIMIT 1;
SELECT LPAD(#LastOrderNo + 1, 6,0) INTO #NewSequenceNo;
SET NEW.OrderNumber = #NewSequenceNo;
END
I have also tried to add unique index for OrderNumber column.
In such case, OrderEntry fails with error.
Please correct me, where I am wrong or any alternate suggestions.
.
I would suggest changing you table structure so that you store the order prefix instead of the whole order number (6 chars instead of 12 and not storing the sequence twice in the same table) -
CREATE TABLE `tOrder` (
`OrderUID` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`OrderPrefix` CHAR(6) NOT NULL,
`CreatedBy` INT UNSIGNED NOT NULL,
`CreatedOn` DATETIME NOT NULL,
PRIMARY KEY (`OrderUID`));
There is no need to have another sequence for the order number as you already have your OrderUID column. So when accessing the table you can just -
SELECT
OrderUID,
CONCAT(OrderPrefix, LPAD(OrderUID, 6, '0')) AS OrderNumber,
CreatedBy,
CreatedOn
FROM tOrder;
or just save this query as a view -
CREATE VIEW `vw_orders` AS
SELECT
OrderUID,
CONCAT(OrderPrefix, LPAD(OrderUID, 6, '0')) AS OrderNumber,
CreatedBy,
CreatedOn
FROM tOrder;
You did not include the source for your GenerateOrderNo() method in your original post. I suspect it would be a reasonable approach to drop the OrderPrefix from the tOrder table and replace it with a foreign key to the object referenced by your order prefix.
I am learning basic PHP and SQL (using PHPmy admin) and I am having trouble understanding how to handle values from previous rows to calculate data in a new row in the table.
How would I calculate values in a row based on the previously entered row? For example, I want to have each row:
1. Auto create rows until Col A ID = 20
2. Col B – Auto add 1 month to previous row date
3. Col D – Previous row value minus payment(Col C)
Do I use PHP or sql? How would I set it out? Please be gentle I am still very new at this.
CREATE TABLE #Details(ID INT IDENTITY(1,1),_Date DATE ,Payment INT,
LoanAmount INT)
DECLARE #Date DATE,#Payment INT,#LoanAmount INT,#PreLoanAmount INT
IF NOT EXISTS(SELECT 1 FROM #Details)
BEGIN
INSERT INTO #Details(_Date ,Payment ,LoanAmount )
SELECT #Date,#Payment,#LoanAmount
END
ELSE
BEGIN
SELECT TOP 1 #PreLoanAmount = LoanAmount FROM #Details ORDER BY ID DESC
INSERT INTO #Details(_Date ,Payment ,LoanAmount )
SELECT DATEADD(MONTH,1,#Date),#Payment,#PreLoanAmount - #Payment
END
Well I have this mysql table with numbers in one column and a confirmation boolean of 0 or 1 and I have about 1,000 rows so it's not something I can do manually but anyways...
I want to sort the row by highest value and grab the names of the first 5 people and put those 5 people in another table on a column and then set them to confirmed and continue until there's no one left in the table that isn't confirmed...
ex:
Name:Rank:Confirm
Bob:5000:0
James:34:0
Josh:59:1
Alex:48:0
Romney:500:0
Rolf:24:0
Hat:51:0
so when you run the code it will do the following:
Squad:Name1:Name2:Name3:Name4:Name5
1:Bob:Romney:Hat:Alex:James
(as you can see Josh was excluded and Rolf was too low)
And since Rolf is alone and there are no one else left, he wont be put into a team and will be left unconfirmed...
I'm not really pro at mysql so I was stumped on this and at most was capable of organizing the whole thing by rank and that's it ._.
edit:
The terrible attempt I had at this:
<?php
$parse = mysql_query("SELECT MAX(rank) AS rank FROM users AND confirm='0'");
mysql_query("Insert into squad (nameone)values($parse)");
mysql_query("Update squad set confirm = '1' where name = $parse");
?>
Assuming confirm will have only either 1 or 0.
CREATE TABLE table2 (id INT PRIMARY KEY AUTO_INCREMENT, name varchar(255));
CREATE PROCEDURE rank()
BEGIN
DECLARE count INT DEFAULT 1;
WHILE count > 0 DO
UPDATE table1 SET Confirm=2 WHERE Confirm=0 ORDER BY Rank DESC LIMIT 5;
INSERT INTO table2 (SELECT GROUP_CONCAT(Name) FROM table1 WHERE Confirm=2);
UPDATE table1 SET Confirm=1 WHERE Confirm=2;
SELECT count(*) FROM table1 WHERE Confirm=0;
END WHILE;
END;
Call the procedure rank() when ever you want
CALL rank();
I have a data table with hundreds of thousands of rows which represent requests to servers to get data. Each record has a timestamp, the server ID and a binary value (tinyint) of whether the server responded correctly. The query times are not constant.
I am trying to get a total amount of time that the server was deemed to be 'online' by adding up the times between the queries where the server was online (very highly preferable a mysql query).
Eg.
server | time | status
1 | 1/1/2012 11:00 online
1 | 1/1/2012 11:02 online
1 | 1/1/2012 11:05 offline
2 | 1/1/2012 11:10 online
1 | 1/1/2012 11:30 online
Time now: 11:40
Server 1 Online Time = 2+3+10 = 15 minutes
Is it possible to do this in mysql? I would much prefer it over getting all the rows to php and calculating it or averaging anything.
This could be done using UNIX timestamp conversion and variable assignment on a properly sorted row set. By "properly sorted" I mean the rows must be sorted by server, then by time. Here's how you could use variables to get the online time (interval) in seconds since the previous event for every row in your table (called server_status for the purpose of this answer):
SELECT
*,
#currenttime := UNIX_TIMESTAMP(`time`),
#lasttime := CASE
WHEN server <> #lastserver OR #laststatus = 'offline'
THEN #currenttime
ELSE #lasttime
END,
#currenttime - #lasttime AS seconds_online,
#lasttime := #currenttime,
#lastserver := server,
#laststatus := status
FROM
server_satus s,
(SELECT #lastserver := 0) x
ORDER BY
s.server,
s.`time`
As you can see, a temporary variable (#currenttime) is initialised with the UNIX timestamp equivalent of time, another one is used to hold the previous timestamp so that the difference between the two could be calculated. Other variables are used to remember the previous server ID and the previous status, so that, when necessary, the difference was returned as 0 (which is done for every row which records a server's first event as well as those that come after offline events).
You could now just group the result set produced by the above query, SUM() the seconds_online values and divide them by 60 to get minutes (if you aren't happy with seconds), like this:
SELECT
server,
SUM(seconds_online) DIV 60 AS minutes
FROM (
the query above
) s
Note, however, that the first query doesn't really calculate the servers' seconds spent online since their respective last events. That is, the current time might very well differ from that in any of the latest event records, and it wouldn't be taken into account, because the query calculates the seconds per row since the previous row.
One way to solve this would be to add one row per server containing the current timestamp and the same status as in the last record. So, instead of just server_status you would have the following as the source table:
SELECT
server,
`time`,
status
FROM server_status
UNION ALL
SELECT
s.server,
NOW() AS `time`,
s.status
FROM server_status s
INNER JOIN (
SELECT
server,
MAX(`time`) AS last_time
FROM server_status
GROUP BY
server
) t
ON s.server = t.server AND s.`time` = t.last_time
The left part of the UNION ALL just returns all rows from server_status. The right part first gets the last time per server, then joins the result set to server_status to get hold of the corresponding statuses, substituting time with NOW() along the way.
Now that the table is completed with the "fake" event rows reflecting the current time, you can apply the method used in the first query. Here's what the final query looks like:
SELECT
server,
SUM(seconds_online) DIV 60 AS minutes_online
FROM (
SELECT
*,
#currenttime := UNIX_TIMESTAMP(`time`),
#lasttime := CASE
WHEN server <> #lastserver OR #laststatus = 'offline'
THEN #currenttime
ELSE #lasttime
END,
#currenttime - #lasttime AS seconds_online,
#lasttime := #currenttime,
#lastserver := server,
#laststatus := status
FROM
(
SELECT
server,
`time`,
status
FROM server_status
UNION ALL
SELECT
s.server,
NOW() AS `time`,
s.status
FROM server_status s
INNER JOIN (
SELECT
server,
MAX(`time`) AS last_time
FROM server_status
GROUP BY
server
) t
ON s.server = t.server AND s.`time` = t.last_time
) s,
(SELECT #lastserver := 0) x
ORDER BY
s.server,
s.`time`
) s
GROUP BY
server
;
And you can try it (as well as play with it) at SQL Fiddle too.
Here is the sample table structure I created:
-- SQL EXAMPLE
CREATE TABLE IF NOT EXISTS `stack_test` (
`server` int(11) NOT NULL,
`rtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint(4) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `stack_test` (`server`, `rtime`, `status`) VALUES
(1, '2012-01-01 11:00:24', 1),
(1, '2012-01-01 11:02:24', 1),
(1, '2012-01-01 11:05:24', 0),
(2, '2012-01-01 11:10:24', 1),
(1, '2012-01-01 11:30:24', 1);
-- SQL EXAMPLE END
This is the PHP code:
<?php
$query = 'SELECT DISTINCT(`server`) `server` FROM stack_test';
$res = sql::exec($query); // replace with your function/method to execute SQL
while ($row = mysql_fetch_assoc($res)) {
$server = $row['server'];
$uptimes = sql::exec('SELECT * FROM stack_test WHERE server=? ORDER BY rtime DESC',$server);
$online = 0;
$prev = time();
$prev = strtotime('2012-01-01 11:40:00'); // just to show that it works given the example
while ($uptime = mysql_fetch_assoc($uptimes)) {
if ($uptime['status'] == 1) {
echo date('g:ia',$prev) . ' to ' . date('g:ia',strtotime($uptime['rtime'])) . ' = '.(($prev-strtotime($uptime['rtime']))/60).' mins<br />';
$online += $prev-strtotime($uptime['rtime']);
}
$prev = strtotime($uptime['rtime']);
}
echo 'Server '.$server.' is up for '.($online/60).' mins.<br />';
}
?>
This is the output I get:
11:40am to 11:30am = 10 mins
11:05am to 11:02am = 3 mins
11:02am to 11:00am = 2 mins
Server 1 is up for 15 mins.
11:40am to 11:10am = 30 mins
Server 2 is up for 30 mins.
I have a table with named "user-recent-activity" which has following columns: id, userid, activity and datetime. Now, I want to delete the records if any unique userid has more than 50 items, deleting the oldest records. For example, if the user id(lets say 1234) has more than 50 records in this table, then I have to save latest 50 records of user id(1234) and delete the oldest one.
Before inserting, query for the last 50 records with that ID (ordering from newer to older). If there is a 50th, substitute it (via update) instead of inserting a new row.
Assuming you are using a RDBMS that supports standard SQL the following stored procedure should do it.
create procedure remove-old-activities
(
#userid int
)
as
delete from user-recent-activity where userid=#userid and id not in (select top 50 id from user-recent-activity where userid=#userid order by datetime desc)
If you're DB does not support stored procedures then you should be able to use SQL parameters to pass the userid value...
Hope that helps
You could use rank method to precisely defined the rows number and thus delete the rows you want.
delete from tblName where id=
(select id from (
select #i := CASE WHEN ( #userid <> userid ) THEN 1
ELSE #i+1
END AS rank , id,userid, datetime2 ,#userid:=userid AS clset
from tblName x,(SELECT #i:=0) a ,(SELECT #userid:= 0) s
order by x.userid, datetime2 desc) T
where T.rank='50') ;
Another option:
Use the select query to select the rank <=50 and insert into a new table. Delete the old table and rename the new table afterwards.
insert into newtable (userid,activity,datetime2)
select userid,datetime2 from (
select #i := CASE WHEN ( #userid <> userid ) THEN 1
ELSE
#i+1
END AS rank , userid, activity,datetime2 ,#userid:=userid AS clset
from tblName x,(SELECT #i:=0) a ,(SELECT #userid:= 0) s
order by x.userid, datetime2 desc) T
where t.rank <=50