Первый раз дизайн базы данных: я перерабатываю? [закрыто]

246

Задний план

Я студент первого курса CS, и я работаю неполный рабочий день для малого бизнеса моего отца. У меня нет никакого опыта в разработке приложений реального мира. Я написал сценарии на Python, некоторые курсовые работы на C, но ничего подобного.

У моего отца небольшой учебный бизнес, и в настоящее время все занятия планируются, записываются и отслеживаются с помощью внешнего веб-приложения. Существует функция экспорта / «отчетов», но она очень общая, и нам нужны конкретные отчеты. У нас нет доступа к фактической базе данных для выполнения запросов. Меня попросили настроить систему отчетности.

Моя идея состоит в том, чтобы создать общий экспорт CSV и импортировать их (возможно, с помощью Python) в базу данных MySQL, размещаемую в офисе каждую ночь, откуда я могу выполнять конкретные запросы, которые необходимы. У меня нет опыта работы с базами данных, но я понимаю основы. Я прочитал немного о создании базы данных и нормальных формах.

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

Схема, которую я придумал, следующая:

  1. С точки зрения клиента:
    • Клиенты это основной стол
    • Клиенты связаны с отделом, в котором они работают
      • Отделы могут быть разбросаны по всей стране: HR в Лондоне, маркетинг в Суонси и т. Д.
      • Отделы связаны с подразделением компании
    • Подразделения связаны с материнской компанией
  2. С точки зрения классов:
    • Сессии является основной таблицей
      • Учитель связан с каждой сессией
      • Статус предоставляется каждому сеансу. Например, 0 - Завершено, 1 - Отменено
      • Сессии сгруппированы в «пачки» произвольного размера
    • Каждый пакет назначается клиенту

Я «спроектировал» (точнее, набросал) схему на листе бумаги, стараясь, чтобы она нормализовалась до 3-го класса. Затем я подключил его к MySQL Workbench, и это сделало все это красивым для меня:
( Нажмите здесь для полноразмерной графики )

альтернативный текст
(источник: maian.org )

Примеры запросов, которые я буду выполнять

  • Какие клиенты с кредитом все еще остались неактивными (те, у кого не запланировано обучение в будущем)
  • Какова посещаемость на клиента / отдел / подразделение (измеряется идентификатором статуса в каждой сессии)
  • Сколько занятий у учителя за месяц
  • Пометить клиентов с низкой посещаемостью
  • Пользовательские отчеты для отделов кадров с показателями посещаемости людей в их отделе

Вопросы)

  • Это слишком силен или я направляюсь в правильном направлении?
  • Приведет ли необходимость объединять несколько таблиц для большинства запросов к значительному снижению производительности?
  • Я добавил колонку «lastsession» для клиентов, поскольку это, вероятно, будет общий запрос. Это хорошая идея, или я должен держать базу данных строго нормализованной?

Спасибо за ваше время

Боб Эспонья
источник
131
Уважаемый студент CS первого курса: пожалуйста, продолжайте использовать StackOverflow. Ваш вопрос интересен, хорошо написан и полезен. Другими словами, вы находитесь в топе 1% респондентов.
Адам Кроссленд
Может ли Дивизион содержать другие Дивизии? ЕСЛИ в этом случае таблица «имеет» может быть использована для связи Отдела с Отделом, в котором он содержится.
Марк Шультхайс
Спасибо за добрые комментарии :) Марк, мне придется снова просмотреть документацию по этому проекту, но я не думаю, что мы выявили этот случай. Спасибо за указание на это.
Боб Эспонжа
1
Мне не нравятся ваши соглашения об именах первичных ключей. таблица divisionsимеет столбец с именем divisionid. Разве вы не находите это излишним? Просто назови это id. также имена ваших таблиц, в том числе _has_: я бы удалил это и просто назвал его, например cities_departments. Ваши DATETIMEстолбцы должны иметь тип, TIMESTAMPесли они не являются пользовательскими значениями. Я думаю , что это хорошая идея , чтобы иметь citiesи countriesтаблицу. Вы можете столкнуться с проблемами, ограничивающими таблицы одним status. рассмотрите возможность использования INTи выполнения побитовых сравнений, чтобы вы могли иметь больше смысла
Джеймс
@binnyb Существует много аргументов в пользу использования идентификатора в качестве имени первичного ключа, который люди должны рассмотреть, прежде чем принять решение.
Джедай

