Подтвердить что ты не робот

Запрос на ввод запроса

Я хочу предложить логин пользователю, если их первый выбор уже был сделан. Предположим, пользователь хочет зарегистрироваться как "Супермен". На сайте уже есть Супермена. Логины предлагаются в форме "Superman01", "Superman02" и т.д. Итак, script должен:

  • проверить для входа Superman в db
  • если он уже используется, добавьте '01' для входа и проверки его в DB
  • если он уже используется, счетчик приращений ('02'), добавляется к логину и снова проверяется
  • когда найден неверный логин, верните его пользователю

Что мне сейчас не нравится в этой схеме, так это то, что в базу данных MySQL требуется несколько запросов. Есть ли способ получить первый невостребованный логин за один раз? Может быть, с хранимой процедурой или умным SQL-запросом?

UPD: предложили награду

4b9b3361

Ответ 1

Пользователь regexp найдет необходимые совпадения:

SELECT .. FROM users WHERE username REGEXP '^superman[0-9]{1,2}'

Это вернет все имена пользователей в форме "supermanX" или "supermanXX" (одна или две цифры).

После того, как вы получите свои результаты, вы можете легко найти номер в строке или недостающие.

Для получения дополнительной информации прочитайте следующее:

http://dev.mysql.com/doc/refman/5.0/en/pattern-matching.html

http://dev.mysql.com/doc/refman/5.0/en/regexp.html


Edit

Предположим, что таблица называется "пользователями", а поле, которое называется "имя пользователя", может быть следующим:

/**
 * Checks a given name exists at the users table
 * and returns possible alternatives
 * or an empty string if no alternatives can be found
 */
function CheckUsername($name);
    // sanitize
    $query = sprintf("SELECT username FROM users
            REGEXP '%s[0-9]{0,2}' ORDER BY username",
            mysql_real_escape_string($name));

    $result = mysql_query($query);

    // get all possible matches
    $rows = array();
    while (list($match) = mysql_fetch_row($result)) {
        $rows[] = $match;
    }

    if (count($rows) == 0) {
        // no rows found, return the original name
        return $name;

    } else {
        // found multiple rows

        if ($rows[0] != $name) {
            // first check if the original name exists
            return $name;

        } else {
            // else go through each number until we find a good username
            $count = 1;
            while ($counter < count($rows) {
                $test = sprintf("%s%02d", $name, $counter);
                if ($rows[$counter] != $test) return $test;
                $counter++;
            }
        }
    }

    // nothing found
    return '';
}

Надеюсь, это поможет.

Ответ 2

Почему бы просто не выбрать where login like 'superman%' и повторить набор результатов в вашем коде?

Ответ 3

Попросите фразу:

Пожалуйста, дополнительно укажите фразу подсказки, которую вы хотели бы быть частью своего имени пользователя, если тот, который вы выберете, уже занят кем-то другим.
Например, если ваше имя - Иосиф, тогда уже будут приняты Джозеф, Джозеф или Джо. Таким образом, вы можете предоставить фразу подсказки, которая может быть одной из следующих:

  • ваше имя - например. Смитсон, который предложил бы "joe.smithson"
  • ваш город проживания - например. Bay Area - что предложит "joseph_bayarea"
  • цель учетной записи - например. разработчик, который предложил бы "joseph-devel"
  • цвет - например. синий - что предполагает "голубое".
  • число, которое должно быть суффиксным как "joe99"

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

Тем более, что служба, предоставляемая вашим сайтом, не указана.

Еще один способ приблизиться к этой проблеме - увидеть код, лежащий в основе этих "интеллектуальных" captchas, которые такие сайты, как Slashdot, генерируют. Некоторое остроумное развитие; -) имеет кучу слов семантически, связанных с обсуждаемой темой, и использует эти фразы для captcha.

Эта интеллектуальная/интеллектуальная вещь captcha немного, например Наборы Google.

Ужас кодирования также иногда показывал эти умные котча.

Играйте с этими типами сервисов или получите хорошую базу данных семантически связанных терминов. Затем свяжите эти термины с фразой подсказки, которую вы просите у пользователя.

Google делает это легко, потому что "все ваши поиски принадлежат Google" (TM).
У вас гораздо более простая задача - вам не нужно сканировать Интернет, и вам не нужно предоставлять результаты поиска или ссылки. Все, что вам нужно, это семантически база данных.

Вы можете получить один из них, достаточно внимательно посмотрев онлайн.
Вы можете начать с синонимов/антонимов и т.д.
IIRC, один из них - wordnet, но я не знаю лицензии. Так что посмотри.


Дополнительно (необязательно, но не реализуйте частично):
Я предлагаю, чтобы, если вы делаете такую ​​хорошую вещь, сделайте ее открытой. Это будет очень полезно для других и даст вам отличную репутацию.
И не забудьте также опубликовать код с автоматическим входом в систему для неизбежной ситуации, когда какой-либо кодер без этики и большого количества свободного времени будет использовать семантически связанную открытую базу данных слов, чтобы генерировать запросы регистрации на ваше приложение и все остальные!
Боты продолжают получать умнее и умнее. Проверка электронной почты является одной защитой от этого, но только в том случае, если эта служба электронной почты не может быть подорвана - что она может, если это новая служба электронной почты, которая постоянно поднимается.

