Основы SQL. Часть 1.

Сообщения
51
Реакции
-24
Обратите внимание, если вы хотите заключить сделку с этим пользователем, он заблокирован
На счёт DATETIME & TIMESTAMP (#2, #3) в статье почти не описано, однако стоит учитывать их формат.
TIMESTAMP - Хранит 4-байтное целое число, равное количеству секунд, прошедших с полуночи 1 января 1970 года (10 знаковое число на данный момент если я не ошибаюсь)
DATETIME - Хранит время в виде целого числа вида YYYYMMDDHHMMSS, используя для этого 8 байтов
 
Последнее редактирование:
Сообщения
496
Реакции
621
Помог
16 раз(а)
На счёт DATETIME & TIMESTAMP (#2, #3) в статье почти не описано, однако стоит учитывать их формат.
Я хотел изначально, но пришлось отказаться, так как уже картинки долго грузятся. Всё впихнуть не получается.
Оставил дату на 2 часть, там рассказывать особо не о чем. Сейчас работать со временем просто, довели типы до ума.
 
Сообщения
1,032
Реакции
828
Помог
10 раз(а)
Добрый день. Впервые изучаю базы данных, скопилось не мало вопросов, скорее всего довольно глупых, но тем не менее, для общей ясности картины хотелось бы их уточнить, что бы двигаться дальше.

1. `id` int(11) NOT NULL AUTO_INCREMENT, Если поле инкрементное, то, как я понимаю можно и не указывать ему NOT NULL ибо оно и так не будет пустым никогда, верно?

2. `bonus_count` int(11) NOT NULL DEFAULT '0', Точно так же и здесь, если есть дефолтное значение, то указывать NOT NULL необязательно, верно?

3. PRIMARY KEY, Первичный ключ, как я понимаю это поле не должно быть пустым и обязано быть уникальным, но надо ли обязательно указывать эти параметры для этого поля с первичном ключом? Или оно по умолчанию примит такие параметры и не даст создать запись дубликат или пустое значение?

4. Есть ли смысл для поля ID(уникальное, первичный ключ) указывать ему unsigned, вопрос риторический наверное, в базе вряд ли будет больше 2 млрд значения поля ID именно для CS.

5. ENGINE=InnoDB Не совсем понял, что это за параметр такой, стоит ли указывать его для работы с sqlite

6. DEFAULT CHARSET=utf8, Как я понимаю, в наше время использовать такую кодировку желательно, для работы, даже, с теме же русскими никнеймами.

7. AUTO_INCREMENT=1 По дефолту отсчет идет с нуля, как я понимаю?

8. INDEX (`steamid`) Обязательно ли индексировать поле, реально ли увеличивается поиск по этому полю?

9. Обязательно ли подключаться к базе данных SQL_Connect при инициализации плагина, говорят так будет эффективнее работать с базой на протяжении всей карты. Но, если я не подключусь к ней, а выполню простой запрос, подключение к базе ведь выполниться автоматически, как я понимаю? И подключение так и будет висеть до конца карты или пока я сам не закрою его.

C++:
public plugin_cfg() {
    if(!file_exists(STATS_DATABASE_DIR)) {
        new iFileID = fopen(STATS_DATABASE_DIR, "a");
        fclose(iFileID);
    }
 
    SQL_SetAffinity("sqlite");
    g_SQLTuple = SQL_MakeDbTuple("", "", "", STATS_DATABASE_DIR);
 
    formatex(g_SQLQuery, charsmax(g_SQLQuery),
        "CREATE TABLE IF NOT EXISTS    `stats`( \
        ID            INTEGER        AUTO_INCREMENT, \
        AuthID        TEXT        PRIMARY KEY, \
        Name        TEXT        NOT NULL, \
        Frags        INTEGER        DEFAULT '0', \
        Deaths        INTEGER        DEFAULT '0')"
    );
 
    SQL_ThreadQuery(g_SQLTuple, "QueryHandleCreate", g_SQLQuery);
}
Подключения не производил, но запрос выполнился, получается подключение произвелось автоматически, но вопрос только в том, останется ли висеть это подключение или будет закрываться автоматически после каждого запроса, если да, то будет правильнее подключиться сразу при инициализации плагина и держать открытым это подключение.

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

C++:
g_SQLTuple = SQL_MakeDbTuple("", "", "", STATS_DATABASE_DIR);
g_SQLConnect = SQL_Connect(g_SQLTuple, i_ErrNo, s_Error, charsmax(s_Error))

public plugin_end() {
SQL_FreeHandle(g_SQLConnect )
SQL_FreeHandle(g_SQLTuple )
}
11. Тоже самое касается запросов, как я понимаю, после выполнение запроса он будет висеть до тех пор, пока не закроешь сам, то есть желательно перед новым запросом закрыть старый SQL_FreeHandle(Query) или я не прав? И еще вопрос касаемо завершение карты, запрос закроется автоматически или тоже желательно его закрывать

C++:
public plugin_end() {
SQL_FreeHandle(Query)
}
 
Сообщения
2,491
Реакции
2,794
Помог
61 раз(а)
Javekson,
1. NOT NULL означает что поле обязательное при INSERT операции. Автоинкремент сам выставляет значение, посему пишут просто NULL.
2. Ответ выше.
3. https://dev.mysql.com/doc/refman/8.0/en/create-table.html
A unique index where all key columns must be defined as NOT NULL. If they are not explicitly declared as NOT NULL, MySQL declares them so implicitly (and silently). A table can have only one PRIMARY KEY. The name of a PRIMARY KEY is always PRIMARY, which thus cannot be used as the name for any other kind of index. If you do not have a PRIMARY KEY and an application asks for the PRIMARY KEY in your tables, MySQL returns the first UNIQUE index that has no NULL columns as the PRIMARY KEY.
24 Фев 2019
4. Да.
5. Это тип таблицы в mysql. В sqlite его нет
6. Конкретно в mysql utf8 - 3 байты, что не покрывает все символы, а лишь основные. Нужно использовать utf8mb4 в mysql. В sqlite не знаю.
7. С 1
8. Да обязательно. Но не как попало. Нужно смотреть результат EXPLAIN.
9. Нет не надо от слова совсем. SQL_Connect создает коннект для работы с синхронными запросами. При SQL_ThreadQuery он не то чтобы не обязателен, он лишний, так как SQL_ThreadQuery внутри себя делает коннект отдельно в потоке независимо от SQL_Connect.
10. Должен самостоятельно. Я же всегда сам все закрываю.
11. Ответ выше

P.S. Нужно учитывать разницу в mysql и sqlite. То что доступно в одном, не доступно во втором.
 
Сообщения
64
Реакции
131
Помог
4 раз(а)
К INSERT я бы добавил пример как добавлять несколько строк одним запросом
SQL:
INSERT aes_test(name,exp,bonus_count,steamid) VALUES ('Вася',50000,200,'STEAM_3:0:2030175304'), ('Петя',50000,200,'STEAM_2:0:2030175304')
А так же добавил ON DUPLICATE KEY UPDATE - обновление данных, если есть строка с уникальным значением
SQL:
INSERT aes_test(name,exp,bonus_count,steamid) VALUES ('Вася',50000,200,'STEAM_3:0:2030175304') ON DUPLICATE KEY UPDATE exp=exp+VALUES(exp)
Если steamid уникально и в базе есть игрок с то стимом STEAM_3:0:2030175304, то ему прибавится 50000 exp, без exp+ ему просто выставится 50000

Таким запросом можно обновлять статистику всем игрокам, к примеру раз в N минут
SQL:
INSERT INTO `stats` (`steamid`, `frags`, `deaths`) VALUES
('STEAM_0:1:123', 10, 1),
('STEAM_0:1:321', 1, 10)
ON DUPLICATE KEY UPDATE `frags`=`frags`+VALUES(`frags`), `deaths`=`deaths`+VALUES(`deaths`)
 
Последнее редактирование:
Сообщения
1,032
Реакции
828
Помог
10 раз(а)
Либо sqlite все таки отличается, либо я не пойму ошибки.
C++:
    formatex(g_SQLQuery, charsmax(g_SQLQuery),
        "CREATE TABLE IF NOT EXISTS    `stats`( \
        ID            INTEGER        NOT NULL AUTO_INCREMENT, \
        AuthID        TEXT        PRIMARY KEY, \
        Name        TEXT        NOT NULL, \
        Frags        INTEGER        DEFAULT '0', \
        Deaths        INTEGER        DEFAULT '0')"
    );
"25.02.2019 - 22:17:30" "SQL QueryHandleCreate error: near "AUTO_INCREMENT": syntax error"

Уберу NOT NULL норм все.

Хотя по мануалу такой синтаксис возможен `id` int(11) NOT NULL AUTO_INCREMENT,
 
Сообщения
496
Реакции
621
Помог
16 раз(а)
Они все маленько различаются. Я как-то больше про mssql) Может и отличаются мелочами.
Например, есть в mssql команда top. а в mysql она же называется limit и пишется в конце.

SELECT TOP(1) Model FROM Cars
аналог
SELECT Model FROM Cars LIMIT 1

NOT NULL AUTO_INCREMENT очень люблю писать в ключе. По сути на всякий случай, но такое... насмотрелся.
25 Фев 2019
К INSERT я бы добавил пример как добавлять несколько строк одним запросом
SQL:
INSERT aes_test(name,exp,bonus_count,steamid) VALUES ('Вася',50000,200,'STEAM_3:0:2030175304'), ('Петя',50000,200,'STEAM_2:0:2030175304')
А так же добавил ON DUPLICATE KET UPDATE - обновление данных, если есть строка с уникальным значением
SQL:
INSERT aes_test(name,exp,bonus_count,steamid) VALUES ('Вася',50000,200,'STEAM_3:0:2030175304') ON DUPLICATE KEY UPDATE exp=exp+VALUES(exp)
Если steamid уникально и в базе есть игрок с то стимом STEAM_3:0:2030175304, то ему прибавится 50000 exp, без exp+ ему просто выставится 50000

Таким запросом можно обновлять статистику всем игрокам, к примеру раз в N минут
SQL:
INSERT INTO `stats` (`steamid`, `frags`, `deaths`) VALUES
('STEAM_0:1:123', 10, 1),
('STEAM_0:1:321', 1, 10)
ON DUPLICATE KEY UPDATE `frags`=`frags`+VALUES(`frags`), `deaths`=`deaths`+VALUES(`deaths`)
1. добавить несколько строк интсертом логично. Согласен.
2. ON DUPLICATE KET UPDATE неплохо, но не уверен что надо на таком уровне. Просто сам таким очень редко пользовался.

Спс в любом случае, но я боюсь статью трогать) Там уже картинки и код и так плохо грузятся.

