Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions Week2/scripts/ex3_1_keys.js
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'))
Comment on lines +57 to +58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of SMALLINT for h_index here. For gender, you can also use SMALLINT as it is like enum. The disadvantage of using SMALLINT is 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.

);
`);

// 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();
127 changes: 127 additions & 0 deletions Week2/scripts/ex3_2_relationships.js
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();
58 changes: 58 additions & 0 deletions Week2/scripts/ex3_3_joins.js
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 = []) {
Copy link

Choose a reason for hiding this comment

The 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();
91 changes: 91 additions & 0 deletions Week2/scripts/ex3_4_aggregates.js
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
Copy link

Choose a reason for hiding this comment

The 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
Copy link

Choose a reason for hiding this comment

The 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();
Loading