2010-01-01 1 views
4

надеюсь, что у всех вас был счастливый новый год.Базы данных: Создание журнала действий, как обрабатывать различные ссылки?

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

User

Friend (Пользователь является другом другого пользователя, многие ко многим)

Message (Пользователь может получить сообщение другому пользователю)

Group (Внутренний абонент может находиться в различных группах)

Game (игра может быть воспроизведен с различными игроками, имеет Некоторая информация напоминает дату игры. это приводит к двум сказках, играх и games_users, последний хранит связи между пользователем и игрой)

Теперь я хотел бы сделать журнал, например:

  1. Пользователь A (ссылка на пользователя) сделал новый друг, пользователь B (ссылка на пользователь)

  2. пользователя A (ссылку на пользователь), в (ссылку на пользователь) и C (ссылка на пользователь) играла в игре (ссылку на игру)

  3. Пользователь C (ссылка для пользователя) присоединился к группе D (ссылка для группы)

Итак, я хотел создать гибкий журнал, который мог бы хранить как можно больше ссылок и ссылок на разные объекты (например, пользователь и игра).

Я знаю два способа сделать это, но все они имеют одну или несколько проблем:

  1. При входе в действие я непосредственно хранить чистый текст Я хочу (т.е. только 1 символ поля, которые магазин «Пользователь С присоединился к группе»). Но проблема такая, этот текст нужно перевести на другие языки, и у меня не может быть поля для каждого языка.

  2. Имея основную таблицу log, в которой каждая строка представляет собой действие журнала и код, поэтому я знаю, какое действие это, то есть пользователь присоединился к группе, x пользователи играли в игру. Затем у меня есть другая таблица для каждого из типов внешнего ключа, поэтому я бы получил log_user, log_group и log_game Например, log_user с полем, ссылающимся на log и другим ссылкой user. Таким образом, у меня может быть несколько пользователей для одного и того же действия с журналом. Проблемы: довольно сложные и могут привести к существенным накладным расходам в зависимости от действия журнала, которое я должен был бы запросить для нескольких таблиц. Правильно ли это, будет ли он слишком интенсивным?

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

Редактировать: Я решил начать щедрость, поскольку я не очень доволен ответами, которые я получил.При необходимости сделайте какие-либо разъяснения. Спасибо

Я хочу что-то очень похожее на facebook/orkut/social networks "friend updates". Это будет отображаться для пользователей.

ответ

2

Следующее, как я это сделаю. У меня есть еще несколько комментариев внизу, когда вы видели схему.

Вход

LogID - уникальный журнал ID

Время - дата/время проведения

LOGTYPE - Строка или ID

(сторона комментарий, я бы с идентификатором здесь, чтобы вы могли использовать таблицу сообщений, показанную ниже, но если вы хотите быстро n грязно, вы можете просто создать уникальную строку для каждого времени регистрации (например, «Игра началась», «Сообщение отправлено» и т. д.)

LogActor

LogID - внешний ключ

LogActorType - Строка или ID (как указано выше, если ID вы будете нуждаться в справочную таблицу)

LogActorID - Это уникальный идентификатор в таблице для типа, например User, Group, Game

Последовательность - это заказ актеров.

LogMessage

LOGTYPE - exernal ключ

сообщение - длинная строка (VARCHAR (макс)?)

Язык - строка (5), так что вы можете ввести от другого языка, например, "US-ан"

Пример данных (используя 3 примера)

Войдите

ID Time LogType 
1 1/1/10 1 
2 1/1/10 2 
3 1/1/10 3 

LogActor

LogID LogActorType LogActorID Sequence 
1  User   1   1 
1  User   2   2 
2  User   1   1 
2  User   2   2 
2  User   2   3 
2  Game   1   4 
3  User   3   1 
3  Group  1   2 

LogMessage

LogType Message 
1  {0} Made a new friend {1} 
2  {0}, {1}, {2} played a game ({3}) 
3  {0} joined a group ({1}) 

Пользователь

ID Name 
1 User A 
2 User B 
3 User C 

игры

ID Name 
1 Name of game 

Группа

ID Name 
1 Name of group 

Так вот хорошие вещи об этом проекте.

  • Это очень легко расширить

  • Он обрабатывает вопросы многоязычных зависит от актеров

  • Он документирован, таблица LogMessage точно объясняет , что данные, которые вы хранятся до .

Некоторые плохие вещи об этом.

  • Для чтения сообщений вам необходимо выполнить сложную обработку.

  • Вы не можете просто взглянуть на БД и посмотреть, что произошло.

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

Дайте мне знать, если у вас есть вопросы.

Update - Некоторые примеры запросов

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

Посмотреть таблицу LogActor (Есть несколько способов сделать это, лучше всего зависит от многих вещей, в том числе и распространения данных, прецедентов и т.д.) Вот два:

а)