А строго говоря рекомендую всем желающим труд Мартина Грабера "Введение в SQL".
 
Сообщения
2,491
Реакции
2,794
Помог
61 раз(а)
Либо sqlite все таки отличается
Есть общий синтаксис SQL. Но все SQL базы имеют те или иные отличия между собой
Пока искал информацию (я больше по mysql), наткнулся на вот это http://www.sqlitetutorial.net/sqlite-autoincrement/
The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed.
И реально увидел в документации что не надо создавать автоинкремент поле. Оно создается автоматически. https://www.sqlite.org/autoinc.html. Но подтверждение цитаты выше я не нашел. Мб плохо искал.
25 Фев 2019
Еще одна ссилка https://www.sqlite.org/lang_createtable.html#rowid
 
Сообщения
82
Реакции
83
Помог
5 раз(а)
1. добавить несколько строк интсертом логично. Согласен.
В Оралке такой подход не работает, либо копипастишь полный запрос, либо в цикле, либо с помощью виртуальных таблиц.
AUTO_INCREMENT - опять же, в Оракле до последних версий не было такого понятия, для инкрементного счетчика писался сиквенс.
Опять же, разные СУБД имеют море своих "фич", которых нет в других СУБД, и\или нет в нативном SQL.
Те же LIMIT\OFFSET и прочий сахар. Поэтому, думаю что стоит ограничиться тем, что будет работать на всех СУБД (читай нативный SQL только описывать). А если рассматривать чуть более детально, то уже следует делать акцент на определенную СУБД.
 
