純前端實現的闖關式SQL 自學網
By 程序員魚皮,一人全役
在線體驗:http://sqlmother.yupi.icu
視頻演示:https://www.bilibili.com/video/BV1pV4y1i7LW
一個完全免費的闖關式SQL 自學教程網站,結合魚皮自己的SQL 學習實踐經驗,編寫了30 多個關卡,用戶可以在線提交SQL 代碼做題闖關,目標是從0 到1 地帶大家掌握常用的SQL 語法。
此外,網站支持自由選擇關卡、自定義關卡、SQL 在線練習廣場等功能。

首先,SQL 知識極為重要,幾乎是程序員、產品經理、數據分析同學的必備技能。
對於SQL 的學習,比起看教程,更適合通過實戰來入門。網上雖然也有類似的SQL 自學網,但是要么收費、要么不夠體系化。
所以魚皮決定自己動手,搞一個開源的SQL 學習網,一方面希望能夠幫助大家更輕鬆地入門SQL;另一方面,也希望項目代碼也能給大家一些啟發,讓更多同學有機會參與進來成為貢獻者,一起做好一個項目!
1)直接進入主頁,左側是教程和題目區域,請先完整閱讀
2)在右上區域編寫SQL 代碼做題,點擊運行提交結果
3)可以通過右下的題目助手區域幫助自己做題
4)執行結果正確後,可以進入下一關

你也可以自由選擇關卡來挑戰,所有關卡都沒有任何限制,不一定非要按順序做題:

由於項目採用純前端實現,本地啟動項目非常簡單!
在線訪問人數較多,可能會卡頓,所以更推薦大家自己在本地使用~
1)下載本項目代碼
2)進入項目根目錄,執行npm install安裝項目依賴
3)執行npm run dev本地啟動即可

本項目採用純前端實現,不需要任何後端的前置知識~
Q:為什麼採用純前端實現?
A:減少攻擊風險+ 省錢+ 新的學習嘗試
採用模塊化的開發思想,把做題頁面(主頁)拆分為題目瀏覽區、SQL 編碼區、題目結果區,每個區都是一個獨立的Vue 組件文件,實現了邏輯的隔離和組件的複用(比如SQL 編碼區同樣可以復用到SQL 練習廣場頁面)。
然後在IndexPage.vue中就可以引入這些組件,並且傳遞關卡信息、運行結果等數據給組件,組裝成一個完整的頁面。
雖然沒有後端數據庫,但是仍應該把所有關卡的數據統一進行管理,所以定義了levels目錄,統一存放關卡相關數據。
首先將關卡分為了兩類,主線關卡(教程)和自定義關卡(便於擴展),分別在mainLevels.ts和customLevels.ts文件中進行管理。
每個關卡都是一個單獨的目錄,實現了關卡之間的隔離。

由於每個關卡的題目教程文章可能非常長,直接寫在ts 文件中不利於閱讀和管理,所以這裡的策略是把所有文章寫在.md Markdown 文件中,在關卡定義文件index.ts中讀取.md文件。
示例代碼如下,每個關卡的信息獨立定義、相互隔離:
import md from "./README.md?raw" ;
import sql from "./createTable.sql?raw" ;
export default {
key : "level1" ,
title : "基础语法 - 查询 - 全表查询" ,
initSQL : sql ,
content : md ,
defaultSQL : "select * from student" ,
answer : "select * from student" ,
hint : "请仔细查看本关给出的示例" ,
type : "main" ,
} as LevelType ;純前端是怎麼操作數據庫、執行SQL 的呢?有前端經驗的同學會本能地想到webassembly技術。
沒錯,通過webassembly技術,我們可以在瀏覽器中執行JS 之外的語言(比如C++)。但是沒必要自己去實現SQL 執行邏輯了,站在巨人的肩膀上,直接使用開源的sql.js庫,就可以在前端執行自己的SQL 操作了。
核心代碼在src/core/sqlExecutor.ts中,定義了初始化DB 和執行SQL 兩個函數,很簡單:
import initSqlJs , { Database , SqlJsStatic } from "sql.js" ;
/**
* SQL 执行器
*
* @author coder_yupi https://github.com/liyupi
*/
let SQL : SqlJsStatic ;
/**
* 获取初始化 DB
* @param initSql
*/
export const initDB = async ( initSql ?: string ) => {
if ( ! SQL ) {
SQL = await initSqlJs ( {
// Required to load the wasm binary asynchronously
locateFile : ( ) =>
"https://cdn.bootcdn.net/ajax/libs/sql.js/1.7.0/sql-wasm.wasm" ,
} ) ;
}
// Create a database
const db = new SQL . Database ( ) ;
if ( initSql ) {
// Execute a single SQL string that contains multiple statements
db . run ( initSql ) ; // Run the query without returning anything
}
return db ;
} ;
/**
* 执行 SQL
* @param db
* @param sql
*/
export const runSQL = ( db : Database , sql : string ) => {
return db . exec ( sql ) ;
} ;在關卡加載時,會先執行關卡對應的初始化SQL 語句完成建表和導入示例數據,然後用戶就可以編寫SQL 查詢表中的數據了。
和判題相關的代碼全部集中定義在src/core/result.ts文件中,包括定義了幾種執行狀態,以及判斷結果是否正確的函數。
如何判斷用戶的SQL 語句是否正確呢?
不是直接去對比用戶的輸入語句和我們預設的答案是否一致(那樣太死板了),而是依次執行以下3 個操作:
這裡作者用了個trick 方式來對比數據,直接把兩份結果集轉為JSON 格式,對比JSON 字符串是否一致即可,而不是多重for 循環。
歡迎各路好漢參與貢獻,利人利己~
目前有幾種推薦的貢獻方式:
在貢獻關卡前,請確保你已經理解了本項目加載關卡的方式。
為保證教程的連貫性,更推薦貢獻自定义关卡而不是主線關卡,更容易被合併。
貢獻自定義關卡的步驟:
1)複製src/levels/custom/自定义关卡模板,將目錄名改為自己的關卡中文名
2)修改模板中的createTable.sql建表語句,導入默認數據
3)修改模板中的index.ts文件,設置關卡的中英文名、默認SQL、答案SQL、提示等
4)修改模板中的README.md文件,更改標題和題目內容,需要給出表結構信息、並且盡量把題目表達清楚(比如必須按照某個順序輸出)
5)在customLevels.ts文件中引入自定義的關卡。
注意,本項目僅支持SQLite 語法(基本上是通用的SQL)!不要使用太花里胡哨的函數!

比如修復關卡的錯誤、優化關卡的文案使其更易於理解或增加更多乾貨、調整關卡的難度等。
本項目僅為魚皮一人開發,時間和精力有限,很多地方沒有做到完善,歡迎大家給項目進行擴展,打造屬於自己的SQL 之子、SQL 之孫、SQL 之曾孫系列產品。 。 。
一些可能的擴展思路:
感謝閱讀,也歡迎加入作者的編程學習圈,學習更多原創項目~