Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/scripts/grade_assignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def grade_files(files, output):
error = None
try:
result = subprocess.run(
[sys.executable, "-m", "pytest", "03_instructional_team/tests/test_assignment_2.py",
[sys.executable, "-m", "pytest", "03_instructional_team/tests/test_assignment.py",
f"--file_path={file_path}", f"--report_path={report}",
"--tb=short", "--disable-warnings"], timeout=120, check=False,
)
Expand Down
150 changes: 7 additions & 143 deletions .github/workflows/sql_assignment_runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ on:
workflow_dispatch: # Manual trigger from GitHub UI
inputs:
branch:
description: 'Assignment branch, e.g. assignment-one or assignment-two'
description: 'Assignment 2 branch to grade, usually assignment-two'
required: true
default: assignment-two
type: string
pr_number:
description: 'Pull request number. Extract a number from the end of pull request url, i.e. https://github.com/khsergvl/sql/pull/17'
Expand All @@ -21,10 +22,10 @@ permissions:
pull-requests: write

jobs:
run-assignment-one:
run-assignment-queries:
if: >-
(github.event_name == 'workflow_dispatch' && (inputs.branch == 'assignment-one' || inputs.branch == 'assignment-1')) ||
((github.head_ref == 'assignment-one' || github.head_ref == 'assignment-1') &&
github.event_name == 'workflow_dispatch' ||
((github.head_ref == 'assignment-two' || github.head_ref == 'assignment-2') &&
github.repository_owner != 'UofT-DSI')
runs-on: ubuntu-latest
permissions:
Expand All @@ -47,137 +48,6 @@ jobs:
run: |
pip install -r ./.github/scripts/requirements.txt

- name: Get changed files
id: changes
run: |
git fetch
git diff --name-only origin/main > changed_files.txt

echo "Changed files:"
cat changed_files.txt
assignment_changed=""

if grep -qE '(^|/)assignment1\.sql$' changed_files.txt; then
assignment_changed=$(grep -E '(^|/)assignment1\.sql$' changed_files.txt | head -n1)
fi

if [ -n "$assignment_changed" ]; then
echo "assignment_changed=$assignment_changed" >> "$GITHUB_OUTPUT"
fi

- name: Run Assignment 1 queries
id: assignment1
if: steps.changes.outputs.assignment_changed != ''
continue-on-error: true
env:
FILE_PATH: ${{ steps.changes.outputs.assignment_changed }}
run: |
pytest 03_instructional_team/tests/test_assignment_1.py --file_path="$FILE_PATH" --tb=short --disable-warnings \
--junitxml=pytest-report.xml

- name: Post Assignment 1 query results to PR
if: always() && steps.assignment1.outcome == 'success'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
with:
script: |
function jsonToMarkdownTable(data) {
if (!Array.isArray(data) || data.length === 0) {
return 'No data';
}

const headers = Object.keys(data[0]);

// Header row
const headerRow = `| ${headers.join(' | ')} |`;

// Separator row
const separatorRow = `| ${headers.map(() => '---').join(' | ')} |`;

// Data rows
const rows = data.map(row =>
`| ${headers.map(h => formatValue(row[h])).join(' | ')} |`
);

return [headerRow, separatorRow, ...rows].join('\n');
}

function formatValue(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'object') return `\`${JSON.stringify(value)}\``;
return String(value);
}

const fs = require('fs')
const file_read_result = fs.readFileSync('test-results.json', 'utf8')
const results = JSON.parse(file_read_result)

let body = `### 🧪 SQL Queries Run Results (up to 3 rows)\n\n`
body += `<details> <summary>Click to expand/collapse assignment queries execution results</summary>\n\n`

for (const result of results) {
const isArrayResult = Array.isArray(result.result);

if (result.error) {
body += `❌ Query ${result.number}: \n\n *${result.query}*, \n\n **Error**: \n${result.error}\n\n`
} else if (isArrayResult && result.result.length === 0) {
body += `⚪ Query ${result.number}: \n\n *${result.query}*, \n\n No rows returned \n\n`
} else if (isArrayResult && result.result.length > 0) {
body += `✅ Query ${result.number}: \n\n *${result.query}*, \n\n **Results**: \n`
const table = jsonToMarkdownTable(result.result)
body += `${table} \n\n`
}

body += `-------------------------------------------------------- \n`;
}

body += `</details>`

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.PR_NUMBER),
body
})