Сообщения
1,032
Реакции
828
Помог
10 раз(а)
Похоже в sqlite INDEX (`steamid`) понятие индекса тоже отсутствует, ибо выдает неверный синтаксис.
Да и вопрос еще в том, стоит ли действительно закрывать запрос после его выполнения, ибо смотрю код других разработчиков, никто его не закрывает, хотя я закрываю таким образом. Если еще правильно закрываю вообще =DD

C++:
public QueryHandleCreate(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime) {
    if(failstate != TQUERY_SUCCESS) {
        logging(g_sLogsDir, "stats_", "^"SQL QueryHandleCreate error: %s^"", error);
    }
    SQL_FreeHandle(query);
}
Пока общий код таков

C++:
#include <amxmodx>
#include <sqlx>

#pragma loadlib sqlite

#pragma semicolon 1

new const STATS_DATABASE_DIR[] = "addons/amxmodx/data/stats.db";

const QUERY_STRLEN            = 256;
const PATH_STRLEN            = 64;
const MESSAGE_STRLEN        = 512;
const TIME_STRLEN            = 32;
const ELLIPSES_ARG            = 4;

new Handle:g_SQLTuple, g_SQLQuery[QUERY_STRLEN];

new g_sLogsDir[PATH_STRLEN];

public plugin_init() {
    register_plugin("Stats", "2.0", "Javekson");
    register_clcmd("say /test", "ClCmdSQLTest");
}

