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

-- Report every employee whose total expensed amount exceeds 1000.
-- total_expensed_amount = SUM(unit_price * quantity) across all their EXPENSE rows.
-- Results ordered by total_expensed_amount descending.

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,
CAST(SUM(ex.unit_price * ex.quantity) AS DECIMAL(10, 2)) AS total_expensed_amount
FROM EMPLOYEE e
JOIN EXPENSE ex ON e.employee_id = ex.employee_id
JOIN EMPLOYEE m ON e.manager_id = m.employee_id
GROUP BY e.employee_id, e.first_name, e.last_name,
e.manager_id, m.first_name, m.last_name
HAVING SUM(ex.unit_price * ex.quantity) > 1000
ORDER BY total_expensed_amount DESC;
20 changes: 20 additions & 0 deletions create_employees.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
USE memory.default;

CREATE TABLE EMPLOYEE (
employee_id TINYINT,
first_name VARCHAR,
last_name VARCHAR,
job_title VARCHAR,
manager_id TINYINT
);

INSERT INTO EMPLOYEE (employee_id, first_name, last_name, job_title, manager_id) VALUES
(TINYINT '1', 'Ian', 'James', 'CEO', TINYINT '4'),
(TINYINT '2', 'Umberto', 'Torrielli','CSO', TINYINT '1'),
(TINYINT '3', 'Alex', 'Jacobson', 'MD EMEA', TINYINT '2'),
(TINYINT '4', 'Darren', 'Poynton', 'CFO', TINYINT '2'),
(TINYINT '5', 'Tim', 'Beard', 'MD APAC', TINYINT '2'),
(TINYINT '6', 'Gemma', 'Dodd', 'COS', TINYINT '1'),
(TINYINT '7', 'Lisa', 'Platten', 'CHR', TINYINT '6'),
(TINYINT '8', 'Stefano', 'Camisaca', 'GM Activation', TINYINT '2'),
(TINYINT '9', 'Andrea', 'Ghibaudi', 'MD NAM', TINYINT '2');
22 changes: 22 additions & 0 deletions create_expenses.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
USE memory.default;

CREATE TABLE EXPENSE (
employee_id TINYINT,
unit_price DECIMAL(8, 2),
quantity TINYINT
);

-- Employee names resolved to IDs from EMPLOYEE table.
-- Source files: finance/receipts_from_last_night/
INSERT INTO EXPENSE (employee_id, unit_price, quantity) VALUES
-- Alex Jacobson (employee_id = 3)
(TINYINT '3', DECIMAL '6.50', TINYINT '14'),
(TINYINT '3', DECIMAL '11.00', TINYINT '20'),
(TINYINT '3', DECIMAL '22.00', TINYINT '18'),
(TINYINT '3', DECIMAL '13.00', TINYINT '75'),
-- Andrea Ghibaudi (employee_id = 9)
(TINYINT '9', DECIMAL '300.00', TINYINT '1'),
-- Darren Poynton (employee_id = 4)
(TINYINT '4', DECIMAL '40.00', TINYINT '9'),
-- Umberto Torrielli (employee_id = 2)
(TINYINT '2', DECIMAL '17.50', TINYINT '4');
42 changes: 42 additions & 0 deletions create_invoices.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
USE memory.default;


CREATE TABLE SUPPLIER (
supplier_id TINYINT,
name VARCHAR
);

-- Column name 'invoice_ammount' preserved double 'm' as a typo expecting "production" is the same
CREATE TABLE INVOICE (
supplier_id TINYINT,
invoice_ammount DECIMAL(8, 2),
due_date DATE
);

-- supplier_id assigned in strict alphabetical order by company name
INSERT INTO SUPPLIER (supplier_id, name) VALUES
(TINYINT '1', 'Catering Plus'),
(TINYINT '2', 'Dave''s Discos'),
(TINYINT '3', 'Entertainment tonight'),
(TINYINT '4', 'Ice Ice Baby'),
(TINYINT '5', 'Party Animals');

-- Due dates are computed dynamically: "N months from now" = last day of month N months ahead.
INSERT INTO INVOICE (supplier_id, invoice_ammount, due_date)
SELECT
CAST(supplier_id AS TINYINT),
CAST(invoice_ammount AS DECIMAL(8, 2)),
date_add('day', -1,
date_add('month', months_from_now + 1,
date_trunc('month', current_date)
)
) AS due_date
FROM (
VALUES
(1, 2000.00, 2), -- brilliant_bottles.txt: Catering Plus, 2 months
(1, 1500.00, 3), -- crazy_catering.txt: Catering Plus, 3 months
(2, 500.00, 1), -- disco_dj.txt: Dave's Discos, 1 month
(3, 6000.00, 3), -- excellent_entertainment: Entertainment tonight, 3 months
(4, 4000.00, 6), -- fantastic_ice_sculptures: Ice Ice Baby, 6 months
(5, 6000.00, 3) -- awesome_animals.txt: Party Animals, 3 months
) AS t(supplier_id, invoice_ammount, months_from_now);
51 changes: 51 additions & 0 deletions find_manager_cycles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
USE memory.default;