- name: Upload query results
if: always()
uses: actions/upload-artifact@v4
with:
name: a1-query-results
path: test-results.json
if-no-files-found: ignore

- name: Preserve runner failure status
if: always() && steps.assignment1.outcome == 'failure'
run: exit 1

run-assignment-two:
if: >-
(github.event_name == 'workflow_dispatch' && (inputs.branch == 'assignment-two' || inputs.branch == 'assignment-2')) ||
((github.head_ref == 'assignment-two' || github.head_ref == 'assignment-2') &&
github.repository_owner != 'UofT-DSI')
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: pip install -r ./.github/scripts/requirements.txt

- name: Verify grader regressions
run: python -m pytest 03_instructional_team/tests/test_grader_regressions.py -q

Expand Down Expand Up @@ -242,14 +112,8 @@ jobs:
let resultSection = `**Query ${result.number}: ${labels[result.status] || '⚪ Instructor review needed'}**\n\n`;
resultSection += `${result.feedback}\n\n`;
if (result.error) resultSection += fenced(result.error);
let details = '';
if (result.query) details += fenced(result.query, 'sql');
if (result.result?.length) details += fenced(JSON.stringify(result.result, null, 2), 'json');
if (details && result.status === 'correct') {
resultSection += `<details>\n<summary>SQL and output</summary>\n\n${details}\n</details>\n\n`;
} else {
resultSection += details;
}
if (result.query) resultSection += fenced(result.query, 'sql');
if (result.result?.length) resultSection += fenced(JSON.stringify(result.result, null, 2), 'json');
resultSection += '\n';
if (!appendSection(resultSection)) break reportLoop;
}
Expand Down
83 changes: 83 additions & 0 deletions 02_activities/assignments/DC_Cohort/Assignment1.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ Steps to complete this part of the assignment:
1. Write a query that returns everything in the customer table.
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.

My code:
Q1
SELECT *
FROM customer;
Q2
SELECT *
FROM customer
ORDER BY customer_last_name, customer_first_name
LIMIT 10;

<div align="center">-</div>

#### WHERE
Expand All @@ -132,18 +142,52 @@ Steps to complete this part of the assignment:
2. one condition using BETWEEN <br>
Limit to 25 rows of output.

Q1
SELECT *
FROM customer_purchases
WHERE product_id=4 OR product_id=9
LIMIT 25;

Q2
SELECT *, quantity *cost_per_quantity AS price
FROM customer_purchases
WHERE product_id BETWEEN 8 AND 10
LIMIT 25;
second option:
SELECT *, quantity *cost_per_quantity AS price
FROM customer_purchases
WHERE product_id <=10 AND product_id>=8
LIMIT 25;

<div align="center">-</div>

#### CASE
1. Products can be sold by the individual unit or by bulk measures like lbs. or oz. Using the product table, write a query that outputs the `product_id` and `product_name` 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.”

2. We want to flag all of the different types of pepper products that are sold at the market. 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.

Q1
SELECT product_id, product_name,
CASE
WHEN product_qty_type='unit' THEN 'unit'
ELSE 'bulk'
END AS product_qty_type_condensed FROM product;

Q2
SELECT product_id, product_name,
CASE WHEN product_qty_type='unit' THEN 'unit' ELSE 'bulk' END AS product_qty_type_condensed,
CASE WHEN product_name LIKE '%pepper%' THEN 1 ELSE 0 END AS pepper_flag
FROM product;

<div align="center">-</div>

#### JOIN
1. Write a query that `INNER JOIN`s the `vendor` table to the `vendor_booth_assignments` table on the `vendor_id` field they both have in common, and sorts the result by `market_date` then `vendor_name`. Limit to 24 rows of output.

SELECT vba.market_date, v.vendor_name FROM vendor_booth_assignments AS vba
INNER JOIN vendor AS v ON vba.vendor_id=v.vendor_id
ORDER BY vba.market_date, v.vendor_name
LIMIT 24;
***