Ответы:

42

Еще несколько ответов на ваши вопросы:

1) Вы в значительной степени ориентированы на того, кто впервые сталкивается с такой проблемой. Я думаю, что указатели других по этому вопросу до сих пор в значительной степени охватывают его. Хорошая работа!

2 и 3) Удар производительности, который вы получите, будет в значительной степени зависеть от наличия и оптимизации правильных индексов для ваших конкретных запросов / процедур и, что более важно, от объема записей. Если вы не говорите о более чем миллионе записей в ваших основных таблицах, вы, похоже, идете по пути к достаточно массовому дизайну, чтобы производительность не была проблемой на разумном оборудовании.

Тем не менее, и это относится к вашему вопросу 3, с самого начала вы, вероятно, не должны слишком беспокоиться о производительности или гиперчувствительности к нормализации ортодоксальности. Это сервер отчетов, который вы создаете, а не серверная часть приложения на основе транзакций, которая имеет совершенно другой профиль в отношении важности производительности или нормализации. База данных, поддерживающая приложение для прямой регистрации и планирования, должна учитывать запросы, которые возвращают данные за считанные секунды. Мало того, что функция сервера отчетов обладает большей терпимостью к сложным и длительным запросам, но и стратегии повышения производительности сильно отличаются.

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

Все это - многословная рутина, иллюстрирующая, что принципы и приемы проектирования, которые вы используете, могут отличаться в зависимости от роли создаваемого вами БД. Я надеюсь, что это полезно.

Том Кроу
источник
1
1. Спасибо, это обнадеживает! 2 и 3. Я до сих пор не знаю, как работают индексы, это то, что я планировал прочитать. Если у нас когда-нибудь возникнет «проблема» в достижении миллиона записей, вероятно, будет бюджет, чтобы нанять опытных разработчиков: P Спасибо за понимание различных существующих ролей БД, для меня все это ново и очень интересно знать. Я посмотрю на снимки, так как то, что вы описываете, в основном является конечной целью проекта.
Боб Эспонжа
Если вы понимаете таблицы, основы индексов довольно просты. Концептуально, индекс может быть (и часто это) реализован в виде таблицы с очень небольшим количеством столбцов, содержимое которых скопировано из основной таблицы, и ссылкой на основную таблицу, строки которой отсортированы по кеоту для быстрого доступа. Дерево B + является наиболее распространенным механизмом индексации, но при оптимизации индексов крупные игроки используют свои дифференцирующие технологии, поэтому становится неясным, если вы попытаетесь применить аналогию слишком глубоко.
pojo-guy
14

У тебя правильная идея. Однако вы можете очистить его и удалить некоторые из таблиц сопоставления (имеет *).

То, что вы можете сделать, это в таблице Департаментов, добавить CityId и DivisionId.

Кроме того, я думаю, что все в порядке ...

Преподобный Гонзо
источник
4
Я думаю, что ему нужны таблицы сопоставления, если он хочет повторно использовать определение отдела в разных отделах или городах.
Джейкоб Г
1
Да, я согласен ..... но это звучало так, будто департамент мог находиться только в одном городе / районе. Если нет, то, что он имел, было определенно правильно.
Преподобный Гонзо
У меня есть статья в вики, которую я написал со «спецификацией» в офисе, мне придется прочитать ее еще раз, но Джейкоб Дж прав, IIRC есть некоторые отделы, которые охватывают подразделения. Один HR-отдел родителей ACME для ACME Healthcare и ACME Bodycare. Если я могу упростить это, хотя я, конечно, буду, спасибо за предложение.
Боб Эспонжа
6

