-
Notifications
You must be signed in to change notification settings - Fork 6
Vlad-2week-DB #13
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?
Vlad-2week-DB #13
Changes from all commits
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,92 @@ | ||
| // ex3_1_keys.js | ||
| // Week2 – Exercise 3.1 (Keys) | ||
| // Creates DB "research", table authors, and adds self-referencing FK "mentor". | ||
|
|
||
| import dotenv from "dotenv"; | ||
| import pg from "pg"; | ||
| dotenv.config(); | ||
|
|
||
| const { Client } = pg; | ||
|
|
||
| const DB_NAME = "research"; | ||
|
|
||
| const admin = new Client({ | ||
| host: process.env.PGHOST, | ||
| port: process.env.PGPORT, | ||
| user: process.env.PGUSER, | ||
| password: process.env.PGPASSWORD, | ||
| database: "postgres", | ||
| }); | ||
|
|
||
| const research = (dbName = DB_NAME) => | ||
| new Client({ | ||
| host: process.env.PGHOST, | ||
| port: process.env.PGPORT, | ||
| user: process.env.PGUSER, | ||
| password: process.env.PGPASSWORD, | ||
| database: dbName, | ||
| }); | ||
|
|
||
| async function run() { | ||
| let root, db; | ||
| try { | ||
| // 1) Create DB "research" fresh (idempotent) | ||
| root = admin; | ||
| await root.connect(); | ||
| await root.query( | ||
| `SELECT pg_terminate_backend(pid) | ||
| FROM pg_stat_activity | ||
| WHERE datname = $1 AND pid <> pg_backend_pid();`, | ||
| [DB_NAME] | ||
| ); | ||
| await root.query(`DROP DATABASE IF EXISTS ${DB_NAME};`); | ||
| await root.query(`CREATE DATABASE ${DB_NAME};`); | ||
| await root.end(); | ||
| root = null; | ||
|
|
||
| // 2) Connect to research and create authors | ||
| db = research(); | ||
| await db.connect(); | ||
|
|
||
| await db.query(` | ||
| CREATE TABLE authors ( | ||
| author_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, | ||
| author_name TEXT NOT NULL, | ||
| university TEXT NOT NULL, | ||
| date_of_birth DATE, | ||
| h_index SMALLINT CHECK (h_index >= 0), | ||
| gender TEXT CHECK (gender IN ('male','female','nonbinary','other')) | ||
| ); | ||
| `); | ||
|
|
||
| // 3) Add mentor column that references authors.author_id (self FK) | ||
| await db.query(` | ||
| ALTER TABLE authors | ||
| ADD COLUMN mentor INTEGER NULL; | ||
| `); | ||
|
|
||
| await db.query(` | ||
| ALTER TABLE authors | ||
| ADD CONSTRAINT fk_authors_mentor | ||
| FOREIGN KEY (mentor) REFERENCES authors(author_id) | ||
| ON DELETE SET NULL; | ||
| `); | ||
|
|
||
| console.log(" ex3_1_keys: authors created, mentor FK added"); | ||
| } catch (e) { | ||
| console.error(" ex3_1_keys failed:", e); | ||
| process.exitCode = 1; | ||
| } finally { | ||
| if (root) { | ||
| try { | ||
| await root.end(); | ||
| } catch {} | ||
| } | ||
| if (db) { | ||
| try { | ||
| await db.end(); | ||
| } catch {} | ||
| } | ||
| } | ||
| } | ||
| run(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // ex3_2_relationships.js | ||
| // Week2 – Exercise 3.2 (Relationships) | ||
| // Adds research_papers, author_papers; inserts 15 authors, 30 papers, and links. | ||
|
|
||
| import dotenv from "dotenv"; | ||
| import pg from "pg"; | ||
| dotenv.config(); | ||
|
|
||
| const { Client } = pg; | ||
|
|
||
| const DB_NAME = "research"; | ||
| const db = new Client({ | ||
| host: process.env.PGHOST, | ||
| port: process.env.PGPORT, | ||
| user: process.env.PGUSER, | ||
| password: process.env.PGPASSWORD, | ||
| database: DB_NAME, | ||
| }); | ||
|
|
||
| async function run() { | ||
| try { | ||
| await db.connect(); | ||
|
|
||
| // 1) Tables for papers and m:n link | ||
| await db.query(` | ||
| CREATE TABLE IF NOT EXISTS research_papers ( | ||
| paper_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, | ||
| paper_title TEXT NOT NULL UNIQUE, | ||
| conference TEXT, | ||
| publish_date DATE | ||
| ); | ||
| `); | ||
|
|
||
| await db.query(` | ||
| CREATE TABLE IF NOT EXISTS author_papers ( | ||
| author_id INTEGER NOT NULL REFERENCES authors(author_id) ON DELETE CASCADE, | ||
| paper_id INTEGER NOT NULL REFERENCES research_papers(paper_id) ON DELETE CASCADE, | ||
| PRIMARY KEY (author_id, paper_id) | ||
| ); | ||
| `); | ||
|
|
||
| // 2) Seed 15 authors (some mentors set later) | ||
| const authors = [ | ||
| ["Alice Kim", "TU Delft", "1988-03-01", 12, "female"], | ||
| ["Bob Ivanov", "UvA", "1985-06-10", 18, "male"], | ||
| ["Carla Gomez", "VU", "1990-12-01", 9, "female"], | ||
| ["Dmitri Petrov", "TU Delft", "1982-07-22", 22, "male"], | ||
| ["Eve Tan", "Leiden", "1991-09-19", 7, "female"], | ||
| ["Felix Zhou", "TU/e", "1986-01-05", 15, "male"], | ||
| ["Gina Rossi", "UvA", "1992-04-14", 6, "female"], | ||
| ["Hiro Sato", "TU Delft", "1984-11-08", 24, "male"], | ||
| ["Ivy Wang", "Leiden", "1993-02-17", 5, "female"], | ||
| ["Jamal Noor", "VU", "1987-08-03", 11, "male"], | ||
| ["Kira Novak", "UvA", "1990-01-29", 8, "female"], | ||
| ["Leo Martins", "TU/e", "1983-03-13", 19, "male"], | ||
| ["Mina Park", "Leiden", "1994-04-04", 4, "female"], | ||
| ["Nate Quinn", "TU Delft", "1989-06-06", 13, "male"], | ||
| ["Ola Berg", "VU", "1985-10-10", 17, "male"], | ||
| ]; | ||
|
|
||
| await db.query(`TRUNCATE authors RESTART IDENTITY CASCADE;`); | ||
| for (const a of authors) { | ||
| await db.query( | ||
| `INSERT INTO authors(author_name,university,date_of_birth,h_index,gender) | ||
| VALUES ($1,$2,$3,$4,$5);`, | ||
| a | ||
| ); | ||
| } | ||
|
|
||
| await db.query( | ||
| `UPDATE authors SET mentor = 4 WHERE author_id IN (1,3,5);` | ||
| ); | ||
| await db.query( | ||
| `UPDATE authors SET mentor = 8 WHERE author_id IN (2,6,7);` | ||
| ); | ||
| await db.query( | ||
| `UPDATE authors SET mentor = 12 WHERE author_id IN (9,10,11);` | ||
| ); | ||
| await db.query( | ||
| `UPDATE authors SET mentor = 4 WHERE author_id IN (13,14,15);` | ||
| ); | ||
|
|
||
| // 3) Seed 30 papers | ||
| await db.query(`TRUNCATE research_papers RESTART IDENTITY CASCADE;`); | ||
| const confs = ["ICDB", "SIGMOD", "VLDB", "ICDE", "EDBT"]; | ||
| const today = new Date(); | ||
|
|
||
| for (let i = 1; i <= 30; i++) { | ||
| const title = `On Relational Patterns ${i}`; | ||
| const conf = confs[i % confs.length]; | ||
| const date = new Date(today.getFullYear() - (i % 8), i % 12, (i % 28) + 1) | ||
| .toISOString() | ||
| .slice(0, 10); | ||
| await db.query( | ||
| `INSERT INTO research_papers(paper_title,conference,publish_date) | ||
| VALUES ($1,$2,$3);`, | ||
| [title, conf, date] | ||
| ); | ||
| } | ||
|
|
||
| // 4) Link authors to papers | ||
| await db.query(`TRUNCATE author_papers;`); | ||
| for (let paperId = 1; paperId <= 30; paperId++) { | ||
| const a1 = ((paperId + 0) % 15) + 1; | ||
| const a2 = ((paperId + 3) % 15) + 1; | ||
| const a3 = ((paperId + 7) % 15) + 1; | ||
| const pick = new Set([a1, a2, ...(paperId % 3 === 0 ? [a3] : [])]); | ||
| for (const aid of pick) { | ||
| await db.query( | ||
| `INSERT INTO author_papers(author_id,paper_id) | ||
| VALUES ($1,$2) ON CONFLICT DO NOTHING;`, | ||
| [aid, paperId] | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| console.log(" ex3_2_relationships: papers, links, and seed inserted"); | ||
| } catch (e) { | ||
| console.error(" ex3_2_relationships failed:", e); | ||
| process.exitCode = 1; | ||
| } finally { | ||
| try { | ||
| await db.end(); | ||
| } catch {} | ||
| } | ||
| } | ||
| run(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // ex3_3_joins.js | ||
| // Week2 – Exercise 3.3 (Joins) | ||
|
|
||
| import dotenv from "dotenv"; | ||
| import pg from "pg"; | ||
| dotenv.config(); | ||
|
|
||
| const { Client } = pg; | ||
|
|
||
| const db = new Client({ | ||
| host: process.env.PGHOST, | ||
| port: process.env.PGPORT, | ||
| user: process.env.PGUSER, | ||
| password: process.env.PGPASSWORD, | ||
| database: "research", | ||
| }); | ||
|
|
||
| async function q(label, sql, params = []) { | ||
|
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. Suggestion: have a better name for the function, so when reading the code we know what this function is for easily. |
||
| const res = await db.query(sql, params); | ||
| console.log(`\n=== ${label} ===`); | ||
| console.table(res.rows.slice(0, 15)); | ||
| } | ||
|
|
||
| async function run() { | ||
| try { | ||
| await db.connect(); | ||
|
|
||
| // 1) names of all authors and their mentors | ||
| await q( | ||
| "Authors and their mentors", | ||
| `SELECT a.author_name AS author, m.author_name AS mentor | ||
| FROM authors a | ||
| LEFT JOIN authors m ON a.mentor = m.author_id | ||
| ORDER BY author;` | ||
| ); | ||
|
|
||
| // 2) all columns of authors and their published paper_title | ||
| // include authors without papers | ||
| await q( | ||
| "Authors and their papers (include authors without papers)", | ||
| `SELECT a.*, rp.paper_title | ||
| FROM authors a | ||
| LEFT JOIN author_papers ap ON ap.author_id = a.author_id | ||
| LEFT JOIN research_papers rp ON rp.paper_id = ap.paper_id | ||
| ORDER BY a.author_id, rp.paper_title NULLS LAST;` | ||
| ); | ||
|
|
||
| console.log("\n ex3_3_joins: done"); | ||
| } catch (e) { | ||
| console.error(" ex3_3_joins failed:", e); | ||
| process.exitCode = 1; | ||
| } finally { | ||
| try { | ||
| await db.end(); | ||
| } catch {} | ||
| } | ||
| } | ||
| run(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| // ex3_4_aggregates.js | ||
| // Week2 – Exercise 3.4 (Aggregate Functions) | ||
|
|
||
| import dotenv from "dotenv"; | ||
| import pg from "pg"; | ||
| dotenv.config(); | ||
|
|
||
| const { Client } = pg; | ||
|
|
||
| const db = new Client({ | ||
| host: process.env.PGHOST, | ||
| port: process.env.PGPORT, | ||
| user: process.env.PGUSER, | ||
| password: process.env.PGPASSWORD, | ||
| database: "research", | ||
| }); | ||
|
|
||
| async function q(label, sql, params = []) { | ||
| const res = await db.query(sql, params); | ||
| console.log(`\n=== ${label} ===`); | ||
| console.table(res.rows.slice(0, 20)); | ||
| } | ||
|
|
||
| async function run() { | ||
| try { | ||
| await db.connect(); | ||
|
|
||
| // 1) All research papers and the number of authors that wrote that paper | ||
| await q( | ||
| "Paper → number of authors", | ||
| `SELECT rp.paper_title, COUNT(ap.author_id) AS authors_count | ||
| FROM research_papers rp | ||
| LEFT JOIN author_papers ap ON ap.paper_id = rp.paper_id | ||
| GROUP BY rp.paper_id, rp.paper_title | ||
| ORDER BY authors_count DESC, rp.paper_title;` | ||
| ); | ||
|
|
||
| // 2) Sum of the research papers published by all female authors | ||
| await q( | ||
| "Total papers by female authors", | ||
| `SELECT SUM(cnt) AS total_papers_by_female | ||
| FROM ( | ||
| SELECT a.author_id, COUNT(ap.paper_id) AS cnt | ||
| FROM authors a | ||
| LEFT JOIN author_papers ap ON ap.author_id = a.author_id | ||
| WHERE a.gender = 'female' | ||
| GROUP BY a.author_id | ||
| ) x;` | ||
| ); | ||
|
Comment on lines
+39
to
+49
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. Suggestion: If a paper has two different female authors, this paper will be counted twice. Depending on the business requirements, this might be what we want. If you don't want to count the same paper twice, you need to change the query (use multiple JOIN instead of sub query). |
||
|
|
||
| // 3) Average of the h-index of all authors per university | ||
| await q( | ||
| "Average h-index per university", | ||
| `SELECT university, ROUND(AVG(h_index)::numeric, 2) AS avg_h_index | ||
| FROM authors | ||
| GROUP BY university | ||
| ORDER BY university;` | ||
| ); | ||
|
|
||
| // 4) Sum of the research papers of the authors per university | ||
| await q( | ||
| "Sum of papers per university", | ||
| `SELECT a.university, COUNT(ap.paper_id) AS papers_sum | ||
| FROM authors a | ||
| LEFT JOIN author_papers ap ON ap.author_id = a.author_id | ||
| GROUP BY a.university | ||
| ORDER BY papers_sum DESC, a.university;` | ||
|
Comment on lines
+63
to
+67
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. Similar to the female author query, this will count the same paper twice if it has several authors from the same university. |
||
| ); | ||
|
|
||
| // 5) Min/Max h-index per university | ||
| await q( | ||
| "Min/Max h-index per university", | ||
| `SELECT university, | ||
| MIN(h_index) AS min_h, | ||
| MAX(h_index) AS max_h | ||
| FROM authors | ||
| GROUP BY university | ||
| ORDER BY university;` | ||
| ); | ||
|
|
||
| console.log("\n ex3_4_aggregates: done"); | ||
| } catch (e) { | ||
| console.error(" ex3_4_aggregates failed:", e); | ||
| process.exitCode = 1; | ||
| } finally { | ||
| try { | ||
| await db.end(); | ||
| } catch {} | ||
| } | ||
| } | ||
| run(); | ||
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.
Good use of
SMALLINTforh_indexhere. Forgender, you can also useSMALLINTas it is like enum. The disadvantage of usingSMALLINTis that it is not so readable (when checking the table contents, people don't know 1 is female or male). But it is a common practice.