Разработка базы данных: соответствие ключей базы данных sql константам php? - PullRequest
9 голосов
/ 19 августа 2010

Ну, это простой вопрос о дизайне, о котором я много раз задумывался и никогда не находил удовлетворительного решения. Мой пример с php-sql, но это, безусловно, относится и к другим языкам.

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

usertype_id (primary key)  | name       | description
---------------------------+------------+-------------------
1                          | 'admin'    | 'Administrator'
2                          | 'reguser'  | 'Registered user'
3                          | 'guest'    | 'Guest'

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

1) Выберите * из таблицы пользовательских типов при создании экземпляра класса и сохраните его в массиве.
Тогда все идентификаторы доступны для кода, и я могу сделать простой выбор, чтобы получить нужные мне строки. Это решение требует массив и запрос базы данных каждый раз, когда создается экземпляр класса.

$query = "SELECT info, foo FROM user WHERE usertype_id = ".$usertypes['admin'];

2) Используйте столбец name, чтобы выбрать правильный usertype_id, чтобы мы могли эффективно объединяться с другими таблицами. Это более или менее эквивалентно 1), но без необходимости кэшировать всю таблицу пользовательских типов в объекте php:

$query = "SELECT info, foo FROM user JOIN usertype USING (usertype_id) WHERE usertype.name = 'admin' ";

3) Определите константы, которые соответствуют ключам в таблице пользовательских типов:

// As defines
define("USERTYPE_ADMIN",1);
define("USERTYPE_REGUSER",2);

//Or as class constants
const USERTYPE_ADMIN = 1;
const USERTYPE_REGUSER = 2;

А затем просто выберите.

$query = "SELECT info, foo FROM user WHERE usertype_id = " . USERTYPE_ADMIN;

Это, вероятно, наиболее эффективное с точки зрения ресурсов решение, но его плохо обслуживать, поскольку вам нужно обновить и таблицу, и код, если вам нужно что-то изменить в таблице пользовательских типов.

4) Удалите таблицу usertype и сохраните только те типы, которые указаны в коде php. Мне это не очень нравится, потому что оно позволяет любому значению попадать в базу данных и назначаться типу пользователя. Но, может быть, учитывая все обстоятельства, это не так уж плохо, и я просто усложняю то, что должно быть простым ..

В любом случае, если подвести итог, то решение, которое мне больше всего нравится, это # ​​2, потому что оно связное и с индексом на usertype.name, не может быть таким плохим. Но я часто заканчивал тем, что использовал # 3 для эффективности.

Как бы вы это сделали? Есть лучшие решения?

(редактировать: исправлен запрос в # 2)

Ответы [ 10 ]

1 голос
/ 24 ноября 2011

Я бы предложил # 3 , чтобы избежать бесполезных запросов и предотвратить риск изменения поведения, если случайно изменились существующие строки таблицы БД:

  • Добавление необходимых константв классе модели:

    class Role // + use namespaces if possible
    {
      // A good ORM could be able to generate it (see @wimvds answer)
      const ADMIN = 1;
      const USER = 2;
      const GUEST = 3;
    
      //...
    

    }

  • Тогда имеет смысл запрос подобным образом:

    $query = "SELECT info, foo FROM user WHERE role_id = ".Role::ADMIN;
    

    С ORM (например, Propel в приведенном ниже примере) вы в конечном итоге выполните:

    $isAdminResults = UserQuery::create()->filterByRoleId(Role::ADMIN);
    
1 голос
/ 20 сентября 2010

Я почти всегда выбираю вариант 3).Вы можете сгенерировать необходимый код автоматически на основе того, что доступно в БД.Единственное, что вы должны помнить, это то, что вам нужно запустить скрипт для обновления / перезаписи этой информации, когда вы добавляете другую роль (но если вы используете phing или похожий инструмент сборки для развертывания своих приложений, просто добавьте правило сборкидля этого в вашем сценарии развертывания, и он всегда будет запускаться всякий раз, когда вы развертываете свой код: p).