## Section 3:
Expand All @@ -163,6 +207,23 @@ Steps to complete this part of the assignment:

**HINT**: This query requires you to join two tables, use an aggregate function, and use the HAVING keyword.

Q1
SELECT vendor_id, COUNT(booth_number)
FROM vendor_booth_assignments
GROUP BY vendor_id;

Q2
--customer table has list of customer names with customer_id as the identifier; customer purchases table has the list of purchases with customer_id as an identifier as well
--join customer to customer_purchases table on customer_id
--create a new column total_spending which is quantity*cost_per_quantity from customer_purchases
--insert HAVING function to limit to total_spending >2000
--order by customer_last_name, customer_first_name from customer
SELECT c.customer_id, c.customer_last_name, c.customer_first_name, cp.quantity*cp.cost_per_quantity AS total_spending FROM customer as c
INNER JOIN customer_purchases AS cp ON c.customer_id = cp.customer_id
GROUP BY c.customer_id HAVING total_spending >2000
ORDER BY customer_last_name, customer_first_name;


<div align="center">-</div>

#### Temp Table
Expand All @@ -173,6 +234,13 @@ Steps to complete this part of the assignment:
To insert the new row use VALUES, specifying the value you want for each column:
`VALUES(col1,col2,col3,col4,col5)`

-- create a temp new table temp.new_vendor from vendor and add new entry using VALUES
CREATE TEMP 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');
SELECT * FROM temp.new_vendor;


<div align="center">-</div>

#### Date
Expand All @@ -182,11 +250,20 @@ To insert the new row use VALUES, specifying the value you want for each column:

Limit to 25 rows of output.

SELECT customer_id, STRFTIME('%m', market_date) AS purchase_month, STRFTIME('%Y', market_date) AS purchase_year
FROM customer_purchases
LIMIT 25;

2. Using the previous query as a base, determine how much money each customer spent in April 2022. Remember that money spent is `quantity*cost_per_quantity`.

**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!!

SELECT customer_id, STRFTIME('%m', market_date) AS purchase_month, STRFTIME('%Y', market_date) AS purchase_year, quantity*cost_per_quantity AS customer_spending
FROM customer_purchases
WHERE purchase_month='04' AND purchase_year='2022'
GROUP BY customer_id LIMIT 25;

***

## Section 4:
Expand All @@ -210,4 +287,10 @@ Consider, for example, concepts of fariness, inequality, social structures, marg

```
Your thoughts...
Many of the forms I fill out in Canada quietly encode values about who matters and how. Salutation fields, for instance, embed a social hierarchy: being listed as “Dr.” can bring privileges rooted in assumptions about one’s status, education, and credibility. Even the most routine identifiers, full name and date of birth, reflect a particular worldview about what makes a person legible to the state.

My mother’s story made this clear to me. She was born in a small village in Ethiopia in the 1960s, where names and birth dates were not formally recorded at birth. When she enrolled in elementary school in the capital city, she had to supply her own name and estimate her birthday. Hearing this, I realized how deeply these two data points are woven into the social structures of Western countries, and how developing nations have adopted them even where they do not align with traditional customs. Like the Pakistani family database Qadri describes, these systems do not simply record identity; they define it, and people whose lives do not fit the template must reshape themselves to be counted.

These identifiers are also imperfect on their own terms. Someone can use another person’s name, date of birth, and SIN to misrepresent themselves, which raises questions about whether they deserve the weight we give them. I am curious what alternative identification information a national database could use. An evolving biometric database is one intriguing possibility, though it would carry its own embedded values about bodies, consent, and surveillance that would need careful scrutiny.

```
1 change: 0 additions & 1 deletion 02_activities/assignments/DC_Cohort/Assignment2.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ Steps to complete this part of the assignment:
- from [Github](./02_activities/assignments/assignment2.sql)
- or, from your local forked repository
- Complete each question, by writing responses between the QUERY # and END QUERY blocks
- Some questions build on your earlier queries or tables. Keep track of the column names you use so you can reference them correctly in later questions.


### Write SQL
Expand Down
Loading
Loading