{"id":13484578,"url":"https://github.com/liyupi/sql-mother","last_synced_at":"2025-05-14T20:06:07.714Z","repository":{"id":185822363,"uuid":"674095331","full_name":"liyupi/sql-mother","owner":"liyupi","description":"程序员鱼皮原创项目，免费的闯关式 SQL 自学教程网站，从 0 到 1 带大家掌握常用 SQL 语法、快速学习 SQL 和数据库，纯前端实现，简单易学~","archived":false,"fork":false,"pushed_at":"2024-06-27T03:18:24.000Z","size":2091,"stargazers_count":3506,"open_issues_count":33,"forks_count":374,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-03T09:06:48.320Z","etag":null,"topics":["css","database","frontend","html","javascript","mysql","sql","typescript","vue3","web"],"latest_commit_sha":null,"homepage":"http://sqlmother.yupi.icu","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liyupi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-03T06:11:57.000Z","updated_at":"2025-04-03T08:04:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"91b7b1c0-a79d-4c70-ab60-6f6c89852d82","html_url":"https://github.com/liyupi/sql-mother","commit_stats":null,"previous_names":["liyupi/sql-mother"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liyupi%2Fsql-mother","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liyupi%2Fsql-mother/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liyupi%2Fsql-mother/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liyupi%2Fsql-mother/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liyupi","download_url":"https://codeload.github.com/liyupi/sql-mother/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248351535,"owners_count":21089299,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","database","frontend","html","javascript","mysql","sql","typescript","vue3","web"],"created_at":"2024-07-31T17:01:26.446Z","updated_at":"2025-04-11T06:18:38.252Z","avatar_url":"https://github.com/liyupi.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# SQL 之母 - 闯关式 SQL 自学网\n\n\u003e 纯前端实现的闯关式 SQL 自学网\n\u003e\n\u003e By [程序员鱼皮](https://docs.qq.com/doc/DUFFRVWladXVjeUxW) ，一人全役\n\n\n\n在线体验：http://sqlmother.yupi.icu\n\n视频演示：https://www.bilibili.com/video/BV1pV4y1i7LW\n\n\n\n## 项目介绍\n\n一个完全免费的闯关式 SQL 自学教程网站，结合鱼皮自己的 SQL 学习实践经验，编写了 30 多个关卡，用户可以在线提交 SQL 代码做题闯关，目标是从 0 到 1 地带大家掌握常用的 SQL 语法。\n\n此外，网站支持自由选择关卡、自定义关卡、SQL 在线练习广场等功能。\n\n![](./doc/index.png)\n\n\n\n### 为什么做这样一个网站？\n\n首先，SQL 知识极为重要，几乎是程序员、产品经理、数据分析同学的必备技能。\n\n对于 SQL 的学习，比起看教程，更适合通过实战来入门。网上虽然也有类似的 SQL 自学网，但是要么收费、要么不够体系化。\n\n所以鱼皮决定自己动手，搞一个开源的 SQL 学习网，一方面希望能够帮助大家更轻松地入门 SQL；另一方面，也希望项目代码也能给大家一些启发，让更多同学有机会参与进来成为贡献者，一起做好一个项目！\n\n\n\n## 20 秒学会使用\n\n1）直接进入主页，左侧是教程和题目区域，请先完整阅读\n\n2）在右上区域编写 SQL 代码做题，点击运行提交结果\n\n3）可以通过右下的题目助手区域帮助自己做题\n\n4）执行结果正确后，可以进入下一关\n\n![SQL 之母使用教程](./doc/tutorial.png)\n\n你也可以自由选择关卡来挑战，所有关卡都没有任何限制，不一定非要按顺序做题：\n\n![选择关卡](./doc/levels.png)\n\n\n\n\n## 1 分钟本地启动\n\n由于项目采用纯前端实现，本地启动项目非常简单！\n\n\u003e 在线访问人数较多，可能会卡顿，所以更推荐大家自己在本地使用~\n\n1）下载本项目代码\n\n2）进入项目根目录，执行 `npm install` 安装项目依赖\n\n3）执行 `npm run dev` 本地启动即可\n\n\n\n## 功能和特性\n\n- 展示教程题目文档（Markdown 格式）\n- 在线做题\n  - 比对结果\n  - 题目助手\n    - 展示执行结果\n    - 查看提示\n    - 查看建表语句\n    - 查看答案\n- 关卡设置\n  - 自由选择关卡\n  - 主线关卡 - 支持上一关 / 下一关\n  - 自定义关卡\n- SQL 广场（自由输入 SQL）\n\n\n\n![SQL 广场](./doc/sql-playground.png)\n\n\n\n## 技术选型\n\n本项目采用纯前端实现，不需要任何后端的前置知识~\n\n\u003e Q：为什么采用纯前端实现？\n\u003e\n\u003e A：减少攻击风险 + 省钱 + 新的学习尝试\n\n\n\n- 主框架：Vue 3\n- 组件库：ant-design-vue\n- Markdown 展示组件：bytemd + github-markdown-css 主题\n- 代码编辑器：monaco-editor\n- SQL 执行：sql.js\n- SQL 代码格式化：sql-formatter\n- 全局状态管理：pinia + pinia-plugin-persistedstate\n- 前端工程化：typescript + eslint + prettier\n- 工具库：lodash\n\n\n\n## 核心设计\n\n### 1、界面模块化\n\n采用模块化的开发思想，把做题页面（主页）拆分为题目浏览区、SQL 编码区、题目结果区，每个区都是一个独立的 Vue 组件文件，实现了逻辑的隔离和组件的复用（比如 SQL 编码区同样可以复用到 SQL 练习广场页面）。\n\n- 题目浏览区（QuestionBoard）：展示题目 Markdown 文档\n- SQL 编码区（SqlEditor）：封装了代码编辑器、运行 / 格式化 / 重置按钮\n- 题目结果区（SqlResult）：封装了题目执行结果的展示\n\n然后在 `IndexPage.vue` 中就可以引入这些组件，并且传递关卡信息、运行结果等数据给组件，组装成一个完整的页面。\n\n\n\n### 2、关卡设计\n\n虽然没有后端数据库，但是仍应该把所有关卡的数据统一进行管理，所以定义了 `levels` 目录，统一存放关卡相关数据。\n\n首先将关卡分为了两类，主线关卡（教程）和自定义关卡（便于扩展），分别在 `mainLevels.ts` 和 `customLevels.ts` 文件中进行管理。\n\n每个关卡都是一个单独的目录，实现了关卡之间的隔离。\n\n![每个关卡独立目录](./doc/customLevel.png)\n\n由于每个关卡的题目教程文章可能非常长，直接写在 ts 文件中不利于阅读和管理，所以这里的策略是把所有文章写在 `.md` Markdown 文件中，在关卡定义文件 `index.ts` 中读取 `.md` 文件。\n\n示例代码如下，每个关卡的信息独立定义、相互隔离：\n\n```ts\nimport md from \"./README.md?raw\";\nimport sql from \"./createTable.sql?raw\";\n\nexport default {\n  key: \"level1\",\n  title: \"基础语法 - 查询 - 全表查询\",\n  initSQL: sql,\n  content: md,\n  defaultSQL: \"select * from student\",\n  answer: \"select * from student\",\n  hint: \"请仔细查看本关给出的示例\",\n  type: \"main\",\n} as LevelType;\n```\n\n\n\n### 3、纯前端 SQL 执行\n\n纯前端是怎么操作数据库、执行 SQL 的呢？有前端经验的同学会本能地想到 `webassembly` 技术。\n\n没错，通过 `webassembly` 技术，我们可以在浏览器中执行 JS 之外的语言（比如 C++）。但是没必要自己去实现 SQL 执行逻辑了，站在巨人的肩膀上，直接使用开源的 `sql.js` 库，就可以在前端执行自己的 SQL 操作了。\n\n核心代码在 `src/core/sqlExecutor.ts` 中，定义了初始化 DB 和执行 SQL 两个函数，很简单：\n\n```ts\nimport initSqlJs, { Database, SqlJsStatic } from \"sql.js\";\n\n/**\n * SQL 执行器\n *\n * @author coder_yupi https://github.com/liyupi\n */\nlet SQL: SqlJsStatic;\n\n/**\n * 获取初始化 DB\n * @param initSql\n */\nexport const initDB = async (initSql?: string) =\u003e {\n  if (!SQL) {\n    SQL = await initSqlJs({\n      // Required to load the wasm binary asynchronously\n      locateFile: () =\u003e\n        \"https://cdn.bootcdn.net/ajax/libs/sql.js/1.7.0/sql-wasm.wasm\",\n    });\n  }\n  // Create a database\n  const db = new SQL.Database();\n  if (initSql) {\n    // Execute a single SQL string that contains multiple statements\n    db.run(initSql); // Run the query without returning anything\n  }\n  return db;\n};\n\n/**\n * 执行 SQL\n * @param db\n * @param sql\n */\nexport const runSQL = (db: Database, sql: string) =\u003e {\n  return db.exec(sql);\n};\n\n```\n\n在关卡加载时，会先执行关卡对应的初始化 SQL 语句完成建表和导入示例数据，然后用户就可以编写 SQL 查询表中的数据了。\n\n\n\n### 4、判题机制\n\n和判题相关的代码全部集中定义在 `src/core/result.ts` 文件中，包括定义了几种执行状态，以及判断结果是否正确的函数。\n\n如何判断用户的 SQL 语句是否正确呢？\n\n不是直接去对比用户的输入语句和我们预设的答案是否一致（那样太死板了），而是依次执行以下 3 个操作：\n\n1. 分别提交用户的输入语句和答案语句，得到两份结果表\n2. 判断两个结果表输出的列名是否一致（名称和顺序都要一致）\n3. 判断两个结果表输出的数据是否一致\n\n这里作者用了个 trick 方式来对比数据，直接把两份结果集转为 JSON 格式，对比 JSON 字符串是否一致即可，而不是多重 for 循环。\n\n\n\n## 目录结构\n\n- public：公共静态资源\n- doc：文档相关资源\n- src\n  - assets：静态资源\n  - components：组件\n    - CodeEditor.vue：代码编辑器\n    - MdViewer.vue：Markdown 浏览\n    - QuestionBoard.vue：题目面板（教程区）\n    - SqlEditor.vue：SQL 编辑器（练习区）\n    - SqlResult.vue：SQL 执行结果（结果区）\n    - SqlResultTable.vue：SQL 结果表格\n  - configs：配置\n    - routes：路由\n  - core：核心\n    - sqlExecutor.ts：SQL 执行引擎\n    - result.ts：执行结果相关变量和函数\n    - globalStore.ts：全局状态管理\n  - levels：关卡\n    - custom：自定义关卡\n    - main：主线关卡\n      - level1：每个关卡都是一个单独的目录\n        - createTable.sql：关卡依赖的建表语句\n        - index.ts：关卡的定义\n        - README.md：关卡教程\n    - index.ts：定义了关卡相关变量和函数\n    - level.d.ts：关卡类型定义\n    - mainLevels：主线关卡列表\n    - customLevels：自定义关卡列表\n  - pages：页面\n    - IndexPage.vue：主页\n    - LevelsPage.vue：关卡页面\n    - PlaygroundPage.vue：广场页面\n  - App.vue：主页\n  - main.ts：Vue 主文件\n  - style.css：全局样式文件\n  - vite-env.d.ts：环境定义\n- .eslintrc.js：代码规范\n- .gitignore：提交忽略文件\n- index.html：静态主页\n- package.json：项目管理\n- tsconfig.json：TS 配置\n- vite.config.ts：打包工具配置\n\n\n\n## 贡献指南\n\n欢迎各路好汉参与贡献，利人利己~\n\n目前有几种推荐的贡献方式：\n\n\n\n### 1、贡献关卡\n\n在贡献关卡前，请确保你已经理解了本项目加载关卡的方式。\n\n为保证教程的连贯性，更推荐贡献 `自定义关卡` 而不是主线关卡，更容易被合并。\n\n贡献自定义关卡的步骤：\n\n1）复制 `src/levels/custom/自定义关卡模板` ，将目录名改为自己的关卡中文名\n\n2）修改模板中的 `createTable.sql` 建表语句，导入默认数据\n\n3）修改模板中的 `index.ts` 文件，设置关卡的中英文名、默认 SQL、答案 SQL、提示等\n\n4）修改模板中的 `README.md` 文件，更改标题和题目内容，需要给出表结构信息、并且尽量把题目表达清楚（比如必须按照某个顺序输出）\n\n5）在 `customLevels.ts` 文件中引入自定义的关卡。\n\n\u003e 注意，本项目仅支持 SQLite 语法（基本上是通用的 SQL）！不要使用太花里胡哨的函数！\n\n\n\n![自定义关卡](./doc/customLevel.png)\n\n\n\n### 2、完善关卡\n\n比如修复关卡的错误、优化关卡的文案使其更易于理解或增加更多干货、调整关卡的难度等。\n\n\n\n### 3、项目扩展\n\n本项目仅为鱼皮一人开发，时间和精力有限，很多地方没有做到完善，欢迎大家给项目进行扩展，打造属于自己的 SQL 之子、SQL 之孙、SQL 之曾孙系列产品。。。\n\n一些可能的扩展思路：\n\n1. 点击 “提交” 题目后，自动展开执行结果区域\n2. 过关后，给出更友好的过关提示，可以更方便地到达下一关\n3. 支持 SQL 一键格式化\n4. 优化关卡加载机制，按需加载\n5. 给项目增加一个后端，用数据库来存放关卡数据，并且支持在线提交 / 审核关卡\n6. 增加过关排行榜\n\n\n\n---\n\n\n\n感谢阅读，也欢迎加入 [作者的编程学习圈](https://yupi.icu)，学习更多原创项目~\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliyupi%2Fsql-mother","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliyupi%2Fsql-mother","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliyupi%2Fsql-mother/lists"}