-
Notifications
You must be signed in to change notification settings - Fork 3
Oleksandr Starshynov w3 databases #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # Exercise 1: SQL Normalization – Dinner Club | ||
|
|
||
| ## Step 1: Violation of 1NF (First Normal Form) | ||
|
|
||
| **1NF Rule**: Each field must contain only **atomic** (indivisible) values. | ||
|
|
||
| ### ❌ Violations of 1NF: | ||
| - **`food_code`** and **`food_description`** contain **multiple values** separated by commas (e.g., `"C1, C2"`, `"Curry, Cake"`). | ||
| - **`dinner_date`** has **inconsistent formats** (e.g., `"2020-03-15"`, `"20-03-2020"`, `"Mar 25 '20"`), violating domain consistency. | ||
|
|
||
| --- | ||
|
|
||
| ## Step 2: Recognized Entities | ||
|
|
||
| Based on the table, we can identify the following entities: | ||
|
|
||
| 1. **Member** – identified by `member_id`. | ||
| 2. **Dinner** – identified by `dinner_id`. | ||
| 3. **Venue** – identified by `venue_code`. | ||
| 4. **Food** – identified by `food_code`. | ||
| 5. **Dinner Participation** – mapping between members and dinners (many-to-many). | ||
| 6. **Dinner Food** – mapping between dinners and foods served (many-to-many). | ||
|
|
||
| --- | ||
|
|
||
| ## Step 3: 3NF-Compliant Tables and Attributes | ||
|
|
||
| ### 1. **Members** | ||
| | Column Name | Type | | ||
| |-----------------|-------------| | ||
| | member_id (PK) | INTEGER | | ||
| | member_name | TEXT | | ||
| | member_address | TEXT | | ||
|
|
||
| --- | ||
|
|
||
| ### 2. **Venues** | ||
| | Column Name | Type | | ||
| |---------------------|---------| | ||
| | venue_code (PK) | TEXT | | ||
| | venue_description | TEXT | | ||
|
|
||
| --- | ||
|
|
||
| ### 3. **Dinners** | ||
| | Column Name | Type | | ||
| |-----------------|-----------| | ||
| | dinner_id (PK) | TEXT | | ||
| | dinner_date | DATE | | ||
| | venue_code (FK) | TEXT | | ||
|
|
||
| --- | ||
|
|
||
| ### 4. **Foods** | ||
| | Column Name | Type | | ||
| |--------------------|---------| | ||
| | food_code (PK) | TEXT | | ||
| | food_description | TEXT | | ||
|
|
||
| --- | ||
|
|
||
| ### 5. **Dinner_Participation** (Many-to-Many: members attend multiple dinners) | ||
| | Column Name | Type | | ||
| |--------------------|---------| | ||
| | member_id (FK) | INTEGER | | ||
| | dinner_id (FK) | TEXT | | ||
|
|
||
| **Primary Key**: (member_id, dinner_id) | ||
|
|
||
| --- | ||
|
|
||
| ### 6. **Dinner_Food** (Many-to-Many: dinners serve multiple foods) | ||
| | Column Name | Type | | ||
| |--------------------|---------| | ||
| | dinner_id (FK) | TEXT | | ||
| | food_code (FK) | TEXT | | ||
|
|
||
| **Primary Key**: (dinner_id, food_code) | ||
|
|
||
| --- | ||
|
|
||
| ## Summary of Normalization | ||
|
|
||
| - The **original table** violates **1NF** due to multi-valued fields and inconsistent formats. | ||
| - We **decomposed** the table into **six 3NF-compliant tables** by: | ||
| - Ensuring atomicity of values, | ||
| - Removing transitive and partial dependencies, | ||
| - Isolating repeating groups into separate relations. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import mysql from "mysql2/promise"; | ||
|
|
||
| const connection = await mysql.createConnection({ | ||
| host: "localhost", | ||
| user: "hyfuser", | ||
| password: "hyfpassword", | ||
| database: "w3_assignment", | ||
| }); | ||
|
|
||
| await connection.beginTransaction(); | ||
|
||
|
|
||
| const fromAccount = 101; | ||
| const toAccount = 102; | ||
| const amount = 1000; | ||
| const date = "2025-06-02"; | ||
|
|
||
| const transactionFrom = { | ||
| account_number: fromAccount, | ||
| amount: -amount, | ||
| changed_date: date, | ||
| remark: `Transfer to account #${toAccount}`, | ||
| }; | ||
|
|
||
| const transactionTo = { | ||
| account_number: toAccount, | ||
| amount: amount, | ||
| changed_date: date, | ||
| remark: `Transfer from account #${fromAccount}`, | ||
| }; | ||
|
||
|
|
||
| try { | ||
| const [availableBalanceResult] = await connection.query( | ||
| "SELECT balance FROM account WHERE account_number = ?", | ||
| [fromAccount] | ||
| ); | ||
|
|
||
| const availableBalance = availableBalanceResult[0]?.balance; | ||
|
|
||
| if (availableBalance < amount) { | ||
| throw new Error("Insufficient balance to perform the transfer"); | ||
| } | ||
|
|
||
| console.log(`Account #${fromAccount} available balance: ${availableBalance}`); | ||
|
|
||
| // Deduct from sender | ||
| await connection.query("INSERT INTO account_changes SET ?", transactionFrom); | ||
| await connection.query( | ||
| "UPDATE account SET balance = balance + ? WHERE account_number = ?", | ||
| [transactionFrom.amount, fromAccount] | ||
| ); | ||
| console.log(`$${amount} deducted from account #${fromAccount}`); | ||
|
|
||
| // Add to receiver | ||
| await connection.query("INSERT INTO account_changes SET ?", transactionTo); | ||
| await connection.query( | ||
| "UPDATE account SET balance = balance + ? WHERE account_number = ?", | ||
| [transactionTo.amount, toAccount] | ||
| ); | ||
| console.log(`$${amount} added to account #${toAccount}`); | ||
|
|
||
| await connection.commit(); | ||
| console.log("Transaction completed successfully."); | ||
| } catch (error) { | ||
| await connection.rollback(); | ||
| console.error("Transaction failed:", error.message); | ||
| } finally { | ||
| await connection.end(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import mysql from 'mysql2/promise'; | ||
|
|
||
| async function createTables() { | ||
| const connection = await mysql.createConnection({ | ||
| host: 'localhost', | ||
| user: 'hyfuser', | ||
| password: 'hyfpassword', | ||
| database: 'w3_assignment', | ||
| }); | ||
|
|
||
| try { | ||
| await connection.beginTransaction(); | ||
|
|
||
| await connection.query(` | ||
| CREATE TABLE IF NOT EXISTS account ( | ||
| account_number INT PRIMARY KEY, | ||
| balance DECIMAL(12, 2) NOT NULL | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A 100 billion is a nice big number for this, well done! |
||
| ) | ||
| `); | ||
|
|
||
| await connection.query(` | ||
| CREATE TABLE IF NOT EXISTS account_changes ( | ||
| change_number INT AUTO_INCREMENT PRIMARY KEY, | ||
| account_number INT, | ||
| amount DECIMAL(12, 2) NOT NULL, | ||
| changed_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||
| remark TEXT, | ||
| FOREIGN KEY (account_number) REFERENCES account(account_number) | ||
| ) | ||
| `); | ||
|
|
||
| await connection.commit(); | ||
| console.log('Tables created successfully'); | ||
| } catch (err) { | ||
| await connection.rollback(); | ||
| console.error('Error creating tables:', err); | ||
| } finally { | ||
| await connection.end(); | ||
| } | ||
| } | ||
|
|
||
| createTables(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import mysql from 'mysql2/promise'; | ||
|
|
||
| async function insertValues() { | ||
| const connection = await mysql.createConnection({ | ||
| host: 'localhost', | ||
| user: 'hyfuser', | ||
| password: 'hyfpassword', | ||
| database: 'w3_assignment', | ||
| }); | ||
|
|
||
| try { | ||
| await connection.beginTransaction(); | ||
|
|
||
| await connection.query(` | ||
| INSERT INTO account (account_number, balance) VALUES | ||
| (101, 5000.00), | ||
| (102, 3000.00) | ||
| `); | ||
|
|
||
| await connection.query(` | ||
| INSERT INTO account_changes (account_number, amount, remark) VALUES | ||
| (101, 5000.00, 'Initial deposit'), | ||
| (102, 3000.00, 'Initial deposit') | ||
| `); | ||
|
|
||
| await connection.commit(); | ||
| console.log('Values inserted successfully'); | ||
| } catch (err) { | ||
| await connection.rollback(); | ||
| console.error('Error inserting values:', err); | ||
| } finally { | ||
| await connection.end(); | ||
| } | ||
| } | ||
|
|
||
| insertValues(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| 1. - Give an example of a value that can be passed as name and code that would take advantage of SQL-injection and ( fetch all the records in the database) | ||
| - answer: | ||
| - SELECT Population FROM Countries WHERE Name = '' OR '1'='1' and code = '' OR '1'='1' | ||
| - I think it would work sinse '1'='1' is always true the query will return all rows in the table, ignoring filtering by name and code. | ||
|
|
||
| 2. | ||
| ``` | ||
| function getPopulation(Country, name, code, cb) { | ||
| const allowedTables = ['Countries', 'OtherValidTable']; | ||
| if (!allowedTables.includes(Country)) { | ||
| return cb(new Error("Invalid table name")); | ||
| } | ||
|
|
||
| const sql = `SELECT Population FROM ${Country} WHERE Name = ? AND code = ?`; | ||
|
|
||
| conn.query(sql, [name, code], function (err, result) { | ||
| if (err) return cb(err); | ||
| if (result.length === 0) return cb(new Error("Not found")); | ||
| cb(null, result[0].Population); | ||
| }); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is all correct! The layout and language are triggering my AI-spider sense a bit. If you figured all of this out on your own, I'm completely ok with it if you asked ChappyG to format it. For me the most important thing is that you know what and why things are happening.