Запрос предложения входа - PullRequest
12 голосов
/ 15 января 2010

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

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

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

UPD : предлагается вознаграждение

Ответы [ 11 ]

9 голосов
/ 15 января 2010

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

6 голосов
/ 19 января 2010

Запросите подсказку , например:

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

  1. ваша фамилия - например. Смитсон - что бы предложить "joe.smithson"
  2. ваш город проживания - например. Район залива - который предлагает "joseph_bayarea"
  3. цель счета - например. разработчик - который предложил бы "joseph-devel"
  4. цвет - например. синий - что означало бы "bluejoe"
  5. число - к которому добавляется суффикс "joe99"

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

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

Другой способ решения этой проблемы - увидеть код, скрывающийся за «интеллектуальными» капчами, которые генерируют сайты типа Slashdot . У некоторых остроумных devel ;-) есть куча слов семантически , связанных с данной темой, и они используют эти фразы для ввода капчи.

Эта интеллектуальная / интеллектуальная капча немного похожа на Google Sets .

Coding Horror также иногда показывал эти умные catpchas.

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

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

Вы можете получить его, если выглядишь достаточно усердно в Интернете.
Вы можете начать с синонимов / антонимов и т. Д.
IIRC, одним из них является Wordnet, но я не знаю лицензию. Так что ищи это.


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

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

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

4 голосов
/ 21 января 2010

Вот мой путь к этому:

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-запрос в свете первого комментария Клаусбыскова:

SELECT `login`
  FROM `usertable`
WHERE `login` RLIKE '^Superman[0-9]*$'
ORDER BY `login` DESC
LIMIT 1;
4 голосов
/ 21 января 2010

Пользователь 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


Редактировать

Предположим, что таблица называется 'users', а рассматриваемое поле называется 'username', возможный фрагмент кода следующий:

/**
 * 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 '';
}

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

3 голосов
/ 15 января 2010

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

select login from usertable where login = 'Superman';

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

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

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

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

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

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

2 голосов
/ 20 января 2010

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

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

Если username_suffix равен 0, он не отображается. то есть 'superman' и 'superman0' эквивалентны.

Вы можете просто

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

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

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

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

например.

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

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


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

2 голосов
/ 19 января 2010

Вот мое мягкое решение: добавьте один столбец varchar (называемый, например, username_string_part) в вашу таблицу пользователей, чтобы сохранить строковые части имени пользователя, и второй столбец int (например, username_number_part), чтобы сохранить числовую часть.Таким образом, superman1 делится на «superman» в столбце 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;

(Обратите внимание, что для имени пользователя "superman" по умолчанию установлено значение 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 значения ""плюс один "в базе данных.Таким образом, для имени пользователя "superman":

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.Вы можете сделать конкат в запросе или нет, как вам нравится.Используя приведенные выше примеры данных, вы получите возвращаемое значение «2».

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

Перевернутые стороны: это один запрос к индексированным столбцам.

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

2 голосов
/ 19 января 2010

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

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

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

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

1 голос
/ 23 января 2010

В приведенном ниже запросе используется вспомогательная таблица с 10 записями (цифры от 0 до 9) и перекрестное соединение для создания списка строк от 00 до 99. Эти значения объединяются с выбранным пользователем логином («superman»), и результат проверяется на 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 )

1 голос
/ 15 января 2010

Согласно комментариям к вопросу, желателен фиксированный диапазон 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 с?

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

...