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
23 changes: 23 additions & 0 deletions calculate_largest_expensors.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
USE memory.default;

-- NOTE: employee and expense tables must be created first
-- Aggregate first, then join to minimise rows being joined
-- Report employees who have expensed more than 1000 in total
-- LEFT JOIN on manager to handle employees without a manager (e.g. CEO)
SELECT
e.employee_id,
e.first_name || ' ' || e.last_name AS employee_name,
e.manager_id,
m.first_name || ' ' || m.last_name AS manager_name,
ex.total_expensed_amount
FROM (
SELECT
employee_id,
SUM(unit_price * quantity) AS total_expensed_amount
FROM expense
GROUP BY employee_id
HAVING SUM(unit_price * quantity) > 1000
) ex
INNER JOIN employee e ON e.employee_id = ex.employee_id
LEFT JOIN employee m ON m.employee_id = e.manager_id
ORDER BY ex.total_expensed_amount DESC;
29 changes: 29 additions & 0 deletions create_employees.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
USE memory.default;

-- Drop table if exists to allow re-running the script
DROP TABLE IF EXISTS employee;

-- Create the EMPLOYEE table based on hr/employee_index.csv
-- employee_id and manager_id are TINYINT as per requirements
CREATE TABLE employee
(
employee_id TINYINT,
first_name VARCHAR,
last_name VARCHAR,
job_title VARCHAR,
manager_id TINYINT
);

-- Insert all employees from hr/employee_index.csv
INSERT INTO employee
VALUES
(1, 'Ian', 'James', 'CEO', 4),
(2, 'Umberto', 'Torrielli', 'CSO', 1),
(3, 'Alex', 'Jacobson', 'MD EMEA', 2),
(4, 'Darren', 'Poynton', 'CFO', 2),
(5, 'Tim', 'Beard', 'MD APAC', 2),
(6, 'Gemma', 'Dodd', 'COS', 1),
(7, 'Lisa', 'Platten', 'CHR', 6),
(8, 'Stefano', 'Camisaca', 'GM Activation', 2),
(9, 'Andrea', 'Ghibaudi', 'MD NAM', 2)
;
34 changes: 34 additions & 0 deletions create_expenses.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
USE memory.default;

-- NOTE: employee table must be created first (see create_employees.sql)

-- Drop table if exists to allow re-running the script
DROP TABLE IF EXISTS expense;

-- Create the EXPENSE table
CREATE TABLE expense (
employee_id TINYINT,
unit_price DECIMAL(8, 2),
quantity TINYINT
);

-- Insert expenses from finance/receipts_from_last_night
-- Mapping employee names to IDs via INNER JOIN on employee table
-- Expenses without a matching employee are excluded
INSERT INTO expense
SELECT
e.employee_id,
r.unit_price,
r.quantity
FROM (
VALUES
('Alex Jacobson', DECIMAL '6.50', TINYINT '14'),
('Alex Jacobson', DECIMAL '11.00', TINYINT '20'),
('Alex Jacobson', DECIMAL '22.00', TINYINT '18'),
('Alex Jacobson', DECIMAL '13.00', TINYINT '75'),
('Andrea Ghibaudi', DECIMAL '300.00', TINYINT '1'),
('Darren Poynton', DECIMAL '40.00', TINYINT '9'),
('Umberto Torrielli', DECIMAL '17.50', TINYINT '4')
) AS r(employee_name, unit_price, quantity)
INNER JOIN employee e
ON e.first_name || ' ' || e.last_name = r.employee_name;
50 changes: 50 additions & 0 deletions create_invoices.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
USE memory.default;

DROP TABLE IF EXISTS invoice;
DROP TABLE IF EXISTS supplier;
DROP TABLE IF EXISTS invoices_raw;

-- Raw invoice data exactly as in finance/invoices_due, no modifications
CREATE TABLE invoices_raw (
company_name VARCHAR,
invoice_amount DECIMAL(8, 2),
months_due TINYINT
);

INSERT INTO invoices_raw VALUES
('Catering Plus', DECIMAL '2000.00', 2),
('Catering Plus', DECIMAL '1500.00', 3),
('Dave''s Discos', DECIMAL '500.00', 1),
('Entertainment Tonight', DECIMAL '6000.00', 3),
('Ice Ice Baby', DECIMAL '4000.00', 6),
('Party Animals', DECIMAL '6000.00', 3);

-- Create supplier table
-- Deduplicate first, then apply RANK() for supplier_id
CREATE TABLE supplier (
supplier_id TINYINT,
name VARCHAR
);