Единственные изменения, которые я хотел бы сделать:
1- Измените VARCHAR на NVARCHAR, если вы собираетесь выходить на международный уровень, вы можете захотеть использовать Unicode.

2. Если возможно, измените свои int id на GUID (uniqueidentifier) ​​(это может быть только мое личное предпочтение). Предполагая, что вы в конечном итоге дойдете до точки, где у вас есть несколько сред (dev / test / staging / prod), вы можете захотеть перенести данные из одной в другую. Наличие идентификатора GUID делает это значительно проще.

3- Три слоя для вашей компании -> Отдел -> Структура отдела может быть недостаточно. Теперь это может быть чрезмерно сложно, но вы можете обобщить эту иерархию так, чтобы вы могли поддерживать n уровней глубины. Это сделает некоторые ваши запросы более сложными, так что это может не стоить компромисса. Кроме того, может случиться так, что любой клиент, который имеет больше слоев, может быть легко «вписан» в эту модель.

4- У вас также есть статус в таблице клиентов, который является VARCHAR и не имеет ссылки на таблицу статусов. Я ожидал бы немного большей ясности относительно того, что представляет собой статус клиента.

Джейкоб Г
источник
1- Спасибо, у меня были проблемы с диакритическими знаками и UTF8, для которых я собирался опубликовать еще один вопрос. Может быть, это проблема. 2- Я читал здесь некоторые другие вопросы о SO с большим количеством противоречивых мнений по этому вопросу, я буду больше читать по этому вопросу. 3. Я еще раз расскажу об этом со своим отцом, посмотрев «спецификации», которые я написал, и посмотрим, стоит ли нам на это смотреть. --Не могу оставить следующий комментарий
bob esponja
4- Я не стал вдаваться в основной вопрос для краткости: статус на клиенте - активны ли они (есть ли сеансы остаются) или неактивны (не осталось ни одной сессии). Под большей ясностью вы имеете в виду более описательное название col? Например, enrolment_status? Спасибо за ваш вклад.
Боб Эспонжа
re # 4- В дополнение к вашему более ясному имени, если есть только два состояния, активное / неактивное, то почему бы просто не сделать его битовым столбцом?
Джейкоб Г
3
Не согласен по поводу GUID, вздрагивать. Они могут быть ужасны для производительности. Не используйте их, если вам не нужно воспроизвести.
HLGEM
1
Производительность вступает в игру только тогда, когда вы говорите десятки миллионов строк в таблице. Если у вас есть структура такого типа, вы можете смягчить ее с помощью последовательных направляющих и индексирования объявлений. В противном случае «производительность» является красной сельдью при дисконтировании GUID.
Джейкоб Г
6

Нет. Похоже, вы разрабатываете на хорошем уровне детализации.

Я думаю, что страны и компании - это одно и то же лицо в вашем дизайне, как и города и отделы. Я бы избавился от таблиц стран и городов (и Cities_Has_Departments) и, при необходимости, добавил бы логический флаг IsPublicSector в таблицу Companies (или столбец CompanyType, если есть больше вариантов, чем просто частный сектор / государственный сектор).

Кроме того, я думаю, что вы используете ошибку в таблице отделов. Похоже, таблица «Отделы» служит ссылкой на различные виды отделов, которые могут иметь каждое клиентское подразделение. Если это так, он должен называться DepartmentTypes. Но ваши клиенты (которые, я полагаю, посетители) не принадлежат к типу отдела, они принадлежат фактическому экземпляру отдела в компании. В нынешнем виде вы будете знать, что данный клиент относится к отделу кадров где-то, а не к какому!

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

