diff --git a/exercises/concept/intro-select/create_fixture.sql b/exercises/concept/intro-select/create_fixture.sql new file mode 100644 index 00000000..37d4b63f --- /dev/null +++ b/exercises/concept/intro-select/create_fixture.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS weather_readings; +CREATE TABLE weather_readings ( + date TEXT NOT NULL, + location TEXT NOT NULL, + temperature REAL NOT NULL, + humidity INTEGER NOT NULL +); + +.mode csv +.import ./data.csv weather_readings diff --git a/exercises/concept/intro-select/create_test_table.sql b/exercises/concept/intro-select/create_test_table.sql new file mode 100644 index 00000000..7f4bd05f --- /dev/null +++ b/exercises/concept/intro-select/create_test_table.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS tests; +CREATE TABLE IF NOT EXISTS tests ( + description TEXT NOT NULL, + -- The following section is needed by the online test-runner + status TEXT DEFAULT 'fail', + message TEXT, + output TEXT, + test_code TEXT, + task_id INTEGER DEFAULT NULL, + -- Here are columns for the actual tests + expected TEXT NOT NULL +); + +INSERT INTO tests (description, expected) +VALUES + ('ALL records => SELECT * FROM weather_readings', '[{"date":"2025-10-22","location":"Portland","temperature":53.1,"humidity":72},{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-22","location":"Boise","temperature":60.4,"humidity":55},{"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68},{"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58}]'), + ('Just location and temperature columns => SELECT location, temperature FROM weather_readings', '[{"location":"Portland","temperature":53.1},{"location":"Seattle","temperature":56.2},{"location":"Boise","temperature":60.4},{"location":"Portland","temperature":54.6},{"location":"Seattle","temperature":57.8},{"location":"Boise","temperature":62.0}]'), + ('Without "FROM" => SELECT ''Hello, world.''', '[{"''Hello, world.''":"Hello, world."}]'), + ('All records from Seatle location => SELECT * FROM weather_readings WHERE location = ''Seattle''', '[{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68}]'), + ('All records where humity in range => SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70', '[{"date":"2025-10-22","location":"Seattle","temperature":56.2,"humidity":66},{"date":"2025-10-23","location":"Portland","temperature":54.6,"humidity":70},{"date":"2025-10-23","location":"Seattle","temperature":57.8,"humidity":68}]'), + ('Just location column => SELECT location FROM weather_readings', '[{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"},{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"}]'), + ('Only unique locations => SELECT DISTINCT location FROM weather_readings', '[{"location":"Portland"},{"location":"Seattle"},{"location":"Boise"}]'); +; diff --git a/exercises/concept/intro-select/data.csv b/exercises/concept/intro-select/data.csv new file mode 100644 index 00000000..808ccddf --- /dev/null +++ b/exercises/concept/intro-select/data.csv @@ -0,0 +1,6 @@ +"2025-10-22","Portland",53.1,72 +"2025-10-22","Seattle",56.2,66 +"2025-10-22","Boise",60.4,55 +"2025-10-23","Portland",54.6,70 +"2025-10-23","Seattle",57.8,68 +"2025-10-23","Boise",62.0,58 diff --git a/exercises/concept/intro-select/intro-select.sql b/exercises/concept/intro-select/intro-select.sql new file mode 100644 index 00000000..265301d8 --- /dev/null +++ b/exercises/concept/intro-select/intro-select.sql @@ -0,0 +1,14 @@ +SELECT * FROM weather_readings; + +SELECT location, temperature FROM weather_readings; + +-- This one will fail on purpose +SELECT 'Hello, world.' AS say_hi; + +SELECT * FROM weather_readings WHERE location = 'Seattle'; + +SELECT * FROM weather_readings WHERE humidity BETWEEN 60 AND 70; + +SELECT location FROM weather_readings; + +SELECT DISTINCT location FROM weather_readings; diff --git a/exercises/concept/intro-select/intro-select_test.sql b/exercises/concept/intro-select/intro-select_test.sql new file mode 100644 index 00000000..6010d111 --- /dev/null +++ b/exercises/concept/intro-select/intro-select_test.sql @@ -0,0 +1,85 @@ +-- Create database: +.read ./create_fixture.sql +-- Read user student solution and save any output as markdown in user_output.md: +.mode markdown +.output user_output.md +.echo on +.read ./intro-select.sql +.echo off +.output +.shell sed -i 1d user_output.md + +-- Re-run stub file to collect the results as json arrays +.mode json +.output outputs.txt +.read ./intro-select.sql +.output +-- Creating the results table from the outputs.txt file +DROP TABLE IF EXISTS outputs; +CREATE TEMPORARY TABLE outputs (line TEXT NOT NULL); +.mode tabs +.import ./outputs.txt outputs +DROP TABLE IF EXISTS results; +CREATE TABLE results (result TEXT NOT NULL); +WITH inputs (input) AS ( + SELECT JSON(PRINTF('[%s]', GROUP_CONCAT(RTRIM(TRIM(line), ','), ','))) + FROM outputs +) +INSERT INTO results (result) +SELECT j.value + FROM inputs, JSON_EACH(input) j +; + +-- Create a clean testing environment: +.read ./create_test_table.sql +-- Comparison of user input and the tests updates the status for each test: +UPDATE tests + SET status = 'pass' + FROM (SELECT result FROM results) AS actual + WHERE NOT EXISTS ( + SELECT key, value, type, path + FROM JSON_TREE(result) + WHERE type NOT IN ('array', 'object') + EXCEPT + SELECT key, value, type, path + FROM JSON_TREE(expected) + WHERE type NOT IN ('array', 'object') + ) + AND NOT EXISTS ( + SELECT key, value, type, path + FROM JSON_TREE(expected) + WHERE type NOT IN ('array', 'object') + EXCEPT + SELECT key, value, type, path + FROM JSON_TREE(result) + WHERE type NOT IN ('array', 'object') + ) +; + +-- Update message for failed tests to give helpful information: +UPDATE tests + -- SET message = 'Result for "' || tests.description || '"' || ' is <' || COALESCE(actual.result, 'NULL') || '> but should be <' || tests.expected || '>' + SET message = 'Result for <"' || tests.description || '">' || ' NOT FOUND' -- need improvements +WHERE tests.status = 'fail'; + +-- Save results to ./output.json (needed by the online test-runner) +.mode json +.once './output.json' +SELECT + description, + status, + message, + output, + test_code, + task_id +FROM + tests; + +-- Display test results in readable form for the student: +.mode table +SELECT + description, + status, + message +FROM + tests; diff --git a/exercises/concept/intro-select/outputs.txt b/exercises/concept/intro-select/outputs.txt new file mode 100644 index 00000000..9e837a4a --- /dev/null +++ b/exercises/concept/intro-select/outputs.txt @@ -0,0 +1,27 @@ +[{"date":"2025-10-22","location":"Portland","temperature":53.10000000000000142,"humidity":72}, +{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, +{"date":"2025-10-22","location":"Boise","temperature":60.39999999999999858,"humidity":55}, +{"date":"2025-10-23","location":"Portland","temperature":54.60000000000000142,"humidity":70}, +{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}, +{"date":"2025-10-23","location":"Boise","temperature":62.0,"humidity":58}] +[{"location":"Portland","temperature":53.10000000000000142}, +{"location":"Seattle","temperature":56.20000000000000284}, +{"location":"Boise","temperature":60.39999999999999858}, +{"location":"Portland","temperature":54.60000000000000142}, +{"location":"Seattle","temperature":57.79999999999999716}, +{"location":"Boise","temperature":62.0}] +[{"say_hi":"Hello, world."}] +[{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, +{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}] +[{"date":"2025-10-22","location":"Seattle","temperature":56.20000000000000284,"humidity":66}, +{"date":"2025-10-23","location":"Portland","temperature":54.60000000000000142,"humidity":70}, +{"date":"2025-10-23","location":"Seattle","temperature":57.79999999999999716,"humidity":68}] +[{"location":"Portland"}, +{"location":"Seattle"}, +{"location":"Boise"}, +{"location":"Portland"}, +{"location":"Seattle"}, +{"location":"Boise"}] +[{"location":"Portland"}, +{"location":"Seattle"}, +{"location":"Boise"}]