Итак, это важная задача, если вы собираетесь реализовать эту идею и освободить ее как opensource. Тогда вы также должны его защитить.

Или вы можете просто сохранить свой собственный сайт.

Ответ 4

Вот мой вариант:

SELECT `login`
  FROM `usertable`
WHERE `login` LIKE 'Superman%'
ORDER BY `login` DESC
LIMIT 1;

Если запрос не возвращает результаты $username = 'Superman', в противном случае:

$username = 'Superman' . (strrev(intval(strrev($result['username']))) + 1);

Это должно сделать трюк, однако я должен сказать, что я не большой поклонник схемы выбора имени пользователя.


Пересмотренный SQL-запрос, в свете первого комментария klausbyskov:

SELECT `login`
  FROM `usertable`
WHERE `login` RLIKE '^Superman[0-9]*$'
ORDER BY `login` DESC
LIMIT 1;

Ответ 5

Вы можете, считая, что поле входа правильно проиндексировано (каким оно должно быть), выполните:

select login from usertable where login = 'Superman';

Если строки не возвращены, все готово. В противном случае вам придется проверить другие возможности:

select login from usertable where login like 'Superman%' order by login;

Теперь просто найдите вариант с наивысшим числовым суффиксом и добавьте его.

EDIT:
Один запрос к db для проверки только фактического имени выполняется быстро, но один запрос для проверки всех возможностей в большой базе данных будет медленным (не из-за подобного совпадения - это быстро, если вы проиндексированы), а скорее для загрузки всех этих строк и их обработка).

Вам было бы лучше сделать 1 запрос, чтобы проверить имя, а затем выполнить запрос только для проверки всех имен, когда желаемое имя не работает.

Вы также можете кэшировать результаты этого запроса, чтобы их можно было повторно использовать без необходимости возвращаться к БД в следующий раз, когда кто-то выбирает суперменное имя. Просто не забудьте очистить результаты, когда вы добавляете аналогичное имя входа в db.

Ответ 6

Если вы хотите сохранить некоторое состояние в базе данных...

Когда кто-то регистрирует имя пользователя, вставьте его в "доступную" таблицу, которая имеет два столбца: "base_name" (строка) и "next_available" (целое число). Если кто-то зарегистрирует имя пользователя, которое заканчивается двумя цифрами, найдите базу (часть, предшествующую двум последним цифрам), и либо вставьте ее в "доступную", либо обновите "next_available".

Когда кто-то вводит имя пользователя, которое недоступно, вы можете просто посмотреть его в "доступной" таблице и выдать базу и суффикс next_available. Это можно сделать в одном запросе.

Предостережение: если кто-то регистрирует "супермен93", вы получаете только 6 имен пользователей, даже если доступны номера от 01 до 92.

Ответ 7

Здесь мое мягкое решение: добавьте в таблицу пользователя один столбец varchar (называемый username_string_part), чтобы сохранить строковые части имени пользователя и второй столбец int (например, username_number_part) для хранения числовой части. Поэтому супермен 1 разбивается на "супермен" в столбце username_string_part и "1" в username_number_part. Также создайте индекс, возможно, по обоим столбцам или просто через username_string_part, если вы не ожидаете большого количества дубликатов имен username_string_part. Итак, в MySQL ваша таблица создания выглядит примерно так:

CREATE TABLE `users` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(25) NOT NULL default '',
  `username_string_part` varchar(25) NOT NULL default '',
  `username_number_part` int(11) NOT NULL default 0,
  PRIMARY KEY  (`id`),
  KEY `ix_username_string_part` (`username_string_part`)
) TYPE=MyISAM AUTO_INCREMENT=1;

(Обратите внимание, что имя пользователя "супермен" имеет значение по умолчанию username_number_part нуля - это важно.)

Как только у вас будет несколько записей, ваши данные будут выглядеть примерно так:

+----+-----------+----------------------+----------------------+
| id | username  | username_string_part | username_number_part |
+----+-----------+----------------------+----------------------+
|  1 | superman  | superman             |                    0 |
|  2 | superman1 | superman             |                    1 |
|  3 | superman3 | superman             |                    3 |
+----+-----------+----------------------+----------------------+

Затем это случай выбора минимального значения username_number_part, который не имеет значения username_number_part "самого плюс плюс один" в базе данных. Итак, для имени пользователя "супермен" :

select min(username_number_part) + 1 as min_number_available from users
    where username_string_part = 'superman' and username_number_part not in
    (select username_number_part - 1 from users where
        username_string_part = 'superman');

Возвращаемое значение min_number_available, NULL, если это первый экземпляр этого имени пользователя, поэтому он может иметь его, или целое число для следующего свободного слота в противном случае. Затем вы создадите рекомендуемое имя пользователя как "superman" + min_number_available. Вы можете выполнить concat в запросе или нет, как вам нравится. С приведенными выше примерами вы получите возвращаемое значение "2".