0 голосов
/ 24 ноября 2011

Я бы выбрал вариант № 2 и использовал бы соединение так, как оно предназначено для использования. Никогда не знаешь, что будет в будущем, лучше быть готовым сегодня!

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

Примечание:

(LStore, LFetch, GetFileName) принадлежат объекту Cache, который вызывается статически.

(Blobify и Unblobify) принадлежат объекту SystemComponent, который всегда жив

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

public function LStore($key,$data, $blnBlobify=true) {
    /* Opening the file in read/write mode */
    $h = fopen(self::GetFileName($key, 'longstore'),'a+');
    if (!$h) throw new Exception('Could not write to cache');
    flock($h,LOCK_EX); // exclusive lock, will get released when the file is closed
    fseek($h,0); // go to the start of the file
    /* truncate the file */
    ftruncate($h,0);
    if($blnBlobify==true) { $data = SystemComponent::Blobify(array($data)); }
    If (fwrite($h,$data)===false) {
        throw new Exception('Could not write to cache');
    }
    fclose($h);
}   
public function LFetch($key) {
    $filename = self::GetFileName($key, 'longstore');
    if (!file_exists($filename)){ return false;}
    $h = fopen($filename,'r');
    if (!$h){ return false;}
    /* Getting a shared lock */
    flock($h,LOCK_SH);
    $data = file_get_contents($filename);
    fclose($h);
    $data = SystemComponent::Unblobify($data);
    if (!$data) {
        /* If unserializing somehow didn't work out, we'll delete the file */
        unlink($filename);
        return false;
    }
    return $data;
}
/* This function is necessary as the framework scales different directories */

private function GetFileName($key, $strCacheDirectory='') {
    if(!empty($strCacheDirectory)){ 
        return SystemComponent::GetCacheAdd() . $strCacheDirectory.'/' .  md5($key); 
    } else {    
        return SystemComponent::GetCacheAdd() . md5($key);
    }
}
public function Blobify($Source){
    if(is_array($Source)) { $Source = serialize($Source); }

    $strSerialized = base64_encode($Source);

    return $strSerialized;

}
public function Unblobify($strSerialized){
    $Decoded = base64_decode($strSerialized);
    if(self::CheckSerialized($Decoded)) { $Decoded = unserialize($Decoded); }

    return $Decoded;    

}
function CheckSerialized($Source){
    $Data = @unserialize($Source);
    if ($Source === 'b:0;' || $Data !== false) {
        return true;
    } else {
        return false;
    }
}  

Теперь, когда дело доходит до доступа к фактическим данным, я просто вызываю выборку. Чтобы удостовериться, что это актуально, я говорю это хранить. В вашем случае это будет после обновления таблицы пользовательских типов.

0 голосов
/ 20 ноября 2011

Да, вы правы, избегая # 3 и придерживаясь # 2.По мере возможности поиски, например, когда вы используете таблицу пользовательских типов для хранения ролей, а затем связываете их с пользовательской таблицей, используя значения идентификаторов, должны оставаться в базе данных.Если вы используете константы, то данные всегда должны полагаться на ваш php-код для интерпретации.Кроме того, вы можете обеспечить целостность данных с помощью внешних ключей (если это позволяют серверы), и это позволит вам переносить отчеты из вашего php-кода в другие инструменты отчетности.Обслуживание также становится проще.Администраторам баз данных не нужно знать php, чтобы получить значения чисел, если вы используете № 3, если их когда-либо попросят помочь в разработке отчетов.Это может показаться не слишком уместным, но с точки зрения обслуживания использование хранимых процедур, а не встроенного sql в вашем php-коде, также было бы удобным в обслуживании несколькими способами, а также было бы выгодно для администраторов баз данных.

0 голосов
/ 12 ноября 2011

