I am making multiple count using the same parameter. I would like to know if there is a way of doing only one request (to be more efficient)?
My COUNT are as follows:
// Get number of MEMBERS in roster of this roster manager
$stmt = $mysqli->prepare("SELECT DISTINCT COUNT(rm.id_membre)
FROM roster_par_membre rm
JOIN roster_par_membre rm2
WHERE rm.id_roster = rm2.id_roster
AND rm2.level = 1
AND rm2.id_membre = ?");
$stmt->bind_param('i', $id_manager);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($nombre_total_membre);
$stmt->fetch();
// Get number of Alerts for this roster manager
$stmt = $mysqli->prepare("SELECT COUNT(id_alerte)
FROM alerte
WHERE modified_by = ?");
$stmt->bind_param('i', $id_manager);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($nombre_total_alerts);
$stmt->fetch();
// Get number of Rosters for this roster manager
$stmt = $mysqli->prepare("SELECT COUNT(id_roster)
FROM roster_par_membre
WHERE id_membre = ?
AND level = 1");
$stmt->bind_param('i', $id_manager);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($nombre_total_rosters);
$stmt->fetch();
If you don't want to keep the 3 queries separate, you can create a separate table to handle these statistics:
CREATE TABLE `statistics` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`members_in_roster` int(11) DEFAULT '0',
`number_of_alerts` int(11) DEFAULT '0',
`number_of_roster_per_manager` int(11) DEFAULT '0',
`id_membre` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
You'll link these numbers to a id_membre.
Also, you can use MySQL Triggers to update these numbers automatically to avoid doing 3 queries. You'll have to use one query to get all the results you need.
For instance, you can use the following trigger every time the Alerts table is updated:
DELIMITER $$
CREATE TRIGGER before_alerts_update
BEFORE UPDATE ON alerts
FOR EACH ROW BEGIN
UPDATE statistics
SET number_of_alerts = IFNULL(number_of_alerts, 0) + 1
WHERE modified_by = NEW.id_membre;
END$$
DELIMITER ;
If you want to keep the calculations in PHP instead of using MySQL triggers, it's not wrong. Keep in mind that using multiple triggers might slow down your database's performance. For instance, in the example mentioned above, every time the "alerts" table is updated, an UPDATE query is triggered. If you don't need to have these statistics numbers in real-time, this solution is an "overkill". It'll be better if you do the query once the user requests these numbers.
Related
I am using a three table structure to deal with a many-to-many relationship. I have one table that has a list of people and another that has a list of items. Sometimes multiple people have the same item and sometimes multiples items are linked to the same person so I set up the following table structure:
CREATE TABLE people (
id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
fname varchar(128) NOT NULL,
lname varchar(128) NOT NULL,
);
CREATE TABLE items (
id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
name varchar(128) NOT NULL UNIQUE,
);
The UNIQUE prevents the item name from repeating.
CREATE TABLE people_items (
pid int(11) NOT NULL,
iid int(11) NOT NULL,
FOREIGN KEY (pid) REFERENCES people(id)
ON UPDATE CASCADE
ON DELETE CASCADE
FOREIGN KEY (iid) REFERENCES items(id)
ON UPDATE CASCADE
ON DELETE CASCADE
);
This allows me to link multiple items to multiple people and vice versa. It also allows me to delete unnecessary records from the intermediate table.
All works fine so long as a new item is entered, but if an existing item is entered, the intermediate table is not updated, even though the people table is. I also do not get any errors.
Items are a comma delimited text entry which are exploded and lower cased into $items.
First I insert any new items and retrieve the id:
for ($i=0;$i<count($items);$i++){
$sql="INSERT IGNORE INTO items (name) VALUES (?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
$itemid=$stmt->insert_id;
If a new id is returned the following is executed:
if ($itemid){
$sql="INSERT INTO people_items (pid,iid) VALUES (?,?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('ii',$peopleid,$itemid);//the $peopleid is acquired the same way that the $itemid is acquired above
$stmt->execute();
}
Up to here, everything works just fine. At this point if an existing item is already in the items table, is where my intermediate table does not update. The people table however updates just fine, and the items table does not need to update as it already has the item in it.
Here is where I tried two different approaches to update the intermediate table.
First I kept the select and insert queries separate.
elseif(!$itemid){
$sql="SELECT id,name FROM items WHERE name=?;";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($iid,$name);
$stmt->fetch();
$sql="INSERT INTO people_items (pid,iid) VALUES (?,?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('ii',$pid,$iid);
$stmt->execute();
}
Here is my alternative approach which also does not update the intermediate table:
elseif(!$itemid){
$sql="INSERT INTO people_items (pid, iid) SELECT id,name FROM items WHERE name IN (?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
}
What am I doing wrong?
It's because you are using INSERT IGNORE, so if the item already exists, it won't insert anything, and you will get either 0 or the ID of the last successful insert as the ID (per the docs). So what you need to check is the affected number of rows. If it's 0, then it was ignored and you can SELECT it from the DB. If it's not 0, then you can safely insert the last ID.
for ($i=0;$i<count($items);$i++){
$sql="INSERT IGNORE INTO items (name) VALUES (?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
$itemid=$stmt->insert_id;
$affected_rows = $stmt->affected_rows;
if ($affected_rows !== 0) {
// insert
}
else {
// select and insert
}
}
Sidenote:
The way you are inserting the items in a loop is terrible for performance, especially if the DB is not in the same host as the server (think of going to the supermarket: do you go N times and get each item by itself, or do you just go once and get your N items?). What you should be doing, is inserting all of the elements in one query. Clearly it's not that clear cut in your case since you the IDs and what not, but you can read about this here to get started.
Sorry I didn't get to this sooner, but I have figured it out and was busy with work in the meantime. I figure I'd provide the answer in case anyone has the same issue in the future. Anyway, the issue was with my SQL query.
Where I had this:
elseif(!$itemid){
$sql="INSERT INTO people_items (pid, iid) SELECT id,name FROM items WHERE name IN (?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
}
I should have had this:
elseif(!$itemid){
$sql="INSERT INTO people_items (pid,iid) VALUES (?,(SELECT id FROM items WHERE name IN (?)));";
$stmt=$conn->prepare($sql);
$stmt->bind_param('ss',$peopleid,$items[$i]);
$stmt->execute();
}
I am learning SQL and I am in situation where I have to INSERT values into database only if it do not exist already.
My tableview structure is like this:
+----------+-----------+-----+-----+
| first_id | second_id | timestamp |
+----------+-----------+-----------+
I Want to insert only if there is not same first_id and second_id e.g if in table there is first_id 1 and second_id 2 and I am adding it again, I do not want to add it anymore. So if the first_id and second_id rows already have values 1 and 2 then do not add but if first_id is 3 and second_id is 1 then I would allow the inserting.
This is my query ATM:
INSERT INTO `testtable`.`ids` (`first_id`, `second_id`) VALUES (:first_id, :second_id)
And like this I tried with NOT EXISTS but it is not working:
NOT EXISTS (SELECT first_id, second_id FROM `testtable`.`ids` WHERE first_id = : first_id AND second_id = : second_id) INSERT INTO `testtable`.`ids` (`first_id `, `second_id `) VALUES (: first_id, : second_id)
The last mentioned query gives me Syntax error but once I even got the integrity violation and it told me to check documentation.
I am executing my queries using PHP ->query(""); function.
I tried to do like IF NOT EXISTS and NOT EXISTS but those didn't work. How should I approach to this?
This is simple. Declare first_id and second_id as composite key. I would prefer not to make any changes in your PHP Code but make your DB structure versatile so that it doesn't accept any duplicate values how-so-ever you are inserting it.
CREATE TABLE `demo` (
`first_id` smallint(6) DEFAULT NULL,
`second_id` smallint(6) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `first_id` (`first_id`,`second_id`)
)
Now first_id and second_id will never accept duplicate values.
^ table contains values (1,2). Now inserting (1,3).
^the table accepts (1,3). Now inserting (1,2) again.
the insert statement throws an error. Now the table will never accept duplicate values for the key (first_id,second_id).
If the table already exists and you're not creating it from scratch, simply execute:
alter table `table_name` add unique key (first_id, second_id);
This will prevent the duplicate values henceforth.
You may try this if you are using PHP and MySql:
<?php
//Added database connection code here
$first_id = $_POST['first_id '];
$second_id = $_POST['second_id '];
$sql = "select * from ids where first_id = ".$first_id ." and second_id ='".$second_id."'" ;
$result = $mysqli->query($sql);
$row = $result->fetch_row();
if($row[0]) {
$mysqli->close();
} else {
//preapare an insert statement
$stmt = $mysqli->prepare(" INSERT INTO `ids` (first_id, second_id) VALUES (?,?)");
$stmt->bind_param("ii", $first_id, $second_id);
//execute the statement
if( $stmt->execute() ) {
unset($first_id);
unset($second_id);
} else {
echo $mysqli->error;
}
//close statement
$stmt->close();
$mysqli->close();
}
?>
Checking for existence for the first and second ids and additionally declare 2 sql variables for first and second ids and set them as per required.
DECLARE #Exists int; #first_id int; #second_id int;
SET #first_id = 1;
SET #second_id = 2;
SELECT #Exists = COUNT(*) FROM [testtable] where [first_id] = #first_id and [second_id] = #second_id;
Condition to insert if count of matched records is 0:
IF(#Exists = 0)
BEGIN
INSERT INTO [testtable](first_id, second_id)
VALUES(#first_id,#second_id)
END
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I am checking the for the user_id (it is held in a session) - this is working. Then I am running a SELECT query for that user for the database table click_count. I am checking to see if that user has any records within it, ie: $page_count. If not, I want my INSERT statement to run to add that user to the database table along with other data.
The part I do not understand is it seems that my UPDATE query is always running. For example no matter which user I login with my query only updates the only user in the database table. IE: Bob is the only user in the click_count table, if I log in with Pete, Bob's record is being updated.
I have tested the value for $page_count and it equals 0, so my INSERT should be running. I have also tried if ($page_count === 0) {
Does anyone see anything I am missing?
$curPage = $_SERVER['PHP_SELF'];
$clicks = 0;
$setup = 0;
$page_total_count = 0;
var_dump($user_id);
$click_sql = "
SELECT *
FROM click_count
WHERE user_id = ?
AND page_url = ?
";
$click_stmt = $con->prepare($click_sql);
$click_stmt->execute(array($user_id, $curPage));
$click_stmt_rows = $click_stmt->fetchAll(PDO::FETCH_ASSOC);
$page_count = $click_stmt->rowCount();
foreach ($click_stmt_rows as $click_stmt_row) {
$setup_status = $click_stmt_row['setup'];
$page_total_count = $click_stmt_row['page_count'];
}
if ($page_count == 0) {
$click_insert_sql = "
INSERT INTO click_count
(user_id, page_url, page_count, setup)
VALUES(?, ?, ?, ?)
ON DUPLICATE KEY UPDATE page_count=page_count+1;
";
$click_insert_stmt = $con->prepare($click_insert_sql);
$click_insert_stmt->execute(array($user_id, $curPage, 1, $setup));
}
else {
$click_update_sql = "
UPDATE click_count
SET page_count=page_count+1
WHERE user_id = ?
AND page_url = ?
";
$click_update_stmt = $con->prepare($click_update_sql);
$click_update_stmt->execute(array($user_id, $curPage));
}
Table
click_count
CREATE TABLE `click_count` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`page_url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`page_count` int(11) NOT NULL,
`setup` int(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`),
UNIQUE KEY `page_url` (`page_url`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Since there is only the one user in the table, there is no record "to insert/update", therefore
ON DUPLICATE KEY UPDATE failed you silently.
A regular UPDATE will suffice:
I.e. and as an example:
UPDATE table SET col_x = 0|1 WHERE col_y = ? // (boolean 0-1)
Note:
If ever you wish to increase a column by counting later on, the syntax would be:
UPDATE table SET col_x = col_x + 1 WHERE col_y = ?
In regards to your asking about how you could improve on your code:
#Fred-ii- Thanks. Yes, it is working now how I want, but if there are ways to improve the code I am always willing to try to learn it. I just remembered people in the past saying that I didn't need the update query at all with the duplicate key update. – Paul
You could use named placeholders :name rather than ? since they are easier to keep track of, but this is of course a matter of opinion that I feel is also shared by many and not just myself.
Footnotes/credits:
I would like to also give credit to the following comment:
"If you always fall into update indicates that $page_count is not zero.. Try to echo() it to see maybe.. I would probably first try to add another user into click_count table and then it may become easier to see where it goes wrong.. – johnyTee"
where the OP responded with:
"#Fred-ii- I figured it out. I used johnyTee's advise and tried adding another user to the database manually and it wouldn't let me because of the unique index for the page_url column. I then removed the unique index from it and now it works perfectly. Thanks for the help! – Paul"
from PHP PDO doc http://php.net/manual/en/pdostatement.rowcount.php
PDOStatement::rowCount() returns the number of rows affected by a
DELETE, INSERT, or UPDATE statement.
if you need th number of rows in select you should use somethings like
$sql = "SELECT *
FROM click_count
WHERE user_id = ?
AND page_url = ?
";
$result = $con->prepare($sql);
$result->execute();
$number_of_rows = $result->fetchColumn();
It may be '0' (a string). You can use intval to convert it to an integer.
$page_count = intval( $click_stmt->rowCount() );
http://php.net/manual/en/function.intval.php
For most databases, PDOStatement::rowCount() does not return the number of rows affected by a SELECT statement. Instead, use PDO::query() to issue a SELECT COUNT(*) statement with the same predicates as your intended SELECT statement, then use PDOStatement::fetchColumn() to retrieve the number of rows that will be returned. Your application can then perform the correct action.
try like this:
$sql = "SELECT count(*)
FROM click_count
WHERE user_id = ?
AND page_url = ?
";
if ($res = $conn->query($sql)) {
/* Check the number of rows that match the SELECT statement */
if ($res->fetchColumn() > 0) {
//insert
}else {
//update
}
}
I have a matter in PHP & Mysql Project.
Simply, I have two tables project and project features,
Every project has as specific features.
CREATE TABLE projects (
ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name varchar(255) NOT NULL
);
CREATE TABLE projects_features (
projectId INT NOT NULL,
name varchar(255) NOT NULL,
value varchar(255) NOT NULL,
weight INT NOT NULL
);
INSERT INTO projects VALUES (NULL,'project1');
INSERT INTO projects VALUES (NULL,'project2');
INSERT INTO projects_features VALUES (1,'Feature1','Feature1 Value',1);
INSERT INTO projects_features VALUES (2,'Feature2','Feature2 Value',2);
INSERT INTO projects_features VALUES (1,'Feature3','Feature3 Value',3);
INSERT INTO projects_features VALUES (2,'Feature4','Feature4 Value',4);
INSERT INTO projects_features VALUES (1,'Feature5','Feature5 Value',5);
I Get the Project features by:
SELECT * FROM projects_features WHERE projectId = 1 ORDER BY weight ASC;
So the bigger weight will be down and lower weight will be Up.
Now,
In My View I have move up and move down buttons, so I can re-sort project features.
I can firstly select the current item weight then select the upper item weight ,
then type two update queries to exchange the weight between the two rows,
but it's not a professional way , I don't like to use four queries.
I need to do it in one query Instead of four queries.
Can anybody help please ?
Here's how I'd tackle this, assuming I've understood the question.
First, I'd add a featureId column to projects_features, and make (projectId, featureId) the composite primary key. This isn't actually necessary to my solution; it just makes the rest a whole lot easier. For the next part you need to be able to reference individual records in projects_features.
Next, I'd have the Move buttons populate an array in PHP, like
$update_list = array();
$update_list[i] = array(':project_id' => $proj_id,
':feature_id' => $feat_id,
':new_weight' => $weight);
Finally, I'd do the update through a PHP function that encapsulates the UPDATE statements, like this:
function updateWeights($update_array, $dbh)
{
$sql = 'UPDATE project_features
SET weight = :new_weight
WHERE projectId = :project_id
AND featureId = :feature_id'
$stmt = $dbh->prepare($sql);
foreach ($update_array as $update_item)
{
$stmt->execute($update_item);
}
}
Note I'm using PDO here; it could also be done with mysqli, although mysqli doesn't support named bind parameters so the syntax would be slightly different, something like
function updateWeights($update_array, $dbh)
{
$sql = 'UPDATE project_features
SET weight = ?
WHERE projectId = ?
AND featureId = ?'
$stmt = $dbh->prepare($sql);
foreach ($update_array as $update_item)
{
$stmt->bind_param('i', $update_item[':new_weight']);
$stmt->bind_param('i', $update_item[':project_id']);
$stmt->bind_param('i', $update_item[':feature_id']);
$stmt->execute();
}
}
Also note that I haven't actually run this code, and so make no representation that it is free of syntax or other errors.
I hope that helps.
I am looking to create a function that gets me a random item from a mySQL table, but let's me keep the returned as the "item of the day". In other words, the item that was "the item of the day" yesterday should not be shown again until all other items have been shown as item of the day.
Any suggestions on how to do this in an elegant way?
Thanks
Add a bool column "UsedAsItemOfTheDay" set to false (0). Update to true when item is picked. Exclude already used items from the picking process.
SELECT * FROM `table`
WHERE UsedAsItemOfTheDay = 0
ORDER BY RAND() LIMIT 1;
(Note: this is not the fastest way to return a random row in MySql; it will be slow on huge tables)
See also: quick selection of a random row from a large table in mysql
SELECT <fields> FROM <table> WHERE <some logic to exclude already used> ORDER BY RAND() LIMIT 1 will get you a random row from the table.
Add a column to store whether the item has been used:
ALTER TABLE your_table ADD COLUMN isused BOOL DEFAULT 0;
Get a random item of the day:
SELECT t.*
FROM your_table t
WHERE t.isused = 0
ORDER BY RAND()
LIMIT 1
Now update that record so it can't be used in the future:
UPDATE your_table
SET isused = 1
WHERE id = id_from_select_random_statement
People who "know" SQL will look for declarative solutions and will shun procedural code. Flagging rows is a "smell" for procedural code.
Is the set of Items static (never changes) or stable (rarely changes)? If yes, it would be easier to do a one-off exercise of generating a lookup table of values from now until the end of time, rather than scheduling a proc to running daily to look for unused flags and update the flag for today and clear all flags if all have been used etc.
Create a table of sequential dates between today and a far future date representing the lifetime of your application (you could consider omitting non-business days, of course). Add a column(s) referencing the key in you Items table (ensure you opt for ON DELETE NO ACTION referential action just in case those Items prove not to be static!) Then randomly assign the whole set of Items one per day until each has been used once. Repeat again for the whole set of Items until the table is full. You could easily generate this data using a spreadsheet and import it (or pure SQL if you are hardcore ;)
Quick example using Standard SQL:
Say there are only five Items in the set:
CREATE TABLE Items
(
item_ID INTEGER NOT NULL UNIQUE
);
INSERT INTO Items (item_ID)
VALUES (1),
(2),
(3),
(4),
(5);
You lookup table would be as simple as this:
CREATE TABLE ItemsOfTheDay
(
cal_date DATE NOT NULL UNIQUE,
item_ID INTEGER NOT NULL
REFERENCES Items (item_ID)
ON DELETE NO ACTION
ON UPDATE CASCADE
);
Starting with today, add the whole set of Items in random order:
INSERT INTO Items (item_ID)
VALUES ('2010-07-13', 2),
('2010-07-14', 4),
('2010-07-15', 5),
('2010-07-16', 1),
('2010-07-17', 3);
Then, starting with the most recent unfilled date, add the whole set of Items in (hopefully a different) random order:
INSERT INTO Items (item_ID)
VALUES ('2010-07-18', 1),
('2010-07-19', 3),
('2010-07-20', 4),
('2010-07-21', 5),
('2010-07-22', 2);
...and again...
INSERT INTO Items (item_ID)
VALUES ('2010-07-23', 2),
('2010-07-24', 3),
('2010-07-25', 5),
('2010-07-26', 1),
('2010-07-27', 4);
..and so on until the table is full.
Then it would then simply be a case of looking up today's date in the lookup table as and when required.
If the set of Items changes then the lookup table would obviously need to be regenerated, so you need to balance out the simplicity of design against the need for manual maintenance.
If you have fixed items you can add column
ALTER TABLE your_table ADD COLUMN item_day INT DEFAULT 0;
then selecting item use
WHERE item_day = DATE_FORMAT('%j')
If you get empty result then you can format new list of day items:
<?php
$qry = " UPDATE your_table SET item_day = 0";
$db->execute($qry);
// You only need 355 item to set as item of the day
for($i = 0; $i < 355; $i++) {
$qry = "UPDATE your_table SET item_day = ".($i+1)." WHERE item_day = 0 ORDER BY RAND() LIMIT 1";
$rs = $db->execute($qry);
// If no items left stop update
if (!$rs) { break; }
}
?>
Here's a stored procedure which selects a random row without using ORDER BY RAND(), and which resets the used flag once all items have been used:
DELIMITER //
DROP PROCEDURE IF EXISTS random_iotd//
CREATE PROCEDURE random_iotd()
BEGIN
# Reset used flag if all the rows have been used.
SELECT COUNT(*) INTO #used FROM iotd WHERE used = 1;
SELECT COUNT(*) INTO #rows FROM iotd;
IF (#used = #rows) THEN
UPDATE iotd SET used = 0;
END IF;
# Select a random number between 1 and the number of unused rows.
SELECT FLOOR(RAND() * (#rows - #used)) INTO #rand;
# Select the id of the row at position #rand.
PREPARE stmt FROM 'SELECT id INTO #id FROM iotd WHERE used = 0 LIMIT ?,1';
EXECUTE stmt USING #rand;
# Select the row where id = #id.
PREPARE stmt FROM 'SELECT id, item FROM iotd WHERE id = ?';
EXECUTE stmt USING #id;
# Update the row where id = #id.
PREPARE stmt FROM 'UPDATE iotd SET used = 1 WHERE id = ?';
EXECUTE stmt USING #id;
DEALLOCATE PREPARE stmt;
END;
//
DELIMITER ;
To use:
CALL random_iotd();
The procedure assumes a table structure like this:
CREATE TABLE `iotd` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item` varchar(255) NOT NULL,
`used` BOOLEAN NOT NULL DEFAULT 0,
INDEX `used` (`used`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Here's one way to get the result from PHP (to keep things simple, error checking has been removed):
$mysqli = new mysqli('localhost', 'root', 'password', 'database');
$stmt = $mysqli->prepare('CALL random_iotd()');
$stmt->execute();
$stmt->bind_result($id, $item);
$stmt->fetch();
echo "$id, $item\n";
// 4, Item 4
UPADATE
This version should return the same result repeatedly on a given date. I've not really had time to test this, so be sure to do some testing of your own...
DELIMITER //
DROP PROCEDURE IF EXISTS random_iotd//
CREATE PROCEDURE random_iotd()
BEGIN
# Get today's item.
SET #id := NULL;
SELECT id INTO #id FROM iotd WHERE ts = CURRENT_DATE();
IF ISNULL(#id) THEN
# Reset used flag if all the rows have been used.
SELECT COUNT(*) INTO #used FROM iotd WHERE used = 1;
SELECT COUNT(*) INTO #rows FROM iotd;
IF (#used = #rows) THEN
UPDATE iotd SET used = 0;
END IF;
# Select a random number between 1 and the number of unused rows.
SELECT FLOOR(RAND() * (#rows - #used)) INTO #rand;
# Select the id of the row at position #rand.
PREPARE stmt FROM 'SELECT id INTO #id FROM iotd WHERE used = 0 LIMIT ?,1';
EXECUTE stmt USING #rand;
# Update the row where id = #id.
PREPARE stmt FROM 'UPDATE iotd SET used = 1, ts = CURRENT_DATE() WHERE id = ?';
EXECUTE stmt USING #id;
END IF;
# Select the row where id = #id.
PREPARE stmt FROM 'SELECT id, item FROM iotd WHERE id = ?';
EXECUTE stmt USING #id;
DEALLOCATE PREPARE stmt;
END;
//
DELIMITER ;
And the table structure:
CREATE TABLE `iotd` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item` varchar(255) NOT NULL,
`used` BOOLEAN NOT NULL DEFAULT 0,
`ts` DATE DEFAULT 0,
INDEX `used` (`used`),
INDEX `ts` (`ts`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Why don't you use sequence?
Sequence serves your purpose easily...