Обзор нормализации
Нормализация — это процесс уточнения сущностей и атрибутов вашей базы данных, чтобы они просто создавались гораздо более организованным и эффективным образом. Полностью нормализованная база данных помогает свести к минимуму такие вещи, как избыточность данных. Это, в свою очередь, максимизирует производительность и, конечно же, пространство для хранения базы данных, а также просто оптимизирует сами структуры данных систематически и правильно размещая элементы данных в соответствующей группе. Это также помогает поддерживать целостность ваших данных и, безусловно, также оптимизирует время запроса, поскольку, конечно же, запрос включает в себя извлечение данных из базы данных.
Так что, если она была введена очень эффективно и организованно с самого начала, то извлечение этой информации становится намного проще, становится намного быстрее. Итак, как только ваша логическая модель была нормализована, ее можно преобразовать в организованную физическую базу данных. И сам процесс нормализации, по сути, основан на различных математических принципах теории множеств, которая просто работает с наборами единиц, какими бы они ни были. Но когда он применяется к базе данных, он гарантирует, что каждый объект правильно сформирован и эффективен, что просто означает, что таблица содержит все, что должно, и в идеале ничего лишнего. Таким образом, каждый атрибут назначается соответствующему объекту. Итак, опять же, то, что вы пытаетесь сделать, это просто убедиться, что в ваших таблицах есть соответствующие столбцы и что там нет столбцов, которые заставляют вас вводить много избыточных данных или вызывать внезапное появление каких-либо дополнительных объектов. Вы хотите убедиться, что все объекты максимально корректны в процессе нормализации. Теперь это итеративный или рекурсивный процесс, если хотите. Вам предстоит пройти его на нескольких уровнях.
Таким образом, у каждого уровня есть свои правила, которым должна соответствовать база данных. Итак, если вы хотите сказать, что находитесь в первой нормальной форме, и это то, что вы видите на графике — 1NF, 2NF и так далее. Это первая нормальная форма, вторая нормальная форма, и все оттуда. Поэтому, продвигаясь на каждый уровень, вы должны убедиться, что вы сначала удовлетворяете требованиям предыдущего уровня. Таким образом, чтобы достичь второй нормальной формы, необходимо применить все правила для первой нормальной формы. Теперь ясно, вы видите цифры, указывающие уровень, но обратите внимание, что есть третий с половиной уровень, если хотите, BCNF. Теперь мы рассмотрим каждый уровень по мере перехода к следующим нескольким демонстрациям. Но тот был, если хотите, уточнением, улучшением второй, третьей нормальной формы. Так что он находится между тремя и четырьмя, и вы можете услышать, как некоторые люди называют его тремя с половиной. Но он назван в честь некоторых людей, которые придумали сам процесс или уровень, я бы сказал. Итак, как уже упоминалось, мы поговорим обо всех этих уровнях в наших предстоящих демонстрациях, но в конечном итоге вы, как правило, хотите попасть как минимум на третье место. Это не требует, чтобы вы попали на каждый уровень. Нет правила, согласно которому вы должны достичь пятой нормальной формы. Но треть обычно считается, своего рода, минимумом. С этого момента все зависит от структуры вашей базы данных и от того, как вы ведете бизнес, и, в конечном счете, от того, как вы хотите, чтобы база данных выглядела. Так что вы, конечно, можете продолжать двигаться вверх в нормальных формах, но ничто не говорит о том, что вы должны. Так что мы увидим эти формы на наших предстоящих демонстрациях.
Первая нормальная форма
Итак, давайте начнем с первой нормальной формы, и очевидно, что это ваша низшая форма. И как таковой он связан с установлением самых основных правил, закладыванием основы вашей структуры. И, как и в любой постройке, всегда нужно начинать с хорошего прочного фундамента. Итак, ваша главная цель на данном этапе — просто разделить ваши данные на логические объекты, определив, какие у вас есть объекты и какие атрибуты применимы к этим объектам. Итак, первая нормальная форма должна удовлетворять следующим условиям. В одной таблице не должно быть повторяющихся столбцов. Сейчас мы поясним это через мгновение, но это не значит, что вы увидите один и тот же столбец дважды. Но скорее вы увидите данные в столбце, который появляется более одного раза. Итак, вы знаете, вы не будете хранить фамилию человека дважды. Он видит несколько повторяющихся значений в этом столбце. Каждая строка должна быть уникальной в таблице. Таким образом, вы хотите убедиться, что есть что-то, что однозначно идентифицирует каждую запись, и каждый столбец должен содержать только одно значение и использовать только один тип данных.
Итак, вот пример таблицы сотрудников. [Таблица сотрудников. Некоторые из полей в этой таблице: Employee_ID, Last_Name, First_Name и Skills.]И этот конкретный пример содержит разные навыки для каждого сотрудника. Но проблема в том, что он может содержать более одного значения в поле «Навыки». Из-за этого очень сложно найти сотрудников, обладающих только определенными навыками. Итак, возвращаясь к нашему концептуальному дизайну, мы по сути пытаемся найти сущность, что в данном случае мы и сделали. Это сотрудник, но тогда атрибуты описывают сотрудника. И вы вполне справедливо можете подумать, ну, мастерство человека говорит мне о нем кое-что. И в реальном мире, знаете ли, совершенно нормально говорить, что этот человек обладает этим умением, это то, что описывает его. Но с точки зрения нормализации, это действительно не так. Это представляет проблему. Если у человека есть более одного навыка, то в этой структуре вам нужно будет ввести эти два навыка или три навыка, или любое их количество в одной ячейке этой таблицы. Таким образом, сотрудник номер 1 — Джо Смит — по этому адресу и с этим почтовым индексом будет обладать навыком 1, навыком 2, навыком 3, навыком 4, какими бы они ни были. Итак, вы знаете, что это может работать для этой конкретной записи, но тогда вы можете представить, что хотите получить запрос, может быть, как менеджер отдела, который говорит, покажите мне всех сотрудников с навыком 2. Вы действительно не можете сделать это в данном случае потому, что значение ячейки не просто навык 2. Это навык 1, навык 2, навык 3 и так далее и тому подобное. Поэтому найти эти конкретные навыки становится очень сложно, если у вас есть такая структура.
Таким образом, чтобы соответствовать первой нормальной форме, навык должен содержать только одно значение. Поэтому для этого навыки должны быть отдельной таблицей и связаны с основной родительской таблицей. Итак, вы определили потребность в другом объекте. Навык — это не атрибут. Это сущность. Таким образом, вы можете создать таблицу навыков и создать связь с родительской таблицей. [Таблица Skill содержит три поля: Employee_ID, Skill_ID и Skill_Name.]В данном случае это фактически связь многие ко многим, поскольку у одного сотрудника много навыков. Любым одним навыком обладают многие работники. Так что на самом деле вам, вероятно, все еще понадобится таблица пересечений, чтобы решить эту проблему, та, которая подтягивает сотрудника, та, которая вытягивает навык. И теперь вы сократили его до двух «один ко многим» вместо одного «многие ко многим». Но что касается первой нормальной формы, нас это пока не особо беспокоит. Это просто говорит о том, что у нас не должно быть более одного значения в одной ячейке, что указывает на то, что это должна быть отдельная отдельная таблица. На самом деле это сущность.
Таблицы не могут содержать повторяющиеся атрибуты или группы атрибутов, что, по сути, отражает связь «многие ко многим» внутри одной таблицы. Итак, здесь мы снова видим сотрудника. [Он снова обращается к таблице Employee.]В этом случае у нас все еще есть идентификатор, Last_Name, First_Name и Department_Name. И теперь мы видим информацию о проекте, которая была назначена сотруднику. И опять же, вы можете справедливо подумать, что проект, над которым работает этот человек, говорит мне что-то о нем. И опять же, в реальном мире и, знаете ли, при обычном использовании языка, это справедливо. Но снова посмотрите, что происходит с данными. Эта таблица содержит повторяющуюся группу — Project_Name, Start_Date, End_Date и Budget. Таким образом, это будет работать только в том случае, если сотрудник когда-либо назначается только одному проекту. Как только они будут назначены на второй проект, вам придется снова ввести их информацию. Итак, сотрудник номер 1 — Джо Смит — в этом отделе работает над проектом 1, каким бы он ни был, а затем вы заполняете информацию о дате. Есть одна запись. Как только Джо Смит будет назначен на второй проект, вам придется избыточно заполнить сотрудника № 1 — Джо Смит — в этом отделе сейчас работает над проектом 2. Ну, значение первичного ключа Employee_ID не позволит вам дважды ввести сотрудника № 1. Итак, вы определили другую сущность. Имя_проекта, Дата_начала, Дата_окончания и Бюджет для этого проекта не описывают сотрудника. Они описывают проект. Итак, это повторяющиеся значения, которые мы обсуждали ранее. Дело не в том, что какой-то один столбец находится в одной и той же таблице более одного раза. Это требует от вас ввода одних и тех же данных в эти столбцы более одного раза. И само по себе поле первичного ключа не позволяет вам это сделать.
Так же решение. Для проекта создается отдельный объект [Некоторые поля в таблице Project: Employee_ID, Project_ID и Project_Name.] и вы связываете его обратно с сотрудником. И опять у вас, наверное, здесь много ко многим, потому что один сотрудник мог работать над многими проектами. Над любым проектом может работать много сотрудников, но это опять же то, к чему вы можете обратиться позже. Но это ваши первые этапы первой нормальной формы, когда вы пытаетесь идентифицировать сущности и атрибуты, вызывающие нарушения этих правил первой нормальной формы.
Вторая нормальная форма
Переходя ко второй нормальной форме, это еще больше улучшает удаление повторяющихся данных. А чтобы соответствовать второй, вы, во-первых, как уже упоминалось, должны соответствовать всем требованиям первой нормальной формы. И тогда каждый неключевой столбец должен полностью зависеть от ключевого столбца или столбцов, потому что их может быть больше одного. Опять же, составной первичный ключ, например. Поэтому это иногда называют функциональной зависимостью. И я думаю, на простом английском языке, если хотите, это означает, что любой столбец действительно нуждается в своем столбце первичного ключа в ассоциации с ним, чтобы он имел какой-либо смысл, из-за отсутствия лучшего описания.
Итак, мы рассмотрим здесь пример. Но прежде чем мы перейдем к этому, некоторые дополнительные соображения. Ни один атрибут не может зависеть только от ключевых столбцов, а также уточнять, что это означает. Но по сути любой атрибут должен целиком и полностью зависеть от ключевого столбца. Таким образом, если есть какие-либо неключевые столбцы, которые не являются полностью зависимыми, их необходимо переместить в новую таблицу с копией столбца, от которого они полностью зависят. Таким образом, обычно вы видите это в разрешении отношений «многие ко многим» с этими таблицами пересечений. Опять же, на данный момент это немного опережает события, но обычно именно здесь вы чаще всего видите, как вторая нормальная форма вступает в игру. Таким образом, внешние ключи затем используются для связывания таблицы с использованием этого ключевого столбца. Итак, на графике здесь мы видим пример некоторой информации о животных, предположительно из зоопарка. [Отображается таблица сведений о животных. Некоторые из полей в таблице: Animal_ID, Дата, Animal_Name и Animal_Type.]Итак, у нас есть Animal_ID, который является первичным ключом. Таким образом, вторая нормальная форма означает, что любое поле в этой таблице должно полностью зависеть от Animal_ID, чтобы оно имело смысл. Итак, сразу же мы видим поле Date. Что ж, поле Date действительно не имеет никакого смысла в этой таблице. На самом деле это не имеет ничего общего с Animal_ID. Так для чего он там? Что ж, мы вернемся к этому через минуту, но Animal_Name, безусловно, имеет смысл в сочетании с ID, как и Animal_Type.
Но затем мы начинаем видеть Number_of_Feeds, Medication_Given, Medication_Name и Number_of_Doses. Что ж, эти вещи, очевидно, указывают на то, что животное кормят, вероятно, несколько раз, если оно говорит Number_of_Feeds, и лекарство, которое они получают, может быть или не быть более одного раза. В данном случае это не показатель. Это может быть просто поле да/нет. Но тот факт, что есть Дата, действительно объединяет это сейчас, потому что, если предположить, что вы кормите животное более одного раза в день, то зная дату, когда оно в последний раз кормилось, и / или сколько кормлений он получил на эту дату, ну они имеют смысл вместе. То же и с лекарством. Может быть, они получают лекарство только через день, или каждый третий день, или два раза в неделю, или что-то в этом роде. Так что Дата имеет смысл с этим. Таким образом, в этой таблице мы видим поля, которые на самом деле не имеют никакого смысла, если рассматривать только Animal_ID. Знание Number_of_Feeds для конкретного животного, безусловно, является полезной информацией. Но без даты это действительно не имеет никакого смысла. Таким образом, эти столбцы в основном не полностью зависят от идентификатора.
Так что же в итоге происходит? Мы видим эти таблицы или, простите, эти столбцы переместились в другую таблицу. Итак, таблица Animal теперь содержит только те столбцы, которые полностью зависят от первичного ключа — имени и типа. [Таблица Animal содержит три поля: Animal_ID, Animal_Name и Animal_Type. Таблица Animal и таблица Animal Detail имеют отношение «многие ко многим».] В сочетании с первичным ключом поле ID имеет смысл. Но таблица сведений о животных теперь показывает, что Animal_ID также присутствует, а затем дата включена как часть первичного ключа. Потому что здесь вы можете подумать, что, опять же, для любого данного животного будет несколько записей в отношении Number_of_Feeds и независимо от того, получают ли они свое лекарство, Number_of_Doses и так далее. Таким образом, само животное будет появляться в этой таблице много раз. Что ж, если бы Animal_ID сам по себе был первичным ключом, вы не могли бы ввести один и тот же Animal_ID более одного раза. Но как только вы поместите дату как часть первичного ключа, тогда Animal_ ID сам по себе может быть введен несколько раз, если дата каждый раз будет разной. Итак, опять же, мы увидим, что животное номер 1 в любую дату — давайте просто назовем это сегодня — получило три кормления. На следующий день это будет то же самое животное, животное номер 1, но в другой день, получившее три кормления и, знаете, снова информацию о своих лекарствах и так далее. Но два из них вместе — дата и животное — не создают дублирующую запись. Это одно и то же животное, но два разных дня. На любую дату, конечно же, будут корма и лекарства для других животных. Таким образом, вы увидите, что дата повторяется. Мы увидим сегодня, сегодня, сегодня, сегодня, сегодня для каждого экземпляра животного. Итак, опять же, каждый по отдельности повторяется, а двое вместе — нет. И тогда Number_of_Feeds, Medication_Given, Medication_Name и Number_of_Doses теперь полностью зависят от первичного ключа, который является обоими столбцами — животным и датой. Итак, теперь они соответствуют второй нормальной форме, потому что эти поля зависят от знания как животного, так и даты.
Третья нормальная форма
Переходя к третьей нормальной форме, она очень похожа на вторую в том смысле, что она по-прежнему связана с функциональной зависимостью одного столбца по отношению к ключевому столбцу. Но это добавляет уточнение, которое мы увидим через мгновение. Итак, для начала, чтобы достичь третьей нормальной формы, вы прежде всего должны выполнить все требования второй нормальной формы и вспомнить, что это касается неключевых столбцов, которые не зависят от ключевого столбца, который необходимо переместить в другую таблицу. Так что мы заняли второе место. Третий вводит это дополнительное уточнение, когда неключевые столбцы зависят от другого неключевого столбца. Таким образом, вы все еще видите здесь функциональную зависимость, но это не какой-то конкретный столбец, зависящий или независимый от ключевого столбца. Это неключевой столбец, который зависит или не зависит от другого неключевого столбца. Таким образом, вы получите столбец в таблице. Вероятно, это должно быть ключом, но это не так. Итак, мы увидим это через мгновение. Но неключевой столбец следует переместить вместе с другим неключевым столбцом, от которого он зависит, в новую таблицу.
Итак, в этом примере мы видим, что у нас есть таблица Section, [Некоторые поля в этой таблице — Section_Name, Date и Supervisor_ID. Section_Name и Date являются первичными ключами в этой таблице.]и это все еще наш зоологический пример. Но в этом случае Section_Name и Date… создают составной первичный ключ. Итак, мы говорим о разделе. Таким образом, Supervisor_Phone, Supervisor_FName, Supervisor_Lname и Supervisor_ID, по сути, не имеют ничего общего с разделом. И опять же, вы можете подумать, ну, я хочу знать, какой супервайзер отвечает за раздел. Итак, опять же, вы можете справедливо подумать, что это описывает раздел. Но супервайзер в этом случае действительно отдельная сущность. Таким образом, они не относятся к ключевым столбцам Section_ Name или Date. Таким образом, Supervisor_ID выглядит как поле типа идентификатора, которое, возможно, было бы или, может быть, даже должно быть первичным ключом, но в этой таблице его нет. Так что это неключевой столбец. Таким образом, фамилия, имя и телефон супервизора зависят от знания Supervisor_ID. Имя и номер телефона на самом деле не имеют никакого смысла, если мы не знаем, о каком начальнике идет речь, потому что вполне возможно, что у вас может быть двойное имя, даже, возможно, один и тот же номер телефона, если, например, они работают в тот же раздел или отдел или как-то еще, что вы хотите назвать. Таким образом, вы должны знать Supervisor_ID, чтобы они были полезны. Таким образом, все эти столбцы — телефон, имя и фамилия, а также идентификатор, который в данном случае не является ключевым столбцом, — должны быть перемещены в другую таблицу.
Таким образом, мы все еще видим то же самое решение. Создается еще одна таблица. [Он обращается к таблице Supervisor. Поля в этой таблице: Supervisor_ID, First_Name, Last_Name и Phone_Number. Supervisor_ID — это первичный ключ в этой таблице. Таблицы Section и Supervisor имеют отношение «многие ко многим».]Итак, мы снова идентифицировали другую сущность. Таким образом, он создается со всеми неключевыми столбцами и другим столбцом, который, опять же, в данном случае изначально не был ключевым столбцом. Таким образом, Supervisor_ID, First_Name, Last_Name и Phone_Number перемещаются в новую таблицу. Supervisor_ID становится первичным ключом, потому что другие столбцы зависят от этого. А затем в разделе мы создаем новое поле с именем Section_ID и используем его в качестве первичного ключа. Затем Section_Name и Date теперь зависят от того, известен Section_ID. И тогда мы все еще видим Supervisor_ID. Итак, еще раз, возвращаясь к исходной точке зрения, мне нужно знать, какие разделы контролируются какими сотрудниками. Ну, вы все еще можете сделать это сейчас, потому что мы можем просто создать связь, которая использует Supervisor_ID как внешний ключ. И теперь мы точно знаем, какой руководитель отвечает за этот участок. Итак, еще раз, исключив поля, не зависящие от исходного первичного ключа, и те, которые зависят от других неключевых столбцов, вы выполнили требования для третьей нормальной формы.
Теперь я хочу упомянуть, а также. Вы можете не столкнуться с этим. Знаете, возможно, вы просто никогда не помещали неключевой столбец в таблицу, от которой зависят эти другие неключевые столбцы. Другими словами, если вы правильно идентифицировали свои объекты с самого начала, вы можете никогда не столкнуться с этой проблемой, и в этом случае второй нормальной формы может быть достаточно. Но вы хотите убедиться, что вы прошли все эти уровни, и просто убедиться, что вы не пропустили ни один из них. Таким образом, обычно к тому времени, когда вы достигаете третьей нормальной формы, если вы сталкиваетесь с ней, в большинстве случаев у вас есть довольно функциональная база данных, и опять же, это своего рода минимальный уровень, который большинство разработчиков баз данных хотели бы видеть с точки зрения уровня. что вы достигли. Но есть еще некоторые уточнения. Он увидит их через мгновение. Но третья нормальная форма обычно является удовлетворительной точкой останова.
Нормальная форма Бойса-Кодда
Дополнительная форма, если хотите, третья с половиной, как ее иногда называют, также известна как BCNF, что означает нормальную форму Бойса-Кодда. А Бойс и Кодд придумали это усовершенствование. Опять же, он по-прежнему имеет дело с зависимостью от ключевых столбцов. Но в этом случае больше внимания уделяет проверке ваших ключевых столбцов-кандидатов, которые вы могли бы реализовать в качестве ключа. Итак, еще раз для начала, давайте рассмотрим некоторые другие требования для достижения нормальной формы БК. Сначала нам нужно достичь третьей нормальной формы, а BCNF занимается отсутствием перекрывающихся ключей-кандидатов. Теперь мы скоро перейдем к тому, что это значит. Но, по сути, вам сначала нужно определить, какие поля, по вашему мнению, вы могли бы использовать в качестве ключевого столбца. Это ваши ключевые столбцы-кандидаты.
Итак, когда мы смотрим здесь на эту таблицу Project, [Некоторые поля в таблице Project: Project_Name, Employee_ID, First_Name и Last_Name.]мы видим, что есть информация о проекте и сотруднике, который , конечно, мог бы работать над проектом. Но, по сути, внутри этой таблицы существует отношение «многие ко многим». Каждый сотрудник может работать над несколькими проектами. Над каждым проектом может работать несколько сотрудников. Так что многие ко многим в одной таблице действительно не сработают. И снова, если мы вернемся к первой нормальной форме, мы будем говорить о таблице Project. Информация о сотрудниках не описывает проект. Информация о сотруднике описывает сотрудника. Итак, у нас есть таблица двух сущностей, пытающихся сосуществовать в одной сущности.
Таким образом, ключи-кандидаты, которые были идентифицированы, в данном случае — это Employee_ID, Project_Name, First_Name и Last_Name. Сейчас я вам скажу, что не было бы слишком много случаев, когда имя или фамилия использовались бы в качестве ключевого поля. Но мы просто пытаемся продемонстрировать, в чем смысл потенциальных ключей. По сути, вы просто говорите, ну, мы могли бы использовать это, мы могли бы использовать это, и мы могли бы использовать это. Так что это немного преувеличено. Но в этом примере у нас есть эти ключевые поля-кандидаты. Имя проекта в данном случае — это то, что известно как перекрывающийся ключ-кандидат. Другими словами, он пытается охватить обе сущности — сотрудников, работающих над проектом, и сам проект. Итак, если вы посмотрите на фактические данные, [Он ссылается на таблицу Projects. Он содержит четыре столбца и несколько строк. Заголовки столбцов: Project_Name, Employee_ID, First_Name и Last_Name. В одной из строк запись под заголовком столбца Project_Name — Design; запись под заголовком столбца Employee_ID — E001; запись под заголовком столбца First_Name — Alex; и запись под заголовком столбца Last_Name — Baker. В другой строке запись под заголовком столбца Project_Name — Design; запись под заголовком столбца Employee_ID — E002; запись под заголовком столбца First_Name — Amy; и запись под заголовком столбца Last_Name — Foster.] вы видите Project_Name и, конечно же, вы видите информацию о сотруднике, назначенном для этого конкретного проекта. Итак, вы знаете, мы можем посмотреть на дизайн-проект и увидеть, что сотрудник номер 1 и сотрудник номер 2 назначены на этот проект. Это само по себе нормально. Но если вы удалите Имя_Проекта, другие ключи — другие ключи-кандидаты — больше не будут квалифицироваться как ключи-кандидаты, потому что каждое другое поле после удаления Имя_Проекта будет содержать повторяющиеся значения. Вы видите, что сотрудник 2 указан дважды в Employee_ID. Вы видите имя Amy дважды в поле First_Name и дважды видите фамилию Foster в этом поле. Так что дубликаты есть в каждом из них. Так что ни одно из них не могло стать ключевым полем. Первичные ключи не допускают дублирования значений.
Таким образом, эти поля, эти три столбца, которые ранее зависели от знания проекта, должны быть удалены. Итак, опять же, вы все еще просто идентифицируете и уточняете свои сущности. [Отображаются две таблицы: Сотрудник и Проект. Поля в таблице Employee: Employee_ID, First_Name и Last_Name. Employee_ID — это первичный ключ. Поля в таблице Project: Project_ID, Project_Name и Employee_ID. Project_ID — это первичный ключ. Employee_ID — это внешний ключ.] Итак, вы определили, что сотрудник должен быть сущностью, а не чем-то, что описано в таблице Project. Атрибуты проекта должны описывать только проект. Итак, теперь мы видим, что Project_ID вступает в игру, а Project_Name больше даже не используется в качестве ключевого поля. Мы даем проекту идентификатор, затем даем ему имя, а затем сотрудник, работающий над этим конкретным проектом, назначается внешним ключом. Опять же, мы по-прежнему видим здесь отношение «многие ко многим», потому что один сотрудник может работать над многими проектами. Над любым проектом может работать много сотрудников. Так что, скорее всего, нам все еще понадобится таблица пересечений, называемая назначениями сотрудников, или что-то в этом роде. И вы бы удалили Employee_ID в качестве внешнего ключа из таблицы Project и создали эту таблицу пересечений между ними, которая извлекает Employee_ID и извлекает Project_ID. И теперь мы разрешили отношение многие ко многим.