SELECT 
    LogId, 
    COLLESCE(U.Name,Ga.Name,Go.Name) AS Name, 
    Sequence 
FROM LogActor A 
LEFT JOIN User U ON A.LogActorID = U.[ID] AND LogActorType = "User" 
LEFT JOIN Game Ga ON A.LogActorID = Ga.[ID] AND LogActorType = "Game" 
LEFT JOIN Group Go ON A.LogActorID = Go.[ID] AND LogActorType = "Group" 
ORDER BY LogID, Sequence 

б)

SELECT 
    LogId, 
    U.Name AS Name, 
    Sequence 
FROM LogActor A 
INNER JOIN User U ON A.LogActorID = U.[ID] AND LogActorType = "User" 
UNION ALL 
SELECT 
    LogId, 
    Ga.Name AS Name, 
    Sequence 
FROM LogActor A 
INNER JOIN Game Ga ON A.LogActorID = Ga.[ID] AND LogActorType = "Game" 
UNION ALL 
SELECT 
    LogId, 
    Go.Name AS Name, 
    Sequence 
FROM LogActor A 
INNER JOIN Group Go ON A.LogActorID = Go.[ID] AND LogActorType = "Group" 
ORDER BY LogID, Sequence 

В общем, я думаю) лучше, чем б) Например, если вы пропускаете тип актера) будет включать его (с нулевым именем). Однако b) легче поддерживать (поскольку утверждения UNION ALL делают его более модульным.) Существуют и другие способы сделать это (например, CTE, представления и т. Д.). Я склонен делать это как b), и из того, что я видел, кажется, по крайней мере, стандартная практика, если не лучшая практика.

Так, за последние 10 пунктов в журнале будет выглядеть примерно так:

SELECT 
    LogId, 
    M.Message, 
    COLLESCE(U.Name,Ga.Name,Go.Name) AS Name, 
    Time, 
    A.Sequence 
FROM Log 
LEFT JOIN LogActor A ON Log.LogID = A.LogID 
LEFT JOIN User U ON A.LogActorID = U.[ID] AND LogActorType = "User" 
LEFT JOIN Game Ga ON A.LogActorID = Ga.[ID] AND LogActorType = "Game" 
LEFT JOIN Group Go ON A.LogActorID = Go.[ID] AND LogActorType = "Group" 
LEFT JOIN LogMessage M ON Log.LogType = M.LogMessage 
WHERE LogID IN (SELECT Top 10 LogID FROM Log ORDER BY Date DESC) 
ORDER BY Date, LogID, A.Sequence 

NB - Как вы можете видеть, это легче выбрать все элементы журнала из даты, чем последний X, потому что для этого нам нужен (возможно, очень быстрый) подзапрос.

+0

Clash: см. Править выше. Я думаю, что это касалось большинства ваших вопросов. – Hogan

+0

Ничего себе! Я понятия не имел, что могу сделать «AND LogActorType =« Пользователь »' в предложении JOIN. Удивительно! Большое спасибо за помощь. Попробуем позже. – Clash

+0

Да, предложение JOIN велико. Не знаю, как использовать его, является причиной большинства ошибок, которые я вижу на SO (часто используя подзапрос вместо соединения). – Hogan

1
  1. Держите это простой и расширяемый
  2. Не позволяй перевод накладных расходов влияет на производительность, перевод должно быть сделано для вывода целей только.

Предложение:

LogId  DateTime Action Role Entity 

e.g. 

30303  1/1/10 43  Sender John 
30303  1/1/10 43  Receiver Sam 
30304  1/1/10 44  Game  game43 
30304  1/1/10 44  Player Sue 
30304  1/1/10 44  Player Mike 