Недостатки: он добавит хранилище (столбец и индекс) и замедлит вставки очень немного. В нем также нет различий между "superman001" и "superman01". (Хотя это возможно, если вы обработали ведущие нули как часть username_string_part, поэтому "superman001" будет разделен как "супермен00" и "1".)

Upsides: это единственный запрос для индексированных столбцов.

В конце концов, я был бы удивлен, если бы на сайте было так много дубликатов имени пользователя, что выполнение цикла for с несколькими запросами базы данных было действительно настолько плохим.

Ответ 8

Если вы можете изменить схему базы данных, решение тривиально.

Разделите имя пользователя на два столбца: username и username_suffix (INTEGER).

Если username_suffix равно 0, оно не отображается. то есть "супермен" и "супермен0" эквивалентны.

Затем вы можете просто

SELECT MAX(username_suffix)+1 WHERE username = 'superman'

чтобы получить следующий доступный суффикс.

Альтернативно, если вы не можете изменить схему базы данных, попробуйте работать с вероятностью. Добавить случайное 2-значное число; если это сталкивается с существующим пользователем, вместо этого добавьте случайное 3-значное число; если это сталкивается...

Если вы не против мягко раздражать небольшую часть потенциальных пользователей, просто предлагая имя пользователя, которое было предложено пользователем, с любыми забытыми цифрами и добавленными лишними случайными цифрами, а не проверкой базы данных, вероятно, будет работать достаточно хорошо:

например.

superman not available, try superman39...  (Try 2 extra digits first)
superman39 not available, try superman491... (now try 1 extra digit each time)
superman491 not available, try superman8972... (up to (say) 4 digits)
superman9872 not available, try superman2758

Потенциальному пользователю должно быть действительно не повезло, что нужно повторить попытку более одного или двух раз.


Почему-то я не видел решение @Karl, прежде чем написать это. Если дополнительный столбец db - лучшее решение, то он, вероятно, должен получить кредит - хотя я думаю, что это проще. Однако вероятностный подход имеет для меня гораздо больше смысла.

Ответ 9

В соответствии с комментариями к вопросу, требуется фиксированный диапазон 00 - 99. Вы могли бы рассмотреть возможность сделать SELECT MAX() в двух последних частях имени.

SELECT max(convert(substring(name, char_length(username)-1, 2), signed)) AS max
    FROM user 
    WHERE name LIKE 'superman%'

Однако это не освобождает от обслуживания. Что, если 99 superman s?

Это также не лишено потенциальных столкновений/столкновений с именами пользователей, которые уже заканчиваются цифрами типа 01010101 и h4xx0r1337. Что делать, если уже есть superman01 и superman02, а новый (и неосведомленный) пользователь решает зарегистрироваться как superman88, потому что он/она родился в 1988 году; любой следующий superman предложил бы superman89, оставив отверстие между superman02 и superman88.

Трудно дать "лучший" ответ на этот конкретный вопрос. Самый безопасный способ - это что-то вроде:

if (find_user($username) != null) {
    for ($i = 0; $user != null; $i++) {
        $username = $username . $i;
        $user = find_user($username);
    }
}
// Now suggest $username.

Конечно, стоимость, но это не шокирует. Также подумайте еще раз, как часто это происходит? Может быть, раз в день? Или раз в год, если ваш форум получает в среднем только 1 нового участника в день?

Ответ 10

В нижеприведенном запросе используется вспомогательная таблица с 10 записями (цифры от 0 'до' 9 ') и кросс-соединение для создания списка строк от 00' до '99'. Эти значения объединяются с выбранным пользователем именем пользователя ( "супермен" ), а результат проверяется как NOT IN на вашу таблицу текущих пользователей. Конечным результатом является список возможных имен для входа ( "superman00" в "superman99" ), которые в настоящее время не используются. Вы можете показать пользователю несколько из них, чтобы выбрать. Я тестировал в TSQL, должен быть легко перевести на MySQL (я думаю, вам нужно заменить 'superman'+T.i+U.i на CONCAT('superman',T.i,U.i)):

--- prepare a digits table
 create table digits (i char(1));
 insert into digits (i) values ('0')
 insert into digits (i) values ('1')
 insert into digits (i) values ('2')
 insert into digits (i) values ('3')
 insert into digits (i) values ('4')
 insert into digits (i) values ('5')
 insert into digits (i) values ('6')
 insert into digits (i) values ('7')
 insert into digits (i) values ('8')
 insert into digits (i) values ('9')

--- This query returns all 'superman00' to 'superman99' records currently not used

SELECT 'superman'+T.i+U.i AS suggestedlogin
  FROM digits T cross join digits U
  WHERE 'superman'+T.i+U.i NOT IN (
    SELECT login FROM usertable
  )

(Перекрестная идея объединения от http://www.tek-tips.com/viewthread.cfm?qid=755853)

Ответ 11

Большинство этих ответов верны, но жестко запрограммировать имя пользователя в инструкции SQL.

SELECT MAX(SUBSTR(user,LENGTH('{$request}')+1))+1
FROM users
WHERE username LIKE '{$request}%'

Вернет подходящий суффикс (null, если имя пользователя уже не используется)

С.