Ларри Люстиг
источник
Таблица стран предназначена для того, если / когда у нас есть клиенты, которые работают более чем в одной стране и имеют разные отделы кадров для каждой из них. Таким образом, мы можем создавать отчеты с данными из страны, в которой работает департамент. То же самое для департаментов и городов, я думаю, у нас есть клиент, у которого есть отдельные отделы кадров. для двух городов, в которых у них есть главные офисы. Или, по крайней мере, такова была причина, я сижу и переосмысливаю это, чтобы увидеть, действительно ли они необходимы. Не подумал о CompanyType, я выясню, нужно ли это отслеживать.
Боб Эспонья
RE: таблица depts, моя первоначальная мысль заключалась в том, чтобы использовать ее в качестве фактических отделов, указав тип отдела. Мне не приходило в голову просто иметь типы отделов, что кажется более логичным. Зная, к какому отделу и кому принадлежит, я подумал, что если бы отдел был связан с городом и отделом (который связан с компанией), это сработало бы. Был ли я не прав? Для разделения городов на дивизии, некоторые дивизии охватывают несколько городов, и я думаю, может быть, даже страны. Я посмотрю на это снова. Спасибо за ваш вклад.
Боб Эспонья
5

Кстати, стоит отметить, что если вы уже генерируете CSV и хотите загрузить их в базу данных MySQL, LOAD DATA LOCAL INFILE - ваш лучший друг: http://dev.mysql.com/doc/refman/5.1/ ru / load-data.html . Mysqlimport также заслуживает изучения и представляет собой инструмент командной строки, который, по сути, является хорошей оболочкой для загрузки данных.

jrheard
источник
3

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

Еще одна вещь: вы считаете, что вам действительно нужны таблицы «городов» и «стран»? Разве для ваших сценариев использования недостаточно столбцов «город» и «страна» в таблице отделов? Например, ваше приложение должно перечислить департаменты по городам и городам по странам?

Ханс Вестербик
источник
1
Как я ни старался , он продолжает брать ove, вычисляет большую O из helloworld.c, оптимизирует Таблицы городов и стран просто порождались, когда я следовал инструкциям, чтобы получить базу данных 3NF. Я предполагаю, что преимущество, которое они предлагают, состоит в согласованности названий городов / стран. Например, если мы получим клиента в Мюнхене, и по какой-то причине, кто бы ни вводит нового студента в систему планирования, он решит назвать его Мюнхеном, а не Мюнхеном, как для предыдущих студентов. Также нам может понадобиться перечислить департаменты по городам, я должен проверить. Спасибо.
Боб Эспонья
2
Оптимизация на этапе проектирования базы данных имеет решающее значение! Это не преждевременная оптимизация, так как базы данных значительно сложнее перефразировать, когда у них есть миллион записей.
HLGEM
1
Я не говорил, что он не должен подвергать стресс-тесту свой дизайн :)
Ханс Вестербик,
3

