diff --git a/02_activities/assignments/DC_Cohort/Assignment1.md b/02_activities/assignments/DC_Cohort/Assignment1.md index d5ff55ce4..60c26fd6c 100644 --- a/02_activities/assignments/DC_Cohort/Assignment1.md +++ b/02_activities/assignments/DC_Cohort/Assignment1.md @@ -207,7 +207,13 @@ Link if you encounter a paywall: https://archive.is/srKHV or https://web.archive Consider, for example, concepts of fariness, inequality, social structures, marginalization, intersection of technology and society, etc. +Databases appear neutral because they organize information through fields, categories, and rules. However, deciding which information matters and which entries are acceptable involves values. In “When Databases Get to Define Family”, Pakistan’s identification system embeds assumptions about marriage and biological family relationships. People whose circumstances do not fit those assumptions can struggle to access essential services. This example makes me consider how everyday systems also turn social expectations into requirements that shape people’s opportunities. -``` -Your thoughts... -``` + +School databases offer an example. Recording grades, attendance, and submission times reflects values of achievement, accountability, and punctuality. These values can support learning, but the records provide an incomplete picture of a student’s circumstances. A late assignment could reflect poor planning, unreliable internet, paid work, disability, or caregiving responsibilities. If a system treats every late submission as equivalent, equal treatment can produce unequal consequences. Fairness requires room for context and a meaningful way to question decisions based on those records. + +Shopping and social media systems also reveal competing priorities. When browsing histories and interactions become inputs for recommendations, a person is represented through measurable behavior. The values embedded in such systems depend partly on what their designers choose to reward. Optimizing for purchases or continued attention can make commercial goals more influential than privacy, autonomy, or the user’s longer-term interests. I would question whether a recommendation helps someone make a choice or mainly encourages behavior that benefits the platform. + +These concerns can overlap. Someone dealing with unstable housing, limited income, and a disability may encounter several barriers at once. Requiring an address, constant connectivity, and rapid responses could seem reasonable when each requirement is considered separately. Together, those requirements can systematically disadvantage the same person. This is why evaluating individual fields is insufficient: the whole process matters. + +For me, ethical database design means questioning what a system requires people to prove and what happens when they cannot. Designers should collect only necessary information, accommodate different circumstances, and provide accessible correction and appeal processes. They should also involve people likely to be excluded. Efficiency matters, but it should be evaluated alongside dignity and access. A successful system must be able to serve people whose lives differ from the assumptions built into its categories. \ No newline at end of file diff --git a/02_activities/assignments/DC_Cohort/Assignment2.md b/02_activities/assignments/DC_Cohort/Assignment2.md index 72f8c71fd..ce16ebf2a 100644 --- a/02_activities/assignments/DC_Cohort/Assignment2.md +++ b/02_activities/assignments/DC_Cohort/Assignment2.md @@ -56,10 +56,35 @@ The store wants to keep customer addresses. Propose two architectures for the CU **HINT:** search type 1 vs type 2 slowly changing dimensions. -``` -Your answer... -``` +The store wants to keep customer addresses. I propose two architectures for the CUSTOMER_ADDRESS table. + +Architecture A: overwrite the old address (Type 1) + +CUSTOMER_ADDRESS +- customer_id (PK, FK to CUSTOMER) +- street +- city +- province +- postal_code + +There is one row per customer. When a customer moves, the existing row is updated with the new address, so the old address is lost. This is a Type 1 slowly changing dimension. It is simple, keeps the table small, and always shows the current address, but it keeps no history. + +Architecture B: retain changes (Type 2) + +CUSTOMER_ADDRESS +- address_id (PK) +- customer_id (FK to CUSTOMER) +- street +- city +- province +- postal_code +- effective_start_date +- effective_end_date +- is_current + +A customer can have many rows. When a customer moves, the old row is closed (its effective_end_date is set and is_current becomes false) and a new row is inserted with the new address. This is a Type 2 slowly changing dimension. It keeps the full address history, so the store can see where a customer lived when an order was placed, but the table grows and queries need to filter on is_current to get the current address. +So the design that overwrites is Type 1, and the design that retains changes is Type 2. *** ## Section 2: @@ -191,7 +216,16 @@ Read: Boykis, V. (2019, October 16). _Neural nets are just people all the way do Consider, for example, concepts of labour, bias, LLM proliferation, moderating content, intersection of technology and society, ect. +### Ethics -``` -Your thoughts... -``` +Boykis’s article challenges the idea that artificial intelligence operates independently of people. Her discussion of ImageNet and its underlying language classifications shows that machine learning depends on human labour and human decisions. The ethical issue is therefore broader than whether an algorithm produces accurate results: it also concerns whose work makes the system possible, whose assumptions it reproduces, and who bears the consequences when it fails. + +One important issue is the visibility and treatment of workers. Boykis describes people completing image-labelling tasks for very small payments, while prominent researchers receive recognition for the resulting datasets. This raises questions about how the benefits of technological development should be shared. In my view, treating annotation as simple clicking undervalues the judgement involved. Ethical development should include fair compensation, clear working expectations, opportunities to challenge rejected work, and recognition of workers’ contributions. A system should not count as a success solely because its development costs are low if those savings depend on workers having little bargaining power. + +Bias is another central concern. The article traces image classifications through older systems of organising language and discusses offensive or inappropriate categories applied to people. These examples show that categories themselves require scrutiny. Even perfectly consistent labelling cannot make an unjustified category ethically acceptable. For example, we should question whether a person’s character or social identity can reasonably be inferred from a photograph at all. Developers should ask who created a category, what assumptions it contains, and whether people affected by it have a meaningful say in its use. + +Content moderation presents a related ethical dilemma. Extending the article’s argument, asking people to identify harmful material may help protect users, but protecting one group should not require treating another group’s wellbeing as expendable. A responsible moderation process should provide appropriate support, reasonable workloads, and ways for workers to raise concerns. It should also make room for disagreement: deciding whether something is harmful can require cultural context and interpretation, rather than a supposedly universal label. + +The proliferation of large language models raises a further question about reuse. Although Boykis’s article focuses on image recognition, its argument invites us to consider what happens when human judgements become foundations for many other systems. Reusing a dataset or model should carry an obligation to examine its origins and limitations. Otherwise, expansion risks multiplying assumptions that users cannot see or contest. Organisations should be prepared to explain their choices, correct harmful outcomes, and reconsider deployments whose social costs outweigh their benefits. + +Ultimately, I read the article as a call for accountability throughout technological development. Calling a decision automated should never remove responsibility from the people and organisations that designed, funded, and deployed the system. Ethical AI requires fair treatment of workers, scrutiny of classifications, and meaningful ways for affected people to challenge decisions. Technical performance matters, but it should be evaluated alongside dignity, fairness, and the distribution of power. diff --git a/02_activities/assignments/DC_Cohort/assignment1.sql b/02_activities/assignments/DC_Cohort/assignment1.sql index a80923591..d201363e4 100644 --- a/02_activities/assignments/DC_Cohort/assignment1.sql +++ b/02_activities/assignments/DC_Cohort/assignment1.sql @@ -7,7 +7,8 @@ /* 1. Write a query that returns everything in the customer table. */ --QUERY 1 - +SELECT * +FROM customer; --END QUERY @@ -16,7 +17,10 @@ /* 2. Write a query that displays all of the columns and 10 rows from the customer table, sorted by customer_last_name, then customer_first_ name. */ --QUERY 2 - +SELECT * +FROM customer +ORDER BY customer_last_name, customer_first_name +LIMIT 10; @@ -27,7 +31,10 @@ sorted by customer_last_name, then customer_first_ name. */ /* 1. Write a query that returns all customer purchases of product IDs 4 and 9. Limit to 25 rows of output. */ --QUERY 3 - +SELECT * +FROM customer_purchases +WHERE product_id IN (4, 9) +LIMIT 25; @@ -42,7 +49,10 @@ filtered by customer IDs between 8 and 10 (inclusive) using either: Limit to 25 rows of output. */ --QUERY 4 - +SELECT *, quantity * cost_per_quantity AS price +FROM customer_purchases +WHERE customer_id BETWEEN 8 AND 10 +LIMIT 25; @@ -55,7 +65,11 @@ Using the product table, write a query that outputs the product_id and product_n columns and add a column called prod_qty_type_condensed that displays the word “unit” if the product_qty_type is “unit,” and otherwise displays the word “bulk.” */ --QUERY 5 - +SELECT product_id, product_name, + CASE WHEN product_qty_type = 'unit' THEN 'unit' + ELSE 'bulk' + END AS prod_qty_type_condensed +FROM product; @@ -66,7 +80,14 @@ if the product_qty_type is “unit,” and otherwise displays the word “bulk. add a column to the previous query called pepper_flag that outputs a 1 if the product_name contains the word “pepper” (regardless of capitalization), and otherwise outputs 0. */ --QUERY 6 - +SELECT product_id, product_name, + CASE WHEN product_qty_type = 'unit' THEN 'unit' + ELSE 'bulk' + END AS prod_qty_type_condensed, + CASE WHEN product_name LIKE '%pepper%' THEN 1 + ELSE 0 + END AS pepper_flag +FROM product; @@ -78,7 +99,12 @@ contains the word “pepper” (regardless of capitalization), and otherwise out vendor_id field they both have in common, and sorts the result by market_date, then vendor_name. Limit to 24 rows of output. */ --QUERY 7 - +SELECT * +FROM vendor +INNER JOIN vendor_booth_assignments + ON vendor.vendor_id = vendor_booth_assignments.vendor_id +ORDER BY market_date, vendor_name +LIMIT 24; @@ -92,7 +118,9 @@ Limit to 24 rows of output. */ /* 1. Write a query that determines how many times each vendor has rented a booth at the farmer’s market by counting the vendor booth assignments per vendor_id. */ --QUERY 8 - +SELECT vendor_id, COUNT(*) AS booth_rentals +FROM vendor_booth_assignments +GROUP BY vendor_id; @@ -105,7 +133,14 @@ of customers for them to give stickers to, sorted by last name, then first name. HINT: This query requires you to join two tables, use an aggregate function, and use the HAVING keyword. */ --QUERY 9 - +SELECT c.customer_first_name, c.customer_last_name, + SUM(cp.quantity * cp.cost_per_quantity) AS total_spent +FROM customer AS c +INNER JOIN customer_purchases AS cp + ON c.customer_id = cp.customer_id +GROUP BY c.customer_id +HAVING SUM(cp.quantity * cp.cost_per_quantity) > 2000 +ORDER BY c.customer_last_name, c.customer_first_name; @@ -124,7 +159,11 @@ When inserting the new vendor, you need to appropriately align the columns to be VALUES(col1,col2,col3,col4,col5) */ --QUERY 10 +CREATE TABLE temp.new_vendor AS +SELECT * FROM vendor; +INSERT INTO temp.new_vendor (vendor_id, vendor_name, vendor_type, vendor_owner_first_name, vendor_owner_last_name) +VALUES (10, 'Thomass Superfood Store', 'Fresh Focused', 'Thomas', 'Rosenthal'); @@ -138,7 +177,11 @@ HINT: you might need to search for strfrtime modifers sqlite on the web to know and year are! Limit to 25 rows of output. */ --QUERY 11 - +SELECT customer_id, + STRFTIME('%m', market_date) AS month, + STRFTIME('%Y', market_date) AS year +FROM customer_purchases +LIMIT 25; @@ -152,7 +195,12 @@ HINTS: you will need to AGGREGATE, GROUP BY, and filter... but remember, STRFTIME returns a STRING for your WHERE statement... AND be sure you remove the LIMIT from the previous query before aggregating!! */ --QUERY 12 - +SELECT customer_id, + SUM(quantity * cost_per_quantity) AS total_spent +FROM customer_purchases +WHERE STRFTIME('%m', market_date) = '04' + AND STRFTIME('%Y', market_date) = '2022' +GROUP BY customer_id; diff --git a/02_activities/assignments/DC_Cohort/assignment2.sql b/02_activities/assignments/DC_Cohort/assignment2.sql index 4079c18ae..c8686a6b5 100644 --- a/02_activities/assignments/DC_Cohort/assignment2.sql +++ b/02_activities/assignments/DC_Cohort/assignment2.sql @@ -22,7 +22,9 @@ The `||` values concatenate the columns into strings. Edit the appropriate columns -- you're making two edits -- and the NULL rows will be fixed. All the other rows will remain the same. */ --QUERY 1 - +SELECT +product_name || ', ' || COALESCE(product_size, '') || ' (' || COALESCE(product_qty_type, 'unit') || ')' +FROM product; @@ -40,7 +42,10 @@ each new market date for each customer, or select only the unique market dates p HINT: One of these approaches uses ROW_NUMBER() and one uses DENSE_RANK(). Filter the visits to dates before April 29, 2022. */ --QUERY 2 - +SELECT *, + DENSE_RANK() OVER (PARTITION BY customer_id ORDER BY market_date) AS visit_number +FROM customer_purchases +WHERE market_date < '2022-04-29'; @@ -52,7 +57,13 @@ then write another query that uses this one as a subquery (or temp table) and fi only the customer’s most recent visit. HINT: Do not use the previous visit dates filter. */ --QUERY 3 - +SELECT * +FROM ( + SELECT *, + DENSE_RANK() OVER (PARTITION BY customer_id ORDER BY market_date DESC) AS visit_number + FROM customer_purchases +) AS t +WHERE visit_number = 1; @@ -65,7 +76,10 @@ customer_purchases table that indicates how many different times that customer h You can make this a running count by including an ORDER BY within the PARTITION BY if desired. Filter the visits to dates before April 29, 2022. */ --QUERY 4 - +SELECT *, + COUNT(*) OVER (PARTITION BY customer_id, product_id ORDER BY market_date) AS times_purchased +FROM customer_purchases +WHERE market_date < '2022-04-29'; @@ -84,7 +98,11 @@ Remove any trailing or leading whitespaces. Don't just use a case statement for Hint: you might need to use INSTR(product_name,'-') to find the hyphens. INSTR will help split the column. */ --QUERY 5 - +SELECT product_name, + CASE WHEN INSTR(product_name, '-') > 0 + THEN TRIM(SUBSTR(product_name, INSTR(product_name, '-') + 1)) + END AS description +FROM product; @@ -93,7 +111,12 @@ Hint: you might need to use INSTR(product_name,'-') to find the hyphens. INSTR w /* 2. Filter the query to show any product_size value that contain a number with REGEXP. */ --QUERY 6 - +SELECT product_name, product_size, + CASE WHEN INSTR(product_name, '-') > 0 + THEN TRIM(SUBSTR(product_name, INSTR(product_name, '-') + 1)) + END AS description +FROM product +WHERE product_size REGEXP '[0-9]'; @@ -110,7 +133,20 @@ HINT: There are a possibly a few ways to do this query, but if you're struggling 3) Query the second temp table twice, once for the best day, once for the worst day, with a UNION binding them. */ --QUERY 7 - +WITH daily AS ( + SELECT market_date, SUM(quantity * cost_per_quantity) AS total_sales + FROM customer_purchases + GROUP BY market_date +), +ranked AS ( + SELECT market_date, total_sales, + RANK() OVER (ORDER BY total_sales DESC) AS best_rank, + RANK() OVER (ORDER BY total_sales ASC) AS worst_rank + FROM daily +) +SELECT market_date, total_sales, 'best day' AS label FROM ranked WHERE best_rank = 1 +UNION +SELECT market_date, total_sales, 'worst day' FROM ranked WHERE worst_rank = 1; @@ -131,7 +167,15 @@ Think a bit about the row counts: how many distinct vendors, product names are t How many customers are there (y). Before your final group by you should have the product of those two queries (x*y). */ --QUERY 8 - +SELECT vendor_name, product_name, SUM(5 * original_price) AS potential_revenue +FROM ( + SELECT DISTINCT v.vendor_name, p.product_name, vi.original_price + FROM vendor_inventory AS vi + INNER JOIN vendor AS v ON vi.vendor_id = v.vendor_id + INNER JOIN product AS p ON vi.product_id = p.product_id +) AS vp +CROSS JOIN customer +GROUP BY vendor_name, product_name; @@ -144,7 +188,10 @@ This table will contain only products where the `product_qty_type = 'unit'`. It should use all of the columns from the product table, as well as a new column for the `CURRENT_TIMESTAMP`. Name the timestamp column `snapshot_timestamp`. */ --QUERY 9 - +CREATE TABLE product_units AS +SELECT *, CURRENT_TIMESTAMP AS snapshot_timestamp +FROM product +WHERE product_qty_type = 'unit'; @@ -154,7 +201,10 @@ Name the timestamp column `snapshot_timestamp`. */ /*2. Using `INSERT`, add a new row to the product_units table (with an updated timestamp). This can be any product you desire (e.g. add another record for Apple Pie). */ --QUERY 10 - +INSERT INTO product_units +SELECT *, CURRENT_TIMESTAMP +FROM product +WHERE product_name = 'Apple Pie'; @@ -166,7 +216,13 @@ This can be any product you desire (e.g. add another record for Apple Pie). */ HINT: If you don't specify a WHERE clause, you are going to have a bad time.*/ --QUERY 11 - +DELETE FROM product_units +WHERE product_name = 'Apple Pie' + AND snapshot_timestamp = ( + SELECT MIN(snapshot_timestamp) + FROM product_units + WHERE product_name = 'Apple Pie' + ); @@ -190,7 +246,17 @@ Finally, make sure you have a WHERE statement to update the right row, you'll need to use product_units.product_id to refer to the correct row within the product_units table. When you have all of these components, you can run the update statement. */ --QUERY 12 +ALTER TABLE product_units +ADD current_quantity INT; +UPDATE product_units +SET current_quantity = COALESCE(( + SELECT vi.quantity + FROM vendor_inventory AS vi + WHERE vi.product_id = product_units.product_id + ORDER BY vi.market_date DESC + LIMIT 1 +), 0); diff --git a/02_activities/assignments/DC_Cohort/bookstore_erd_1.png b/02_activities/assignments/DC_Cohort/bookstore_erd_1.png new file mode 100644 index 000000000..8049b574d Binary files /dev/null and b/02_activities/assignments/DC_Cohort/bookstore_erd_1.png differ diff --git a/02_activities/assignments/DC_Cohort/bookstore_erd_2.png b/02_activities/assignments/DC_Cohort/bookstore_erd_2.png new file mode 100644 index 000000000..35ea76f6c Binary files /dev/null and b/02_activities/assignments/DC_Cohort/bookstore_erd_2.png differ diff --git a/02_activities/assignments/DC_Cohort/er_diagram.png b/02_activities/assignments/DC_Cohort/er_diagram.png new file mode 100644 index 000000000..afb7b43d0 Binary files /dev/null and b/02_activities/assignments/DC_Cohort/er_diagram.png differ