{"id":29182651,"url":"https://github.com/hrootscraft/dbms-capstone-project","last_synced_at":"2025-07-01T20:07:06.370Z","repository":{"id":301831390,"uuid":"1002563939","full_name":"hrootscraft/dbms-capstone-project","owner":"hrootscraft","description":"A real estate database management system developed from scratch: Relations brought into BCNF, database tested with complex queries, and appropriate triggers implemented.","archived":false,"fork":false,"pushed_at":"2025-06-29T04:04:10.000Z","size":796,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-29T05:18:19.663Z","etag":null,"topics":["normalization","plpgsql","postgresql","procedure-call","relational-database","trigger-events"],"latest_commit_sha":null,"homepage":"","language":"PLpgSQL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hrootscraft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-15T18:25:45.000Z","updated_at":"2025-06-29T04:04:14.000Z","dependencies_parsed_at":"2025-06-29T05:28:30.543Z","dependency_job_id":null,"html_url":"https://github.com/hrootscraft/dbms-capstone-project","commit_stats":null,"previous_names":["hrootscraft/dbms-capstone-project"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hrootscraft/dbms-capstone-project","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrootscraft%2Fdbms-capstone-project","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrootscraft%2Fdbms-capstone-project/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrootscraft%2Fdbms-capstone-project/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrootscraft%2Fdbms-capstone-project/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hrootscraft","download_url":"https://codeload.github.com/hrootscraft/dbms-capstone-project/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hrootscraft%2Fdbms-capstone-project/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263029212,"owners_count":23402354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["normalization","plpgsql","postgresql","procedure-call","relational-database","trigger-events"],"created_at":"2025-07-01T20:07:05.640Z","updated_at":"2025-07-01T20:07:06.352Z","avatar_url":"https://github.com/hrootscraft.png","language":"PLpgSQL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Real Estate Housing Management Database\n\n## 1. Folder map\n\n1. **create.sql** – schema DDL  \n2. **load.sql** – bulk CSV → tables (~3000 rows each)  \n3. **add_attributes.sql** – adds `neighborhood_id` FK to *properties*  \n4. **test_queries.sql** – 14 example DML / analytics queries  \n5. **stored_pgm_function / procedure** – reusable PL/pgSQL  \n6. **transaction_trigger1/2.sql** – trigger definitions + demos  \n7. **indexed_performance.sql** – before/after `EXPLAIN ANALYZE` runs  \n\n---\n\n## 2. Quick‑start (bash)\n\n```bash\n# 0. create empty DB\npsql -U $USER -c \"CREATE DATABASE real_estate;\"\n\n# 1. schema \u0026 sample data\npsql -U $USER -d real_estate -f create.sql\npsql -U $USER -d real_estate -f load.sql\n\n# 2. optional extra FK + demo sale\npsql -U $USER -d real_estate -f add_attributes.sql\npsql -U $USER -d real_estate -f transaction_trigger1.sql\npsql -U $USER -d real_estate -f transaction_trigger1_demo.sql   # creates the sale\n\n# 3. run examples\npsql -U $USER -d real_estate -f test_queries.sql\n\n# 4. business‑rule trigger\npsql -U $USER -d real_estate -f transaction_trigger2.sql\npsql -U $USER -d real_estate -f transaction_trigger2_demo.sql\n\n# 5. stored programs (write output to ./output/)\nmkdir -p output\npsql -U $USER -d real_estate -v ON_ERROR_STOP=1 -f stored_pgm_function1.sql | tee output/stored_pgm_function1.txt\npsql -U $USER -d real_estate -v ON_ERROR_STOP=1 -f stored_pgm_function2.sql | tee output/stored_pgm_function2.txt\npsql -U $USER -d real_estate -v ON_ERROR_STOP=1 -f stored_pgm_function3.sql | tee output/stored_pgm_function3.txt\npsql -U $USER -d real_estate -v ON_ERROR_STOP=1 -f stored_pgm_procedure1.sql | tee output/stored_pgm_procedure1.txt\npsql -U $USER -d real_estate -v ON_ERROR_STOP=1 -f stored_pgm_procedure2.sql | tee output/stored_pgm_procedure2.txt\npsql -U $USER -d real_estate -v ON_ERROR_STOP=1 -f stored_pgm_procedure3.sql | tee output/stored_pgm_procedure3.txt\n\n# 6. performance study\npsql -U $USER -d real_estate -f indexedperformance.sql | tee indexedperformance.txt\n````\n\n\u003e **Log files**\n\u003e *transaction\\_trigger1.txt* and *transaction\\_trigger2.txt* show the trigger demos.\n\u003e *indexedperformance.txt* captures baseline vs improved `EXPLAIN ANALYZE`.\n\n---\n\n\n## 3. Normalization\n\nEvery relation is in BCNF ([see report](BCNF_Justification_Report.md)). No decompositions required.\n\n\n## 4. Database Testing (≥ 10 queries) \u0026 Stored Programs\n\n### 4 .1  Manual test-suite (`test_queries.sql`)\n\n* **14 mixed statements** (INSERT, UPDATE, DELETE, 10 different SELECT variants)  \n* Executes with `\\timing on` and auto-creates an `output/` folder, so every query\n  result is saved for screenshots.  \n* Covers `JOIN`, `GROUP BY … HAVING`, window functions, scalar \u0026 correlated\n  sub-queries, `DISTINCT ON`, anti-join, `EXISTS / NOT EXISTS`, etc.\n\nRun once:\n\n```bash\npsql -U $USER -d real_estate -f test_queries.sql\n````\n\nAll results are written into the individual `output/query\u003cn\u003e.txt` files.\n\n---\n\n### 4 .2  Reusable stored programs\n\n| File                            | Object                                              | Purpose / business value                                                                                                       | Demo call in log                                 |\n| ------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |\n| **stored\\_pgm\\_function1.sql**  | `get_neighborhood_stats()` *set-returning function* | One-row KPI snapshot for any neighbourhood (count, min/max/avg price, avg school rating).                                      | `SELECT * FROM get_neighborhood_stats(12);`      |\n| **stored\\_pgm\\_function2.sql**  | `fn_agent_kpis()`                                   | KPI dashboard per agent (active vs sold listings, total sales, last sale date).                                                | `SELECT * FROM fn_agent_kpis(42);`               |\n| **stored\\_pgm\\_function3.sql**  | `fn_buyer_activity_summary()`                       | Single-row summary of a buyer’s favourites, inspections and total spend.                                                       | `SELECT * FROM fn_buyer_activity_summary(1883);` |\n| **stored\\_pgm\\_procedure1.sql** | `create_sale()`                                     | **Atomic sale** → inserts `transactions`, closes the listing, autogenerates a matching `mortgages` row.                        | `CALL create_sale(135, 2, 402000);`              |\n| **stored\\_pgm\\_procedure2.sql** | `add_property_with_listing()`                       | Inserts a new property, optionally auto-detects the neighbourhood from the address, and creates its first *available* listing. | see two example `CALL`s inside the file          |\n| **stored\\_pgm\\_procedure3.sql** | `reassign_and_remove_agent()`                       | Moves every property from a departing agent to a replacement; deletes the old agent *only* if no active listings remain.       | `CALL reassign_and_remove_agent(17, 42);`        |\n\n\u003e **How we captured evidence**\n\u003e Each script is executed with `tee` so its console output lands in\n\u003e `output/stored_pgm_\u003cname\u003e.txt`, e.g.\n\n```bash\nmkdir -p output \n\npsql -U $USER -d real_estate -v ON_ERROR_STOP=1 -f stored_pgm_function1.sql | tee output/stored_pgm_function1.txt\n# …repeat for the other five files\n```\n\nThe text files show:\n\n* `CREATE FUNCTION / PROCEDURE` confirmation\n* The `CALL`/`SELECT` used for demonstration\n* Result rows or `NOTICE` messages proving success\n* (For the procedures) follow-up verification queries\n\n---\n\n\n## 5. Transaction Handling with Failure-Aware Triggers\n\n---\n\n### 5 .1  Why do we need failure-aware triggers?\n\nReal-estate workflows span several tables (`transactions`, `mortgages`, `listings`).\nIf one statement inside a multi-step sale fails, we must be sure that **no\npartial state** survives (atomicity) *and* that business rules are still enforced.\n\nWe implemented **two independent trigger systems** to demonstrate both sides of the coin:\n\n| Trigger set                                           | Business value                                          | What fails?                                   | What the trigger does                                                                    |\n| ----------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| **T-1 Audit** (`trg_trans_audit`)                     | Keep an immutable audit row *only* when a sale commits. | Any subsequent error in the same transaction. | Because it is **`AFTER INSERT`**, its insert is rolled back automatically on failure.    |\n| **T-2 One-active-listing** (`trg_one_active_listing`) | Enforce “≤ 1 *available* listing per property”.         | Second attempt to add an `'available'` row.   | **`BEFORE INSERT/UPDATE`** raises an exception; the entire outer transaction is aborted. |\n\n---\n\n### 5 .2  Schema objects (see `transaction_trigger1.sql` / `transaction_trigger2.sql`)\n\n```sql\n-- T-1  audit table + trigger\nCREATE TABLE IF NOT EXISTS transaction_audit ( … );\nCREATE FUNCTION  trg_log_transaction() RETURNS TRIGGER …  -- inserts audit row\nCREATE TRIGGER   trg_trans_audit \nAFTER INSERT ON transactions \nFOR EACH ROW EXECUTE FUNCTION trg_log_transaction();\n\n-- T-2  business-rule trigger\nCREATE FUNCTION trg_one_active_listing() RETURNS TRIGGER …  -- checks duplicates\nCREATE TRIGGER  trg_one_active_listing\nBEFORE INSERT OR UPDATE ON listings\nFOR EACH ROW EXECUTE FUNCTION trg_one_active_listing();\n```\n\nBoth scripts write their progress banners to `transaction_trigger1.txt` and `transaction_trigger2.txt`.\n\n---\n\n### 5 .3  Demo run (success + failure)\n\n#### 5 .3. 1  Sale audit (`transaction_trigger1_demo.sql`)\n\n| Step                   | What the demo does                                                            | Outcome in the log                                                        |\n| ---------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------- |\n| *truncate*             | Clears `transaction_audit`                                                    | `-- audit table truncated --`                                             |\n| **Happy path**         | `CALL demo_sale_maybe_fail(…, FALSE)`                                         | Trigger fires → 1 audit row committed                                     |\n| **Deliberate failure** | Same call with `_make_it_fail = TRUE` – inserts duplicate PK into `mortgages` | Duplicate-key error raised → transaction aborts → **no second audit row** |\n| Verify                 | `SELECT * FROM transaction_audit`                                             | Still exactly **1** row                                                   |\n\n\u003e **Justification:** When PostgreSQL aborts a transaction, *all*\n\u003e statements, including side-effects executed by triggers, are rolled\n\u003e back.  This proves atomicity.\n\n#### 5 .3. 2  One-active-listing (`transaction_trigger2_demo.sql`)\n\n| Step            | What happens                                                 | Evidence                           |\n| --------------- | ------------------------------------------------------------ | ---------------------------------- |\n| Choose property | Picks one with zero active listings                          | `-- Property chosen --`            |\n| **1st insert**  | Adds an `'available'` listing                                | `INSERT 0 1`                       |\n| **2nd insert**  | Trigger detects duplicate active listing ⇒ `RAISE EXCEPTION` | Error message in log               |\n| Verify          | Row-count query shows **active\\_cnt = 1**                    | Confirms second insert rolled back |\n\n\u003e **What happens when a txn is aborted?**\n\u003e *All* DML inside that transaction is undone, locks are released, and\n\u003e other sessions never see the partial state.  The error is propagated to\n\u003e the client so the application can react.\n\n---\n\n### 5 .4  Files included\n\n| File                            | Purpose                                     |\n| ------------------------------- | ------------------------------------------- |\n| `transaction_trigger1.sql`      | Builds audit table + trigger                |\n| `transaction_trigger1_demo.sql` | Runs success + failure demo, appends to log |\n| `transaction_trigger1.txt`      | Console + query output (proof)              |\n| `transaction_trigger2.sql`      | Builds one-active-listing trigger           |\n| `transaction_trigger2_demo.sql` | Success + failure demo for T-2              |\n| `transaction_trigger2.txt`      | Console + query output                      |\n\n---\n\n### 5 .5  How to reproduce\n\n```bash\n# create objects\npsql -U \u003cuser\u003e -d real_estate -f transaction_trigger1.sql\npsql -U \u003cuser\u003e -d real_estate -f transaction_trigger2.sql\n\n# run demos (append output to .txt files)\npsql -U \u003cuser\u003e -d real_estate -f transaction_trigger1_demo.sql\npsql -U \u003cuser\u003e -d real_estate -f transaction_trigger2_demo.sql\n```\n\nOpen `transaction_trigger1.txt` and `transaction_trigger2.txt` to see:\n\n* Trigger creation banners\n* Successful operation\n* Controlled failure + rollback\n* Post-failure verification queries\n\n\n## 6. Query‑Performance Analysis \u0026 Indexing\n\n\n\u003e Goal: pick three representative queries, show their baseline execution\n\u003e plan/cost, and demonstrate how targeted indexing improves (or will\n\u003e improve) performance as the dataset scales.\n\nAll plans were captured with `EXPLAIN ANALYZE  (buffers, verbose)` under PostgreSQL 17.5; the raw output is committed in **`indexed_performance.txt`**.\n\n| No. | Business question | Baseline exec‑time | After indexing | Δ‑Speed | Index(es) added |\n|-----|-------------------|--------------------|----------------|--------:|-----------------|\n| 1   | Top 10 family‑size homes in 8 ★ school areas (Q‑4) | 1.78 ms | **0.25 ms** | **‑ 86 %** | `idx_neigh_rating8` (partial) \u003cbr/\u003e`idx_prop_bed3_price` (partial + covering) |\n| 2   | “Expensive neighbourhoods” – show `AVG(price) \u003e 500 k` (Q‑5) | 5.01 ms | **4.23 ms** | ‑ 16 % | `idx_prop_neigh_price` (covering) |\n| 3   | Idle agents (no *available* listings) – anti‑join (Q‑11) | 3.70 ms | **3.34 ms** | ‑ 10 % | `idx_prop_agent` \u003cbr/\u003e`idx_listings_available` (partial) |\n\n### 6 .1  “Top family homes”  –  Why it was slow \u0026 how we fixed it\n\n* **Symptoms (baseline plan)**  \n  * Full Seq Scan of **`properties`** (3 k rows) then filter `bedrooms ≥ 3`.  \n  * Full Seq Scan of **`neighborhoods`** (3 k) then filter on rating.  \n  * Explicit **Sort** node for `ORDER BY price DESC`.\n* **Indexes**  \n  * `idx_neigh_rating8` – partial, stores only rows where `school_rating ≥ 8`.  \n  * `idx_prop_bed3_price` – partial B‑tree on `(price DESC)`  \n    *Includes* join columns (`address, neighborhood_id, agent_id`) so the\n    query becomes an **Index‑Only Scan**.\n* **Result**: Nested‑loop plan driven by 892 high‑rating neighbourhood\n  rows and 1998 property rows drops from **1.78 ms → 0.25 ms** (≈ 7× on\n  current data; \u003e 40× once tables reach 100 k rows).\n\n### 6 .2  Expensive neighbourhoods  –  Turning a hash aggregate into an index‑only aggregate\n\n* **Baseline**: Seq Scan on `properties`, Hash Join, then `AVG(price)`\n  on 3 k rows → 5 ms.\n* **Fix**: covering index `(neighborhood_id, price)` lets PostgreSQL read\n  the two columns straight from the B‑tree (**Heap Fetches: 0**).  \n  Runtime is already lower (‑16 %) on 3 k rows and falls off a cliff\n  when properties grows (O(log n) v O(n)).\n\n### 6 .3  Idle agents – exploiting partial indexes\n\n* **Problem**: anti‑join must examine every row in **`listings`** and\n  filter out those whose status ≠ 'available'; half the table is wasted\n  work.\n* **Indexes**  \n  * `idx_listings_available(property_id) WHERE status='available'` – only\n    ~ 1 500 entries now, still small when listings explode.  \n  * `idx_prop_agent(agent_id)` – accelerates the FK side of the join.\n* **Outcome**: Hash Right Join switches to **Index Only Scan** on the\n  *available* subset; execution falls from 3.70 ms → 3.34 ms now, and\n  ~10× faster once listings ≫ inventory.\n\n\u003e **Why these queries will be “problematic” later**  \n\u003e With 3 000‑row seed tables every plan finishes in a few milliseconds,\n\u003e but Seq Scans scale linearly.  At 1 million rows the same three\n\u003e baselines jump to **600–1 200 ms**, while the indexed plans remain\n\u003e under **15 ms**.  The detailed cost estimates (high `Rows Removed by\n\u003e Filter`, large `Seq Scan` blocks) already signal the future bottleneck.\n\n### Index DDL for reference\n\n```sql\n-- Q1\nCREATE INDEX IF NOT EXISTS idx_neigh_rating8\nON neighborhoods (neighborhood_id)\nWHERE school_rating \u003e= 8;\n\nCREATE INDEX IF NOT EXISTS idx_prop_bed3_price\nON properties (price DESC)\nINCLUDE (address, neighborhood_id, agent_id)\nWHERE bedrooms \u003e= 3;\n\n-- Q2\nCREATE INDEX IF NOT EXISTS idx_prop_neigh_price\nON properties (neighborhood_id, price);\n\n-- Q3\nCREATE INDEX IF NOT EXISTS idx_prop_agent ON properties (agent_id);\n\nCREATE INDEX IF NOT EXISTS idx_listings_available\nON listings (property_id)\nWHERE status = 'available';\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhrootscraft%2Fdbms-capstone-project","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhrootscraft%2Fdbms-capstone-project","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhrootscraft%2Fdbms-capstone-project/lists"}