Следующие комментарии основаны на роли специалиста по бизнес-аналитике / отчетности и менеджера по стратегии / планированию:

  1. Я согласен с указанием Ларри выше. ИМХО, это не так уж и сложно, некоторые вещи выглядят немного не к месту. Чтобы было проще, я бы привязал клиента непосредственно к идентификатору компании, описанию отдела, описанию отдела, идентификатору типа отдела, идентификатору типа отдела. Используйте идентификатор типа отдела и идентификатор типа подразделения в качестве ссылок на таблицы поиска и внутренние поля отчетности / анализа для обеспечения долгосрочной согласованности.

  2. Таблица пакетов содержит столбец «Кредит», разве это не должно быть связано с базовой таблицей Клиента, поэтому, если их много, вы можете увидеть, сколько кредитов осталось для будущих классов? Приложение может заботиться о калькуляции и хранить его централизованно в таблице клиентов.

  3. Информация о компании может использовать гораздо больше полей, включая очевидный адрес / телефон / и т. Д. Информация. Я также был бы готов добавить в столбцы D & B "DUN" (Site / Branch / Ultimate) долгосрочные данные, так как Dun and Bradstreet (D & B) имеют огромный каталог компаний, и позже вы обнаружите, что их информация очень полезна для отчетности / анализа. Это позаботится о проблеме множественного разделения, которую вы упомянули, и позволит вам свернуть их иерархию для подразделов / подразделений / филиалов / и т. Д. большого корпуса.

  4. Вы не упоминаете, с какими записями вы будете работать, и это может означать, что вы настроите себя на крупную инициативу по разработке, которая могла бы быть выполнена быстрее и с гораздо меньшими головными болями с помощью предварительно упакованного программного обеспечения для «отчетности». Если вы не имеете дело со строками большой базы данных (<65000), убедитесь, что решения MS-Access, OpenOffice (Base) или связанные с ними приложения для разработки отчетов / приложений не справились с задачей. Я сам немного использую бесплатное программное обеспечение Oracle APEX, оно поставляется с бесплатной базой данных Oracle XE, просто скачайте его с их сайта.

  5. К сведению: представление отчетности: для больших баз данных у вас обычно есть два экземпляра базы данных: а) база данных транзакций для записи каждой подробной записи. б) база данных отчетов (витрина данных / хранилище данных), размещенная на отдельном компьютере. Для получения дополнительной информации выполните поиск в Google Star Schema и Snowflake Schema.

С уважением.

Будет
источник
1. Вы имеете в виду добавление всех этих столбцов в таблицу клиента? Я думаю, что это нарушит нормализацию, а также сделает ее непоследовательной, хотя я не уверен, что правильно понял. 2. Пакеты являются последовательными, кредит может иметь только последний пакет, поэтому нет необходимости отслеживать несколько пакетов. Вы все еще порекомендуете сохранить его в таблице клиента в этом случае? 3. Похоже, будет очень полезно выяснить структуру компаний-клиентов, я посмотрю на это, спасибо.
Боб Эспонья
4. Мне придется проверить количество клиентов и сеансов, которые мы ожидаем получить в следующем году, но мне кажется возможным, чтобы таблица сеансов достигла такого количества строк в год или около того. Я посмотрю на программное обеспечение для отчетности, мне не пришло в голову. 5. Кажется, что я попал в такую ​​ситуацию случайно; веб-приложение будет нашей «базой данных транзакций», а этот проект - нашей «базой данных репотирования» :) Спасибо за ваш вклад.
Боб Эспонжа
1. Да, добавив столбцы «Идентификатор компании, Описание отдела, Описание отдела, Идентификатор типа отдела, Идентификатор типа отдела» в таблицу клиента. Клиент принадлежит к одной компании, отдельному типу отдела (IT / Ops / Admin / и т. Д.) Внутри компании и отдельному типу подразделения (Sales / HR / Marketing). 2. Я просто думаю, что Кредит связан с клиентом или компанией, а не с Пакетом сессий. Это деловое решение, которое вы можете принять.
Будет
Ларри также упомянул объединение компании и страны. Я полностью согласен и вернусь к вопросу о D & B. Я бы использовал SiteID или что-то уникальное, чтобы разрешить несколько местоположений одной и той же компании, а затем связывал департаменты с одним из уникальных идентификаторов SiteID.
Будет
2