(В приведенной выше таблице, "Message", "Отправитель", "Джон", "game43" и т.д. не будет текста, но было бы внешние ключи в любом таблица действий, роли или сущности. Я написал ключи для «Action», но не для «Role» или «Entity», но они также будут ключами.

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

Action Table 

Id ActionKey Text  Language 
1  43   JoinGame English 
2  43   Jeu  French 
3  44   Message English 
... 
... 

Role Table 
Id RoleKey Text Language 
1 1  Sender English 
2 1  Sendeur French  (I don't know french :) 
.... 

Entity Table 
EntityKey Text 
1   Sam 
2   game43 
3   Sam 

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

Person Table 
Id EntityKey FirstName LastName .... 
1  1   Sam   Johnson 

Game Instance Table 
Id EntityKey GameType 
1  2   444 

Game Table 
Id Name MaxPlayers ... 
444 Quake 10 

В основном мы картографирования предикат (в первом порядка логики предикатов смысле, набор кортежей) в двоичной форме отношения (путем создания искусственного лица, ключ действие). Таким образом, таблица Entity в основном содержит различные столбцы/аргументы отношения, и поэтому это может быть практически все, что может быть аргументом отношения. Преимущество этого представления заключается в том, что он бесконечно расширяется до новых отношений, которые вы, возможно, захотите записать без изменения схемы. Все в ActionTable должно быть ключевым или внешним ключом, я просто не помещал его туда, потому что его может быть труднее прочитать.

+0

Привет Ларри, спасибо за ваш ответ! На вашем примере, как я узнаю, что сообщение было отправлено от Джона к Сэму? Будут другие строки с ролью «Отправитель» и «Получатель». Как объект может ссылаться на пользователя, а в некоторых случаях на игру? Спасибо – Clash

+0

Хорошая точка. Таблице нужен столбец LogId, чтобы идентифицировать все записи для определенной записи в журнале, поскольку запись может охватывать несколько строк. Я изменил исходное сообщение, чтобы включить этот столбец. –

+0

Ларри, поэтому вы предлагаете мне не использовать внешние ключи для сущностей? Потому что кажется, что поле вашего объекта может иметь игру и пользователей – Clash

1

Вам это нужно для ведения журнала/отслеживания или для отображения пользователям и администраторам? Если вы используете для ведения журнала/отслеживания (например, для чтения на компьютере), вам, вероятно, следует отделить регистрацию на несколько таблиц, как вы указали.

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

Например, "Пользователь A (ссылка на пользователя), B (ссылка на пользователя) и C (ссылка на пользователя) играл в игру (ссылка на игру)" будет

<a href="https://stackoverflow.com/users/showuser.php?id=2341">User A</a> 
, <a href="https://stackoverflow.com/users/showuser.php?id=311">User B</a> 
, and <a href="https://stackoverflow.com/users/showuser.php?id=89">User C</a> 
played a game of <a href="/games/gameoverview.php?id=3">Chess</a>. 
+0

Привет, спасибо за ответ Lucky. Проблема с хранением прямого HTML заключается в том, что его нужно перевести – Clash

+0

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

+0

Что? Парень французский и не знает английского, он никогда не поймет «сыграл в игру» – Clash

2

Мой ответ Whats a better strategy for storing log data in a database?:

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

С другой стороны, для аудита след цели, нет ничего, как с дубликат таблицы для всех таблиц с каждый CRUD действия.Таким образом, каждая информация, полученная в таблице , будет занесена в таблицу .

Итак, ответ - оба.

Редактировать: Для того, чтобы осуществить это чисто с ссылочной целостностью и имеет всю гибкость, я предлагаю дублированную таблицу контрольного журнала для всех CRUDs для каждой таблицы, даже если это «тяжелое». В любом случае бизнес-правила более волатильны по сравнению с структурами данных, поэтому, сохраняя логику логики в коде/запросе, вы сохраняете гибкость. Например, предположим, что вы решили не отслеживать, когда пользователи покинули группу. Позже, клиенты спросили, что очень важно отслеживать информацию. Все, что вам нужно сделать, это изменить запрос, поэтому удаление записи user_group является частью результата.

+0

Спасибо за ваш ответ! Я не вижу, как одна таблица может удовлетворить все мои потребности, ее нужно перевести и содержать ссылки. Наличие таблицы для каждого типа действия журнала было бы слишком тяжелым для запросов к показу для посетителей сайта? Я имею в виду, мне нужно будет запросить все таблицы для создания журнала последних действий определенного пользователя. – Clash