INSERT INTO supplier
SELECT
CAST(RANK() OVER (ORDER BY company_name asc) AS TINYINT) AS supplier_id,
company_name AS name
FROM (SELECT DISTINCT company_name FROM invoices_raw);

-- Create invoice table
-- invoice_ammount is intentional typo from README spec
CREATE TABLE invoice (
supplier_id TINYINT,
invoice_ammount DECIMAL(8, 2),
due_date DATE
);

INSERT INTO invoice
SELECT
s.supplier_id,
r.invoice_amount,
date_trunc('month', current_date + r.months_due * interval '1' month)
+ interval '1' month - interval '1' day AS due_date
FROM invoices_raw r
INNER JOIN supplier s ON s.name = r.company_name;
35 changes: 35 additions & 0 deletions find_manager_cycles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
USE memory.default;

-- NOTE: employee table must be created first (see create_employees.sql)
-- Find cycles in the manager approval hierarchy using recursive CTE
-- A cycle exists when an employee appears as their own ancestor in the manager chain
-- Results show each employee in a cycle and the full cycle chain as comma-separated employee_ids
WITH RECURSIVE manager_chain(start_id, manager_id, chain, depth) AS (
-- Base case: start traversal from each employee
SELECT
employee_id AS start_id,
manager_id,
CAST(employee_id AS VARCHAR) AS chain,
1 AS depth
FROM employee

UNION ALL

-- Recursive case: follow manager chain upward
-- Stop if employee already appears in chain (cycle detected) or depth limit reached
SELECT
mc.start_id,
e.manager_id,
mc.chain || ',' || CAST(e.employee_id AS VARCHAR),
mc.depth + 1
FROM manager_chain mc
INNER JOIN employee e ON e.employee_id = mc.manager_id
WHERE mc.depth < 10
AND POSITION(CAST(e.employee_id AS VARCHAR) IN mc.chain) = 0
)
-- Return only employees where traversal loops back to the start
SELECT
start_id AS employee_id,
chain
FROM manager_chain
WHERE manager_id = start_id;
55 changes: 55 additions & 0 deletions generate_supplier_payment_plans.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
USE memory.default;

-- NOTE: invoice and supplier tables must be created first
-- Generate monthly payment plan per supplier
-- Step 1: explode each invoice into monthly payments
-- floor rate for all months except last to avoid rounding errors
-- last month rate = remainder to ensure exact total
-- Step 2: group by supplier + payment_date to sum rates across multiple invoices
-- Step 3: calculate balance_outstanding using window function
WITH exploded AS (
SELECT
s.supplier_id,
s.name AS supplier_name,
i.invoice_ammount,
date_diff('month', date_trunc('month', current_date), i.due_date) AS months_due,
month_number,
date_trunc('month', current_date + month_number * interval '1' month)
+ interval '1' month - interval '1' day AS payment_date,
CASE
-- All months except last: floor to avoid rounding up
WHEN month_number < date_diff('month', date_trunc('month', current_date), i.due_date)
THEN CAST(FLOOR(i.invoice_ammount / date_diff('month', date_trunc('month', current_date), i.due_date)) AS DECIMAL(8,2))
-- Last month: remainder to ensure invoice total is exact
ELSE i.invoice_ammount - CAST(FLOOR(i.invoice_ammount / date_diff('month', date_trunc('month', current_date), i.due_date)) AS DECIMAL(8,2))
* (date_diff('month', date_trunc('month', current_date), i.due_date) - 1)
END AS monthly_rate
FROM invoice i
INNER JOIN supplier s ON s.supplier_id = i.supplier_id
CROSS JOIN UNNEST(SEQUENCE(1, date_diff('month', date_trunc('month', current_date), i.due_date))) AS t(month_number)
),
monthly AS (
-- Group by payment_date (not month_number) to correctly sum rates
-- across multiple invoices for the same supplier in the same month
SELECT
supplier_id,
supplier_name,
payment_date,
SUM(monthly_rate) AS payment_amount
FROM exploded
GROUP BY supplier_id, supplier_name, payment_date
)
SELECT
supplier_id,
supplier_name,
payment_amount,
-- Balance outstanding = total supplier amount minus cumulative payments so far
SUM(payment_amount) OVER (PARTITION BY supplier_id) -
SUM(payment_amount) OVER (
PARTITION BY supplier_id
ORDER BY payment_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS balance_outstanding,
payment_date
FROM monthly
ORDER BY supplier_id, payment_date;
Loading