public plugin_cfg() {
    get_localinfo("amxx_logs", g_sLogsDir, charsmax(g_sLogsDir));
  
    if(!file_exists(STATS_DATABASE_DIR)) {
        new iFileID = fopen(STATS_DATABASE_DIR, "a");
        fclose(iFileID);
    }
  
    SQL_SetAffinity("sqlite");
    g_SQLTuple = SQL_MakeDbTuple("", "", "", STATS_DATABASE_DIR);
  
    formatex(g_SQLQuery, charsmax(g_SQLQuery),
        "CREATE TABLE IF NOT EXISTS    stats( \
        AuthID        TEXT        PRIMARY KEY, \
        Name        TEXT        NOT NULL, \
        Frags        INTEGER        DEFAULT 0, \
        Deaths        INTEGER        DEFAULT 0)");
  
    SQL_ThreadQuery(g_SQLTuple, "QueryHandleCreate", g_SQLQuery);
}

public plugin_end() {
    SQL_FreeHandle(g_SQLTuple);
}

public QueryHandleCreate(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime) {
    if(failstate != TQUERY_SUCCESS) {
        logging(g_sLogsDir, "stats_", "^"SQL QueryHandleCreate error: %s^"", error);
    }
    SQL_FreeHandle(query);
}

public ClCmdSQLTest() {
    formatex(g_SQLQuery, charsmax(g_SQLQuery), "INSERT INTO `stats`(AuthID, Name) VALUES ('STEAM:1.5:01212121', 'Javekson')");
    SQL_ThreadQuery(g_SQLTuple, "QueryHandleInsertTest", g_SQLQuery);
}

public QueryHandleInsertTest(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime) {
    if(failstate != TQUERY_SUCCESS) {
        logging(g_sLogsDir, "stats_", "^"SQL QueryHandleInsertTest error: %s^"", error);
    }
    SQL_FreeHandle(query);
}

stock logging(const sLogsDir[], const sFileName[], const sMessage[], any:...) {
    new sFmtMsg[MESSAGE_STRLEN], sTime[TIME_STRLEN], sLogFile[PATH_STRLEN + 32], iFileID;
    vformat(sFmtMsg, charsmax(sFmtMsg), sMessage, ELLIPSES_ARG);
    get_time("%m.%Y.log", sTime, charsmax(sTime));
    formatex(sLogFile, charsmax(sLogFile), "%s/%s%s", sLogsDir, sFileName, sTime);
    iFileID = fopen(sLogFile, "at");
    get_time("%d.%m.%Y - %H:%M:%S", sTime, charsmax(sTime));
    fprintf(iFileID, "^"%s^" %s^n", sTime, sFmtMsg);
    fclose(iFileID);
}
 
Сообщения
1,032
Реакции
828
Помог
10 раз(а)
Если писать статистику игроков, то, как сказал fantom при смене карты могут возникнуть проблемы, мол не все запросы могут успеть выполниться. Это если обновлять данные при дисконнекте.

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

Может хранить данные игроков в файле обычном? И при старте карты уже обновлять из файла в базу.
А в плугин_енд сохранить всех игроков в файл(по сути должна успеть сохранить всех)
Речь про SQLite
 
Сообщения
1,293
Реакции
2,294
Помог
57 раз(а)
Javekson, С чего при работе со SQLite возникнут проблемы? Это ж локальная БД, хранящаяся на самом кс-сервере, в amxmodx/data/sqlite. Это при работе с MySQL может оборваться коннект, и данные просто не дойдут. Если делать под себя, с условием, что карта не меняется, то сохранять надо по таймеру + при дисконнекте. Таймер можно сделать индивидуальным, либо глобальным (один для всех). Я бы выбрал именно глобальный, и сохранял инфу "потоком" (большой буфер, в который форматируются запросы до момента, пока в нём хватает места, затем происходит отправка, и начинается форматирование с начала). Пример у фримена в стате есть.
 
Сообщения
2,491
Реакции
2,794
Помог
61 раз(а)
С чего при работе со SQLite возникнут проблемы
те же самые что и пры mysql. Не важно интегрированя Бд или нет. Проблема будет конешно намного реже. Но гарантий 100% что ее не будет нет
1 Мар 2019
Может хранить данные игроков в файле обычном? И при старте карты уже обновлять из файла в базу.
немного попиарюсь https://dev-cs.ru/resources/457/
Суть такова. Хукать дроп клиент вместо client_disconnected. При смене карты записивать в модуль все данные. После смены делать запросы на запись. Но и тут не все так гладко
 
Сообщения
1,032
Реакции
828
Помог
10 раз(а)
fantom, а каждый фраг сохранять это слишком жестко уже? =DD
 
Сообщения
2,491
Реакции
2,794
Помог
61 раз(а)
Javekson, думаю да. Но надо смотреть индивидуально
 

Пользователи, просматривающие эту тему

Сейчас на форуме нет ни одного пользователя.
Сверху Снизу