Я хочу остановиться только на том, что присоединение к нескольким таблицам повлечет за собой снижение производительности. Не бойтесь нормализоваться, потому что вам придется делать объединения. Соединения нормальны и ожидаемы в реляционных базах данных, и они разработаны для того, чтобы хорошо с ними справляться. Вам нужно будет установить отношения PK / FK (для целостности данных это важно учитывать при проектировании), но во многих базах данных FK не индексируются автоматически. Так как они будут использоваться в соединениях, вы определенно захотите начать с индексации FKS. ПК обычно получают индекс при создании, поскольку они должны быть уникальными. Это правда, что дизайн хранилища данных уменьшает количество объединений, но обычно не удается добраться до хранилища данных до тех пор, пока в одном отчете не будут доступны миллионы записей. Даже тогда почти все хранилища данных начинают с транзакционной базы данных для сбора данных в режиме реального времени, а затем данные перемещаются в хранилище по расписанию (ночью или ежемесячно или в соответствии с потребностями бизнеса). Так что это хорошее начало, даже если вам нужно спроектировать хранилище данных позже, чтобы повысить производительность отчетов.

Я должен сказать, что ваш дизайн впечатляет для студентов первого курса CS.

HLGEM
источник
1

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

Крис Деннетт
источник
Перечисления являются злом. Каждый раз, когда вам нужно расширить перечисление, вы должны перестраивать свою таблицу - это нормально, пока размер вашей таблицы не станет много ГБ.
Мартин
Спасибо за вклад и предложение Крис, я волновался, что создам слишком сложного монстра. Мартин, статусы довольно четко определены и статичны: в основном 0-класс завершен, 1-класс отменен, 2-не появился. Я думаю, что эти три охватывают любой возможный результат класса. Это все еще плохая идея использовать перечисления в этом случае?
Боб Эспонжа
На мой взгляд, это идеально подходит для перечисления. Все возможные результаты удовлетворены заранее. Int тоже хорошо, что вы можете представить перечислением или статическими int в вашем приложении. Не имеет большого значения :) На перечисления лучше смотреть, если вы редактируете свою базу данных с помощью какого-либо инструмента.
Крис Деннетт
Перечисления могут быть проблематичными (возможно, зло - слишком сильное слово), когда у вас есть большие таблицы, которые должны быть доступны 24x7, и перечисление необходимо изменить. Учитывая, что вы заново заполняете таблицы - не беспокойтесь об этом. Учитывая достаточно маленький набор данных, вы можете просто использовать строки.
Мартин
1

Я работал в области обучения / школы, и я подумал, что должен указать, что обычно существует отношение M: 1 между тем, что вы называете «сессиями» (экземплярами данного курса), и самим курсом. Другими словами, ваш каталог предлагает курс («Испанский 101» или любой другой), но у вас может быть два разных экземпляра в течение одного семестра (Tu-Th, преподаваемый Smith, Wed-Fri, преподаваемый Jones).

Кроме того, это выглядит как хорошее начало. Могу поспорить, вы обнаружите, что клиентский домен (графики, ведущие к «клиентам») является более сложным, чем вы смоделировали, но не переусердствуйте с этим, пока у вас не появятся реальные данные, которые вам помогут.

Ларри Обриен
источник
Если я вас правильно понял, это не совсем так. «Курсы» - это просто группы последующих сессий. Это не традиционная система, основанная на семестрах. Я не могу придумать ничего другого, что можно было бы добавить в клиентский домен. У вас есть какой-нибудь пример? Кроме того, я был обеспокоен тем, что уже сошел со сложностью, рад, что это не так :) Спасибо за ваш вклад.
Боб Эспонжа
0

На ум пришло несколько вещей:

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

  2. Компании часто не имеют отделов. Сотрудники иногда меняют отделы / отделы, возможно, даже в середине сессии. Компании иногда добавляют / удаляют / переименовывают подразделения / отделы. Убедитесь, что возможное изменение содержимого ваших таблиц в реальном времени не затрудняет последующую отчетность / группировку. С таким большим количеством контактных данных, разбитых на множество таблиц, вам, возможно, придется применять очень строгую проверку ввода данных, чтобы ваши отчеты были содержательными и содержательными. Например, когда добавляется новый клиент, убедитесь, что его компания / подразделение / отдел / город соответствуют тем же значениям, что и его коллеги.

  3. Концепция «пачки» не совсем понятна.

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

Джо Снайдер
источник