【问题标题】:Show item of the day显示当天的项目
【发布时间】:2011-03-15 03:55:37
【问题描述】:

我希望创建一个函数来从 mySQL 表中获取随机项,但让我将返回的内容保留为“当天的项目”。换句话说,昨天是“当天的项目”的项目不应该再次显示,直到所有其他项目都显示为当天的项目。

关于如何以优雅的方式做到这一点的任何建议?

谢谢

【问题讨论】:

  • 如果您不再展示那些“每日物品”,为什么它们需要随机?像米奇建议的显示项目上的一个简单标志可以解决问题,而无需缓慢的 rand() 调用
  • 项目的显示是循环的——一旦所有项目都以随机顺序显示,它们就可以重新显示。因此,一个简单的标志是不够的。显示所有项目后,需要一些逻辑来重置标志。

标签: php sql mysql random


【解决方案1】:

为什么不使用序列?

Sequence 轻松满足您的目的...

【讨论】:

  • 也许是因为我不知道?你能解释一下吗?
  • 序列是用于生成唯一编号的数据库对象。甲骨文支持它。但不确定mysql。
【解决方案2】:

这是一个存储过程,它选择随机行而不使用ORDER BY RAND(),并在所有项目都使用后重置 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 ;

使用方法:

CALL random_iotd();

该过程采用如下表结构:

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;

这是从 PHP 获取结果的一种方法(为简单起见,已删除错误检查):

$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

更新

此版本应在给定日期重复返回相同的结果。我真的没有时间测试这个,所以一定要自己做一些测试......

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 ;

以及表结构:

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;

【讨论】:

  • 这看起来很有希望!但是,我从未真正使用过存储过程。我刚刚将您的代码粘贴到我的 phpMyAdmin SQL 输入字段中,效果很好,但我似乎无法在任何地方找到存储过程?
  • @Lars:我对 phpMyAdmin 有点生疏,因为我很少使用它。我感觉 phpMyAdmin 并不真正支持存储过程。您可以从 SQL 选项卡创建它们,但某些版本在您尝试运行存储过程时会返回错误。您是在共享主机服务器上使用 phpMyAdmin,还是连接到您自己的 MySQL 副本?如果是后者,最好使用 MySQL 命令行界面。你最终会从 PHP 调用它吗?
  • @Lars:我添加了一些示例 PHP 代码,它从存储过程中抓取当天的随机项目。
  • 我在共享主机服务器上使用 phpMyAdmin。是的,我最终会从 php.. 中使用它 :)
  • @Mike 我通常使用包装在函数中的 mysql_query 通过 php 查询 mySQL。是否必须使用mysqlli才能使用php中的存储过程?
【解决方案3】:

“了解” SQL 的人会寻找声明式解决方案,并且会避开过程代码。标记行是程序代码的“气味”。

Items 的集合是静态的(从不改变)还是稳定的(很少改变)?如果是,那么从现在到结束进行一次生成值查找表的一次性练习会更容易,而不是安排一个 proc 每天运行以查找未使用的标志并更新今天的标志并清除如果所有标志都已使用等,则所有标志。

创建一个从今天到遥远未来日期之间的连续日期表,代表您的应用程序的生命周期(当然,您可以考虑省略非工作日)。在Items 表中添加一个引用键的列(确保选择ON DELETE NO ACTION 引用操作,以防那些Items 证明不是静态的!)然后随机分配整组Items 一个每天一次,直到每个都用过一次。对整组Items 再次重复,直到表已满。您可以使用电子表格轻松生成这些数据并将其导入(如果您是铁杆,则可以使用纯 SQL;)

使用标准 SQL 的快速示例:

假设集合中只有五个Items

CREATE TABLE Items 
(
 item_ID INTEGER NOT NULL UNIQUE
);

INSERT INTO Items (item_ID)
VALUES (1), 
       (2), 
       (3), 
       (4),
       (5);

你的查找表就这么简单:

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
);

从今天开始,随机添加整套Items

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);

然后,从最近的未填写日期开始,以随机顺序(希望是不同的)添加整组Items

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);

...再一次...

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);

..以此类推,直到桌子满为止。

那么这将只是在需要时在查找表中查找今天的日期的情况。

如果Items 的集合发生变化,那么查找表显然需要重新生成,因此您需要在设计的简单性与手动维护的需要之间取得平衡。

【讨论】:

    【解决方案4】:

    如果你有固定的项目,你可以添加列

    ALTER TABLE your_table ADD COLUMN item_day INT DEFAULT 0;
    

    然后选择项目使用

    WHERE item_day = DATE_FORMAT('%j')
    

    如果您得到空结果,那么您可以格式化新的日项目列表:

    <?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; }
    }
    

    ?>

    【讨论】:

      【解决方案5】:

      添加一列存储该项目是否已被使用:

      ALTER TABLE your_table ADD COLUMN isused BOOL DEFAULT 0;
      

      获取当天的随机项目:

          SELECT t.*
            FROM your_table t
           WHERE t.isused = 0 
      ORDER BY RAND()
             LIMIT 1
      

      现在更新该记录,以便将来无法使用:

      UPDATE your_table
            SET isused = 1
        WHERE id = id_from_select_random_statement
      

      【讨论】:

      • ORDER BY RAND() 不适用于超过 100,000 条记录的表。低于这个就好了。
      【解决方案6】:

      SELECT &lt;fields&gt; FROM &lt;table&gt; WHERE &lt;some logic to exclude already used&gt; ORDER BY RAND() LIMIT 1 会从表中随机获取一行。

      【讨论】:

      • 对于小桌子,这是可以的。这在大桌子上会很慢。
      • 好点——不过应该可以用几年的日常用品。
      【解决方案7】:

      添加一个布尔列“UsedAsItemOfTheDay”设置为false (0)。选择项目时更新为 true。从拣货流程中排除已使用的物品。

      SELECT * FROM `table` 
      WHERE UsedAsItemOfTheDay = 0
      ORDER BY RAND() LIMIT 1;
      

      (注意:这不是在 MySql 中返回随机行的最快方法;在大表上会很慢)

      另请参阅:quick selection of a random row from a large table in mysql

      【讨论】:

      • 同意,当涉及超过 100,000 条记录时,ORDER BY RAND() 不能很好地扩展。低于这个就好了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-11-28
      • 2020-07-10
      • 2012-03-11
      • 2015-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多