| 140 | | |
| 141 | | For a decomposition to be lossless, the common attributes between two joined relations must be a superkey in at least one of them. We verify this for all connected relations: |
| 142 | | |
| 143 | | CHAPTER ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 144 | | |
| 145 | | STORY ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 146 | | |
| 147 | | READING_LIST ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 148 | | |
| 149 | | AI_SUGGESTION ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 150 | | |
| 151 | | COMMENT ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 152 | | |
| 153 | | COMMENT ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 154 | | |
| 155 | | STATUS ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 156 | | |
| 157 | | CONTENT_TYPE ⋈ NOTIFICATION: common attribute notification_id → notification_id is the primary key of NOTIFICATION |
| 158 | | |
| 159 | | SUGGESTION_TYPE ⋈ AI_SUGGESTION: common attribute suggestion_id → suggestion_id is the primary key of AI_SUGGESTION |
| 160 | | |
| 161 | | LIKES ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 162 | | |
| 163 | | LIKES ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 164 | | |
| 165 | | COLLABORATION ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 166 | | |
| 167 | | COLLABORATION ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 168 | | |
| 169 | | ROLES ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 170 | | |
| 171 | | ROLES ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 172 | | |
| 173 | | PERMISSION_LEVEL ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 174 | | |
| 175 | | PERMISSION_LEVEL ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 176 | | |
| 177 | | HAS_GENRE ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 178 | | |
| 179 | | HAS_GENRE ⋈ GENRE: common attribute genre_id → genre_id is the primary key of GENRE |
| 180 | | |
| 181 | | READING_LIST_ITEMS ⋈ READING_LIST: common attribute list_id → list_id is the primary key of READING_LIST |
| 182 | | |
| 183 | | READING_LIST_ITEMS ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 184 | | |
| 185 | | NOTIFY ⋈ USERS: common attribute user_id → user_id is the primary key of USERS |
| 186 | | |
| 187 | | NOTIFY ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 188 | | |
| 189 | | NOTIFY ⋈ NOTIFICATION: common attribute notification_id → notification_id is the primary key of NOTIFICATION |
| 190 | | |
| 191 | | NEED_APPROVAL ⋈ AI_SUGGESTION: common attribute suggestion_id → suggestion_id is the primary key of AI_SUGGESTION |
| 192 | | |
| 193 | | NEED_APPROVAL ⋈ STORY: common attribute story_id → story_id is the primary key of STORY |
| 194 | | |
| 195 | | NEED_APPROVAL ⋈ CHAPTER: common attribute chapter_id → chapter_id is the primary key of CHAPTER |
| 196 | | |
| 197 | | In every case the common attributes form a primary key in at least one of the joined relations, so all joins are lossless and no information is lost during decomposition. |
| 198 | | |
| | 112 | === Lossless Join Verification |
| | 113 | Step 1: Decompose R → USERS + R₁ — common attribute: user_id → primary key of USERS |
| | 114 | R₁ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at} |
| | 115 | Step 2: Decompose R₁ → STORY + R₂ — common attribute: story_id → primary key of STORY |
| | 116 | R₂ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at, mature_content, short_description, image, story_content, story_created_at, story_updated_at} |
| | 117 | Step 3: Decompose R₂ → CHAPTER + R₃ — common attribute: chapter_id → primary key of CHAPTER |
| | 118 | R₃ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at, mature_content, short_description, image, story_content, story_created_at, story_updated_at, chapter_number, chapter_name, title, chapter_content, word_count, rating, published_at, view_count, chapter_created_at, chapter_updated_at} |
| | 119 | Step 4: Decompose R₃ → GENRE + R₄ — common attribute: genre_id → primary key of GENRE |
| | 120 | R₄ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at, mature_content, short_description, image, story_content, story_created_at, story_updated_at, chapter_number, chapter_name, title, chapter_content, word_count, rating, published_at, view_count, chapter_created_at, chapter_updated_at, genre_name} |
| | 121 | Step 5: Decompose R₄ → READING_LIST + R₅ — common attribute: list_id → primary key of READING_LIST |
| | 122 | R₅ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at, mature_content, short_description, image, story_content, story_created_at, story_updated_at, chapter_number, chapter_name, title, chapter_content, word_count, rating, published_at, view_count, chapter_created_at, chapter_updated_at, genre_name, list_name, list_content, is_public, list_created_at, list_updated_at} |
| | 123 | Step 6: Decompose R₅ → NOTIFICATION + R₆ — common attribute: notification_id → primary key of NOTIFICATION |
| | 124 | R₆ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at, mature_content, short_description, image, story_content, story_created_at, story_updated_at, chapter_number, chapter_name, title, chapter_content, word_count, rating, published_at, view_count, chapter_created_at, chapter_updated_at, genre_name, list_name, list_content, is_public, list_created_at, list_updated_at, notification_content, is_read, type, link, notification_created_at} |
| | 125 | Step 7: Decompose R₆ → AI_SUGGESTION + R₇ — common attribute: suggestion_id → primary key of AI_SUGGESTION |
| | 126 | R₇ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at, mature_content, short_description, image, story_content, story_created_at, story_updated_at, chapter_number, chapter_name, title, chapter_content, word_count, rating, published_at, view_count, chapter_created_at, chapter_updated_at, genre_name, list_name, list_content, is_public, list_created_at, list_updated_at, notification_content, is_read, type, link, notification_created_at, original_text, suggested_text, accepted, suggestion_created_at, applied_at} |
| | 127 | Step 8: Decompose R₇ → COMMENT + R₈ — common attribute: comment_id → primary key of COMMENT |
| | 128 | R₈ = All attributes of R, except: {username, email, user_name, surname, password, user_created_at, user_updated_at, mature_content, short_description, image, story_content, story_created_at, story_updated_at, chapter_number, chapter_name, title, chapter_content, word_count, rating, published_at, view_count, chapter_created_at, chapter_updated_at, genre_name, list_name, list_content, is_public, list_created_at, list_updated_at, notification_content, is_read, type, link, notification_created_at, original_text, suggested_text, accepted, suggestion_created_at, applied_at, comment_content, comment_created_at, comment_updated_at} |
| | 129 | Step 9: Decompose R₈ → STATUS + R₉ — common attribute: story_id → primary key of STORY |
| | 130 | R₉ = All attributes of R, except: {... same as R₈ ..., status} |
| | 131 | Step 10: Decompose R₉ → CONTENT_TYPE + R₁₀ — common attribute: notification_id → primary key of NOTIFICATION |
| | 132 | R₁₀ = All attributes of R, except: {... same as R₉ ..., content_type} |
| | 133 | Step 11: Decompose R₁₀ → SUGGESTION_TYPE + R₁₁ — common attribute: suggestion_id → primary key of AI_SUGGESTION |
| | 134 | R₁₁ = All attributes of R, except: {... same as R₁₀ ..., suggestion_type} |
| | 135 | Step 12: Decompose R₁₁ → LIKES + R₁₂ — common attributes: {user_id, story_id} → primary key of LIKES |
| | 136 | R₁₂ = All attributes of R, except: {... same as R₁₁ ..., like_created_at} |
| | 137 | Step 13: Decompose R₁₂ → COLLABORATION + R₁₃ — common attributes: {user_id, story_id} → primary key of COLLABORATION |
| | 138 | R₁₃ = All attributes of R, except: {... same as R₁₂ ..., collab_created_at} |
| | 139 | Step 14: Decompose R₁₃ → ROLES + R₁₄ — common attributes: {user_id, story_id} → primary key of ROLES |
| | 140 | R₁₄ = All attributes of R, except: {... same as R₁₃ ..., roles} |
| | 141 | Step 15: Decompose R₁₄ → PERMISSION_LEVEL + R₁₅ — common attributes: {user_id, story_id} → primary key of PERMISSION_LEVEL |
| | 142 | R₁₅ = All attributes of R, except: {... same as R₁₄ ..., permission_level |
| | 143 | Step 16: Decompose R₁₅ → HAS_GENRE + R₁₆ — common attributes: {story_id, genre_id} → primary key of HAS_GENRE |
| | 144 | R₁₆ = All attributes of R, except: {... same as R₁₅ ..., genre_id} |
| | 145 | Step 17: Decompose R₁₆ → READING_LIST_ITEMS + R₁₇ — common attributes: {list_id, story_id} → primary key of READING_LIST_ITEMS |
| | 146 | R₁₇ = All attributes of R, except: {... same as R₁₆ ..., added_at |
| | 147 | Step 18: Decompose R₁₇ → NOTIFY + R₁₈ — common attributes: {user_id, story_id, notification_id} → primary key of NOTIFY |
| | 148 | R₁₈ = {suggestion_id, story_id, chapter_id} — only the attributes of NEED_APPROVAL remain |
| | 149 | Step 19: Decompose R₁₈ → NEED_APPROVAL (final relation) — common attributes: {suggestion_id, story_id, chapter_id} → primary key of NEED_APPROVAL |
| | 150 | R₁₉ = ∅ — all attributes fully decomposed |
| | 151 | At every step the common attributes form a primary key in the extracted relation, so the entire decomposition is lossless and no information is lost. |