Показать предмет дня - PullRequest
       0

Показать предмет дня

2 голосов
/ 13 июля 2010

Я хочу создать функцию, которая получит мне случайный элемент из таблицы mySQL, но давайте оставим возвращенный элемент как "элемент дня".Другими словами, предмет, который вчера был «предметом дня», не должен показываться снова, пока все остальные предметы не будут показаны как предмет дня.

Любые предложения о том, как сделать это элегантно?

Спасибо

Ответы [ 7 ]

4 голосов
/ 13 июля 2010

Добавить столбец bool "UsedAsItemOfTheDay", установленный в false (0).Обновить до true, когда предмет выбран.Исключите уже использованные предметы из процесса выбора.

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

(Примечание: это не самый быстрый способ вернуть случайную строку в MySql; это будет медленно на огромных столах)

См.также: быстрый выбор случайной строки из большой таблицы в mysql

1 голос
/ 13 июля 2010

Люди, которые «знают» SQL, будут искать декларативные решения и избегать процедурного кода.Пометка строк - это «запах» процедурного кода.

Является ли набор Items статическим (никогда не изменяется) или стабильным (редко изменяется)?Если да, было бы проще выполнить одноразовое упражнение по генерации таблицы поиска значений с настоящего момента и до конца времени, вместо того, чтобы планировать запуск процедуры ежедневно, чтобы искать неиспользуемые флаги и обновлять флаг на сегодня и очищатьвсе флаги, если все они использовались и т. д.

Создайте таблицу последовательных дат между сегодняшним днем ​​и датой далекого будущего, представляющих время жизни вашего приложения (вы, конечно, можете рассмотреть возможность исключения нерабочих дней)Добавьте столбцы, ссылающиеся на ключ в вашей таблице Items (убедитесь, что вы выбрали ON DELETE NO ACTION ссылочное действие на тот случай, если эти Items окажутся не статичными!) Затем случайным образом назначьте весь набор Items одинв день, пока каждый не был использован один раз.Повторите еще раз для всего набора Items, пока таблица не заполнится.Вы можете легко сгенерировать эти данные, используя электронную таблицу, и импортировать их (или чистый SQL, если вы хардкор;)

Быстрый пример с использованием стандартного SQL:

Скажем, в пять только Itemsset:

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 изменится, то таблицу поиска, очевидно, необходимо будет регенерировать, поэтому вам необходимо сбалансировать простоту конструкции с необходимостью ручного обслуживания.

1 голос
/ 13 июля 2010

Добавить столбец, чтобы сохранить, использовался ли элемент:

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
1 голос
/ 13 июля 2010

SELECT <fields> FROM <table> WHERE <some logic to exclude already used> ORDER BY RAND() LIMIT 1 даст вам случайную строку из таблицы.

0 голосов
/ 13 июля 2010

Почему вы не используете последовательность?

Последовательность легко служит вашей цели ...

0 голосов
/ 13 июля 2010

Вот хранимая процедура, которая выбирает случайную строку без использования ORDER BY RAND() и сбрасывает использованный флаг после использования всех элементов:

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;
0 голосов
/ 13 июля 2010

Если у вас есть фиксированные элементы, вы можете добавить столбец

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

?>

...