Для статических таблиц поиска я обычно создаю статические постоянные файлы (например, ваш # 3). Я обычно использую такие классы, как:

namespace Constants;
class UserTypes {
    const ADMIN = 1;
    const USER = 2;
    const GUEST = 3;
}

$id = Constants\UserTypes::ADMIN;

Когда я использую поисковые запросы, которые немного более переменны, я помещаю их в объект и затем кэширую в течение 24 часов. Таким образом, он обновляется только один раз в день. Это избавит вас от обходов базы данных, но позволит вам легко справляться с вещами в коде.

0 голосов
/ 20 сентября 2010

Вам не нужно использовать JOIN в каждом запросе для получения информации о типах / ролях. Вы можете хранить свою модель «пользователя» и «ролевые» модели отдельно в объектах доступа к данным (DAO) - тем более, что существует очень мало записей для пользовательских типов.

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

$user = DAO_User::get(1); // this pulls a JOIN-less record
$role = $user->getRole(); // lazy-load

Код для $ user-> getRole () может выглядеть примерно так:

public function getRole() { 
  // This comes from a cache that may be called multiple 
  // times per request with no penalty (i.e. store in a registry)
  $roles = DAO_UserRoles::getAll();

  if(isset($roles[$this->role_id]))
    return $roles[$this->role_id];

  return null; // or: new Model_UserRole();
}

Это также работает, если вы хотите отобразить список с 1000 пользователями. Вы можете просто отобразить значения для этого столбца из одного ассоциативного массива $ role.

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

Даже при наличии более 100 строк по обе стороны от JOIN вы все равно можете использовать подход с отложенной загрузкой для нечастой или избыточной информации. При наличии разумной службы кэширования в вашем коде нет штрафов за многократный вызов DAO_UserRole :: get (1500), поскольку последующие вызовы во время одного и того же запроса не должны попадать в базу данных дважды. В большинстве случаев вы будете отображать только 10-25 строк на страницу из 1000 с, и ленивая загрузка избавит ядро ​​базы данных от необходимости присоединяться ко всем посторонним строкам, прежде чем они вам действительно понадобятся.

Основная причина создания JOIN заключается в том, требуется ли ваша логика WHERE или если вам нужно ORDER BY данных из внешнего ключа. Относиться к СОЕДИНЕНИЮ как к чрезмерно дорогой привычке.

0 голосов
/ 27 августа 2010

Почему бы не сделать это просто

foreach (getdbarr("SELECT * FROM usertype") as $row)  {
  define($row['name'],$row['id']);
}
0 голосов
/ 26 августа 2010

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

0 голосов
/ 19 августа 2010

Для таблиц, которые будут содержать значения типа, особенно когда ожидается, что такая таблица со временем изменится, я склонен использовать простой подход: добавить столбец Varchar с именем hid (происходит от «читаемого человеком идентификатора») с уникальным ключом.Затем я заполняю его идентификатором, значимым для людей, таким как:

usertype_id (primary key)  | name       | description       | hid (unique key)
---------------------------+------------+-------------------+---------------
1                          | 'admin'    | 'Administrator'   | 'admin'
2                          | 'reguser'  | 'Registered user' | 'user'
3                          | 'guest'    | 'Guest'           | 'guest'

Когда вам нужен фактический идентификатор, вам нужно будет сделать выбор на основе столбца hid, то есть

select usertype_id from tablename where hid = "admin"

Это неэффективный подход, но он обеспечит совместимость вашего приложения между различными развертываниями (т. е. один клиент может иметь 1.admin, 2. guest; другой клиент 1.admin, 2. user и т. д.).Для вашего случая я думаю, что # 3 вполне подходит, но если вы ожидаете иметь более 10 различных ролей пользователей - попробуйте «скрытый» подход.

0 голосов
/ 19 августа 2010

Почему бы не денормализовать таблицу БД, чтобы вместо usertype_id вы получили usertype со строковым типом (admin).Тогда в PHP вы можете просто сделать define('USERTYPE_ADMIN', 'admin');.Это избавляет вас от необходимости изменять два места, если вы хотите добавить тип пользователя ...

И если вы действительно беспокоитесь о получении какого-либо значения, вы всегда можете сделать столбец данными ENUMтипа, чтобы он сам управлял ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...