-- Detect employees who are part of a manager approval cycle.
--
-- Algorithm walks the manager chain from each employee.
-- The VISITED array prevents infinite loops when the path enters someone else's cycle)
-- When current_id returns to start_id the cycle is confirmed and the row surfaces in the final SELECT.
--
-- Output: one row per employee in a cycle.
-- employee_id -- the employee involved in the cycle
-- cycle -- the full cycle path as a string (e.g. '1 -> 4 -> 2 -> 1')

-- Step 1: materialise the recursive expansion into a temp table to reduce query stages
CREATE TABLE manager_chain_temp AS
WITH RECURSIVE manager_chain(start_id, current_id, path, visited) AS (

-- Base: step from each employee to their direct manager
SELECT
employee_id AS start_id,
manager_id AS current_id,
CAST(employee_id AS VARCHAR) || ' -> ' || CAST(manager_id AS VARCHAR) AS path,
ARRAY[employee_id] AS visited
FROM EMPLOYEE
WHERE manager_id IS NOT NULL

UNION ALL

-- Recursive: follow the manager chain one step further
SELECT
mc.start_id,
e.manager_id AS current_id,
mc.path || ' -> ' || CAST(e.manager_id AS VARCHAR) AS path,
mc.visited || ARRAY[mc.current_id] AS visited
FROM manager_chain mc
JOIN EMPLOYEE e ON mc.current_id = e.employee_id
WHERE e.manager_id IS NOT NULL
AND mc.current_id <> mc.start_id -- stop once the cycle closes
AND NOT contains(mc.visited, mc.current_id) -- avoid re-traversing visited nodes
)
SELECT * FROM manager_chain;

-- Step 2: surface only the rows where the chain looped back to the starting employee
SELECT
start_id AS employee_id,
path AS cycle
FROM manager_chain_temp
WHERE current_id = start_id
ORDER BY employee_id;

-- Step 3: clean up temp table
DROP TABLE manager_chain_temp;
94 changes: 94 additions & 0 deletions generate_supplier_payment_plans.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
USE memory.default;

-- Hypothesis:
-- Payments begin at the end of the current month.
-- For each invoice, the number of monthly payments equals the number of full calendar months between now and the invoice due date.
-- Each monthly contribution is invoice_ammount / num_payments (uniform split).
-- The LAST payment per invoice absorbs any rounding remainder so that the sum of payments equals invoice_ammount exactly (balance closes to 0.00).
-- Suppliers with multiple invoices receive ONE combined payment per month.

WITH
-- Step 1: per invoice, compute how many monthly payments are needed
invoice_schedule AS (
SELECT
supplier_id,
invoice_ammount,
due_date,
date_diff(
'month',
date_trunc('month', current_date),
date_trunc('month', due_date)
) AS num_payments
FROM INVOICE
),

-- Step 2: expand each invoice into one row per payment, computing the per-payment amount.
payment_series AS (
SELECT
s.supplier_id,
s.invoice_ammount,
s.num_payments,
k,
CASE
WHEN k = s.num_payments
-- Last payment: residual to ensure exact closure
THEN s.invoice_ammount
- CAST((s.num_payments - 1) AS DECIMAL(10, 4))
* ROUND(
CAST(s.invoice_ammount AS DECIMAL(10, 4))
/ CAST(s.num_payments AS DECIMAL(10, 4)),
2
)
ELSE
ROUND(
CAST(s.invoice_ammount AS DECIMAL(10, 4))
/ CAST(s.num_payments AS DECIMAL(10, 4)),
2
)
END AS actual_payment,
date_add('day', -1,
date_add('month', k,
date_trunc('month', current_date)
)
) AS payment_date
FROM invoice_schedule s
CROSS JOIN UNNEST(sequence(1, s.num_payments)) AS t(k)
),

-- Step 3: combine all invoice contributions into one payment per (supplier, month)
monthly_payments AS (
SELECT
supplier_id,
payment_date,
CAST(SUM(actual_payment) AS DECIMAL(8, 2)) AS payment_amount
FROM payment_series
GROUP BY supplier_id, payment_date
),

-- Step 4: total outstanding balance per supplier
supplier_total AS (
SELECT
supplier_id,
CAST(SUM(invoice_ammount) AS DECIMAL(8, 2)) AS total_balance
FROM INVOICE
GROUP BY supplier_id
)

-- Final: join with SUPPLIER name and compute running balance_outstanding
SELECT
mp.supplier_id,
s.name AS supplier_name,
mp.payment_amount,
CAST(
st.total_balance
- SUM(mp.payment_amount) OVER (
PARTITION BY mp.supplier_id
ORDER BY mp.payment_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
AS DECIMAL(8, 2)) AS balance_outstanding,
mp.payment_date
FROM monthly_payments mp
JOIN SUPPLIER s ON mp.supplier_id = s.supplier_id
JOIN supplier_total st ON mp.supplier_id = st.supplier_id
ORDER BY mp.supplier_id, mp.payment_date;
Loading