SQL
快来分享你的内容吧~
- 03-16 14:40·Java后端
- 03-14 13:35·Java后端
- 2025-10-02·大数据开发查看全文国庆也不能松懈,来道SQL题😀 表: Calls +--------------+----------+ | Column Name | Type | +--------------+----------+...编程导航_小y:优秀 ~110分享
- 2025-01-08查所有但只去除一个字段,除了罗列还有什么办法吗对方正在偷偷写代码:正常来说,只能罗列了,就算有,select * 再去掉不要的字段列,比查罗列的列的效率会慢,你可以select * 之后把数据的表头复制一下,再删掉不用的字段再查一遍就好了吧320分享
- 求SQL Server文档类学习资料学校开设了SQL Server数据库概论课程,请问有学习SQL Server的文档类的学习资源推荐吗?以及学习数据库设计的视频或文档推荐?...查看全文编程导航小智:对于学习SQL Server的文档类学习资源,我推荐以下几个官方文档和站点:1. SQL Server 官方文档:https://docs.microsoft.com/zh-cn/sql/sql-server这个官方文档是最全面的学习SQL Server的参考资源,包括了SQL Server的各个版本的文档,涵盖了数据库管理、查询、存储过程、触发器等各个方面的内容。2. SQL Server 技术
- 2024-08-14·后端
- 2024-07-25·后端
- 2024-07-19·后端
【SQL 极简速通 2/7】拒绝原始原石,给你的数据来一场“整容手术”
### 前言 大家好(๑╹◡╹)ノ”,我是萍雨 欢迎回来。在第一期里,我们已经学会了如何用 `FROM`、`WHERE` 和 `SELECT` 这三把兵器,从千万级的数据废料中精准捞出想要的内容。 **但正如我们上期结尾留下的悬念:捞出来的往往是极其野蛮、未经雕琢的“底层原石”。** 如果直接把这些原始数据甩给前端或产品经理,他们可能会立刻原地爆炸: - “别给我看字段名 `u_sc_01`,我要看中文‘数学成绩’!” - “别把所有成绩乱七八糟地堆在一起,按分数从高到低排好!” - “数据量太大撑爆页面了,我只要这一页的 10 条数据!” **与其在 Java 代码里写一堆累赘的格式转换,不如在 SQL 这一关就完成“数据整容”。** 今天,我们就来拆解如何通过 **别名处理、多级排序、分页截断**,让数据乖乖听话。 ### 一、 AS 别名 ##### 1. 引出 想象你费了九牛二虎之力,从公司的财务库里捞出了一份报表,骄傲地发给财务主管。 但他看到的表头是这样的:`f_income_03_fixed`。 搞不好,你明天就会因为“沟通成本过高”被原地优化。 机器只认冰冷的英文字段,但人类需要看懂“人话”。难道我们要把数据导进 Excel 里,手动一列列改表头吗? 解决这个问题的神级语法,叫做:**`AS`(别名)** ##### 2. 通俗的解释 用 SQL 起别名,就像给网红起花名。 比如王者荣耀一哥,身份证上的本名叫“张宏发”,但大众熟知的是他的花名“张大仙”。 “张大仙”就是“张宏发”的别名。两人指的是同一个物理实体,只是对外展示的招牌变了。 在 SQL 里,语法是这样的: ```SQL SELECT f_income_03_fixed AS 第三季度收入 FROM baobiao; ``` - `f_income_03_fixed`:身份证上的本名(原始列名)。 - `AS`:起花名的动作。 - `第三季度收入`:对外展示的招牌(别名)。 ##### 3. 我会把公司的数据库搞坏吗? 这时可能有小伙伴有疑问🤔了: “我在这里用了 `AS` 改名字,数据库里的原始字段会不会被我覆盖掉?” **绝对不会。** `AS` 只是给捞出来的数据临时戴上了一层面具。它是一个纯粹的 “读取时(Read-time)”化妆操作,无论你把别名起得多花哨,硬盘里存的那个 `f_income_03_fixed` 都毫发无损。大胆用,搞不坏~ ##### 4. “双引号”护体? 绝大多数时候,像上面那样 `AS 第三季度收入` 直接写就能跑通 。 但是,如果你是个起名狂魔,非要踩数据库的底线,SQL 解析器就会当场崩溃报错。 **以下三种情况,你必须给别名穿上双引号 `""` 这件防弹衣:** 1. **别名里带空格:** `AS "User Name"` - 不加引号的下场:数据库以为 `User` 是别名,后面的 `Name` 是一堆无法理解的乱码语法。 2. **撞了 SQL 的枪口(关键字):** `AS "SELECT"` - 不加引号的下场:数据库以为你要在这里嵌套一个新查询,直接逻辑死锁。 3. **纯数字开头:** `AS "1st_place"` - 不加引号的下场:变量命名的大忌,解析器无法分辨这是数字常量还是列名。 记住:**双引号的作用,就是堵住 SQL 解析器的嘴**——“闭嘴,别管里面的语法,把它当成一串纯文本展示出来就行!” 这里可能又有小伙伴有疑问了: **“既然 `""` 这么好,那我以后不管三七二十一,给所有别名都套上 `""` 不就万无一失了?而且不用动脑子,无脑加,多爽啊?”** 如果这样想的话,会面对如下的问题: **可读性下降** 想象一下,如果你写了 20 个字段,每个都长这样: `SELECT name AS "姓名", age AS "年龄", score AS "分数" ...` 满屏幕都是引号。这就像你在家写日记,每句话都非要加个书名号一样别扭。**在开发圈,能简洁就不冗余是一种默认的美德。** 过多的引号会严重干扰你查错的速度。 **大小写陷阱** 这是最坑的一点。在很多数据库(比如 PostgreSQL)里: - 如果你不加双引号:`AS user_name`,数据库会默认把它处理成小写。你后续引用它时,写 `user_name` 还是 `USER_NAME` 都能识别。 - **如果你加了双引号**:`AS "UserName"`,数据库会变得**极其死板**。它会严格锁定大小写。后续你查询的时候,如果少写一个大写字母,数据库就会冷冷地告诉你:`Column "username" does not exist`。 所以对于不必要的字段就不要添加了,对于上面的三种情况再添加 "" ##### 5. 补充:消失的 `AS` 与表别名 在很多老司机的代码里,你可能根本看不见 `AS` 这个词。 ```SQL SELECT name 姓名, age 年龄 FROM student; -- 效果等同于使用 AS ``` **为什么能省?** 因为 SQL 解析器很聪明,当它看到两个名词挨在一起,中间只有一个空格时,它会自动脑补出一个 `AS`。 “既然能省,为什么还要教 `AS`?” 因为省略 `AS` 是典型的“阅读陷阱”。如果你不小心漏掉了一个逗号,比如 `SELECT name age FROM student`,数据库会以为你想把 `name` 改名叫 `age`。对于新手,我建议你把 `AS` 写出来,这是对代码可读性最起码的尊重。 如果你觉得字段名太长很烦,那当你遇到 `long_long_long_company_financial_table` 这种表名时,你会绝望的。 这时候,我们可以给整张表起一个“代号”: ```SQL SELECT s.name, s.age FROM student AS s; ``` 这就像在写文章时,第一次提到“世界卫生组织”,后面为了省事直接用“WHO”代替。 - 在这里,`student` 是本名。 - `s` 就是它的临时代号。 - 一旦你给表起了别名,你在 `SELECT` 后面找字段时,就可以直接用 `s.name` 这种“代号.字段名”的方式,干净利落。  ### 二、 ORDER BY 排序 ##### 1. 引出 捞出数据只是解决了“有没有”的问题,但如果没有排序,数据库吐给你的就是一袋散落的零件。想象一下,全校 3000 人的成绩单如果不按分数排,你要找自己的排名得翻到什么时候? 让数据听话排队的语法,就是:**`ORDER BY`** 。 ##### 2. 升降序 在 SQL 里,排序有两个方向: - **`DESC` (降序)**:从大到小。就像成绩单,高分在顶端,低分在末尾 。 - **`ASC` (升序)**:从小到大。就像排队,个子矮的(数值小)站在前面 。 ```SQL -- 按照成绩从高到低(降序)排列 SELECT * FROM student ORDER BY score DESC; ``` #### 3. 备选规则 你考了全校最高分,但很不巧,隔壁班也有个大神跟你考了一模一样的分数。在数据库的结果集里,你们两个不能重叠在同一行,必须分个先后。 **这时候,“备选规则”就上线了。** 当第一个排序条件(总分)分不出胜负时,数据库会看向第二个条件。 ```SQL -- 总分相同?那就比比谁的语文(Chinese)更高! SELECT * FROM student ORDER BY score DESC, Chinese DESC; ``` ##### 4. 看似冲突的“既要又要” 小白可能会问:我想让成绩从高到低(降序),又想让同分的学生里年纪小的(升序)排在前面,这不冲突吗? **完全不冲突,因为 SQL 讲究的是“先来后到”。** 写在 `ORDER BY` 紧后面的字段优先级最高,它是“一票否决权”;写在逗号后面的,只有在前面的数据“打平手”时,才会被唤醒执行 。 ```SQL -- 成绩降序是主逻辑,年龄升序是平分时的“加赛规则” SELECT * FROM student ORDER BY score DESC, age ASC; ``` ### 三、 LIMIT 截断与偏移 ##### 1. 引出 想象你手里拿着那张全校 3000 人的成绩单,但你此时只关心“谁是前 10 名”。你不需要看剩下的 2990 人,于是你用手指挡住了第 11 名及以后的所有内容 。 在 SQL 里,这根手指就是 **`LIMIT`**: ```SQL -- 只要前 10 名,多一个都不要 SELECT * FROM student ORDER BY score DESC LIMIT 10; ``` **但是,如果你不仅想看前 10 名,还想看“第 11 名到第 20 名”呢?** 这时候,你不仅需要用手指挡住后面的人,还需要把你的目光**向下挪动**,跳过前面的尖子生。这就是 `LIMIT` 的进阶玩法:**偏移(Offset)**。 #### 2. 偏移量 这是新手最容易翻车的地方:当你想要“跳过”某些数据时,你会发现 SQL 的编号是从 **0** 开始的 。 为了不记混,请你换一种思考方式:**偏移量不代表“第几个”,而代表“跳过几个”。** - **`LIMIT 0, 10`**: - **0** 的意思是:**跳过 0 个人**。 - 既然一个都没跳过,自然是从第 1 个人开始拿 。 - **`LIMIT 1, 3`**: - **1** 的意思是:**跳过 1 个人**(也就是把那个并列第一的大神踢掉) 。 - 既然跳过了第 1 个,那自然是从第 2 个开始抓取 。 **语法公式重构:** > `LIMIT (你要跳过的个数), (你要抓取的条数)` **为什么非要这么设计?** 因为在底层计算机逻辑(比如数组)中,第一个位置距离开头的“距离”是 0。**“偏移量”本质上是在量距离,而不是在数个数。** ##### 3. 消失的一百万条数据 当你写下 `LIMIT 1000000, 10` 时,你以为数据库会像瞬间移动一样直接闪现到第一百万条吗?  **不,它是个死脑筋的“数数工人”。** 它必须老老实实地从硬盘里翻出前 1,000,000 条数据,数够了数,然后无情地把它们全部**扔掉**,最后才把你要的那 10 条递给你。 为了这 10 条数据,数据库搬运了一百万次垃圾。这就是面试当中常考的“深分页性能坑”。虽然我们可以通过**子查询**或**游标分页**来解决,但现在的你只需要记住一件事:**翻页越深,数据库越累。**  ### 最后 至此,我们的第二篇《数据整形篇》正式合龙。 回头看看,你现在不仅能精准地“捞”数据,还能霸道地“控”数据了: 1. **`AS` 别名**:给机器乱码穿上人类的“西装”,让表头说人话。 2. **`ORDER BY` 排序**:制定第一规则与备选规则,给杂乱无章的数据来一场严酷的“阅兵式”。 3. **`LIMIT` 截断**:掌握电梯法则与偏移量,掐断深分页的性能隐患。 你已经完全掌控了结果集的展示秩序。 **但是,如果你记性足够好,你会发现我在这期“骗”了你。** 上一期结尾我明明承诺过,要教你把数据库里的 `2026-03-14 12:39:00` 变成 `2026-03-14`,把状态 `1` 变成“正常”。但你仔细回想一下,这期我们学的东西,其实只改了“表头”和“顺序”,**压根没动每一行数据里面的真实值。** 如果这时候产品经理又加码了: “我不仅要把状态 `1` 变‘正常’,我还要你根据年龄把用户自动打上标签,60岁以上的标‘老同学’,20岁以下的标‘小同学’ 。另外,顺便帮我把全校的总分、平均分、最高分全算出来 。” 面对这种复杂的**业务分类**和**数学计算**,很多新手的防线彻底崩溃了,乖乖滚回 Java 里去写又长又臭的 `if-else` 和 `for` 循环。 在 SQL 里,难道就没有像编程语言一样的 `if-else` 分支吗?难道就没有现成的“手术刀”和“计算器”吗? 下期《逻辑增强篇》,带你见识 SQL 里最具“编程感”的大杀器。 我是萍雨,我们下期再见ヾ( ̄▽ ̄)ByeBye~
【SQL 极简速通 1/7】从手动搬砖到声明式“点菜”
### **前言** 大家好,我是萍雨。 在开始聊技术之前,我想先分享一个我刚学编程时踩过的坑。那时候我习惯了用 Java 的思维去处理一切,遇到需要从几万条数据里找几个特定用户,我的第一反应是:写个 `for` 循环,加几层 `if-else`。 结果显而易见,电脑直接卡死。那时候我才意识到,**有些脏活累活,必须要交给专业的工具去干。** 这个工具就是 SQL。 很多人觉得数据库高深莫测,那是被那些厚如砖头的教科书吓到了。其实,只要你用对了方法,SQL 比 Excel 还要直观。这篇教程就是为了**完全零基础**的小白准备的,我会推翻那些枯燥的定义,用大白话带你重装大脑。 **看完这一篇,你将掌握:** - 数据库和 Excel 的本质区别。 - 像剪刀一样裁剪数据的 `SELECT`。 - 像漏斗一样筛选数据的 `WHERE`。 - 以及那个连老手都会翻车的 `NULL` 隐形陷阱。 如果你已经准备好把那些低效的循环逻辑丢进垃圾桶,那我们就开始吧。 --- ### **1. 数据库到底是个啥?** 很多零基础的朋友听到 “数据库” 这三个字就觉得高深莫测。其实,把它拉下神坛,它就是一个 **"打了激素的超级 Excel"**。 你可能会问:“我平时记账、存资料,用 Excel 挺好用的呀,为啥非要搞个数据库?” 想象你开了一家奶茶店,每天 100 个订单,用 Excel 记账毫无压力。但如果你的店变成了全国连锁,每天产生 1000 万个订单呢?此时 Excel 会面临三个必死结局: 1. **装不下**:Excel 一个表格最多只能存 104 万行数据,强行存千万数据,电脑会直接罢工。 2. **没法同时写**:如果全国 500 个店长同时往一个 Excel 里填数据,系统会提示“文件被占用”。而数据库可以支持成千上万人同一秒钟同时写入。 3. **极易填错**:Excel 太自由了,年龄那一列填个“哈哈”也能存。数据库则像个严厉的质检员,只要设置了必须是数字,乱填直接拒收。 为了解决这些问题,专门存海量、高频率数据的软件诞生了,这就是**数据库**(比如最常见的 MySQL)。 ### **2. SQL 干啥用的?(声明式哲学)** 既然不用 Excel 了,我们就不能用鼠标去点“筛选”了。我们需要一种语言来命令数据库干活,这就是 **SQL**。 SQL 是一种 **“声明式”** 语言。什么意思?就像你去餐厅点菜。你只需要对服务员说:“给我来一盘西红柿炒蛋,不要放葱。”你完全不需要关心后厨是怎么打蛋、切菜的,你只管“下命令”,数据库内部的机器人会自动用最快的速度把菜端给你。 不用担心各个数据库的 SQL 差别很大,它就像“普通话”,虽然 MySQL 或 Oracle 偶尔带点“方言”,但骨子里是相通的。 **前面这两小节,请记住:数据库是扛并发的数据仓库,SQL 是你指挥仓库管理员的“普通话”。** --- ### **3. 纵向抽数据:SELECT 这把剪刀** 假设数据库里有一个名叫 `user` 的超级大表格。它就像一个有 100 列的巨型 Excel: |**姓名 (name)**|**年龄 (age)**|**手机号 (phone)**|**家庭住址 (address)**|**...还有96列**| |---|---|---|---|---| |图图|5|138xxxx|翻斗大街大耳朵图图家|...| |萍雨|20|139xxxx|翻斗花园二号楼|...| 现在我们要一份“点名册”,上面**只要姓名**。怎么让数据库别把剩下 99 列隐私信息都吐出来浪费内存?这就要用到:`SELECT` 和 `FROM`。 ```SQL -- 这是一条便利贴(注释),数据库不会管它 SELECT name FROM user; ``` - **`FROM user`**:定位数据源。告诉数据库去翻哪张表。 - **`SELECT name`**:它是一把**极其锋利的垂直剪刀**。它不关心表里装的是谁,只负责自上而下,把 `name` 这一列剪下来,剩下的直接扔进垃圾桶。 **一个极度反常识的真相:** 现实中我们是先找表再剪列,所以逻辑上是先 `FROM` 后 `SELECT`。**虽然写 SQL 时 `SELECT` 在前,但数据库引擎执行时是先看 `FROM` 的!** 它们的逻辑和人类是一致的。(关于整个 SQL 庞大语句的完整执行顺序,我在后续会给大家拼起一张完整的版图) --- ### **4. 横向筛数据:WHERE 这个漏斗** 现在我们出现了新的需求。 如果我们只想看“及格”的人,或者是名字里带 "雨" 的人,上一节的 `SELECT` 剪刀好像办不到了。因为它一剪就是一整列,成绩好的学生和成绩差的学生都被一起剪下来了。 有没有什么办法能像“过筛子”一样,把不符合条件的人直接漏掉? 这就轮到 **`WHERE`** 出场了。 这时可能有细心的小伙伴( ̄ω ̄〃)要问了: **“既然 `SELECT` 和 `WHERE` 都有筛选作用,那它们之间到底有什么区别呢?”** 问得太好了!我们依然拿 Excel 那个拥有 100 列、10万行的大表格来做例子: - **`SELECT` 管的是“宽度”(列):** 它决定了你最后拿到手的表格有几列(比如只要姓名和成绩)。 - **`WHERE` 管的是“长度/条数”(行):** 它像一个严格的**水平漏斗**。数据库在扫描每一行数据时,都会让这行数据穿过 `WHERE` 漏斗,符合条件的留下,不符合的直接滚蛋。 比如我们现在的需求:“找出成绩大于 60 分,或者名字里带 '雨' 的人”。在 SQL 里,我们可以这样写: ```SQL select name, score from user where score > 60 or name like "%雨%"; ``` (💡 **萍雨小贴士**:这里的 `LIKE` 叫做“模糊查询”。百分号 `%` 就像扑克牌里的赖子,代表“任意字符”。`"%雨%"` 的意思就是:不管你叫“萍雨”、“听雨”,还是“大雨”,只要名字里包含“雨”字,统统给我捞出来!)  #### **逻辑运算符的陷阱** 在实际的数据分析中,筛选条件往往是很复杂的。比如我们现在遇到了一个终极需求: **找“成绩及格”【且】“家住翻斗花园”的人,或者“姓王”【或】“姓李”的人。** 这里出现了我们在 SQL 里最常用的两个**逻辑运算符**: - **`AND`(且):** 要求极高,必须同时满足。成绩及格但不住翻斗花园的?淘汰。住翻斗花园但没及格的?淘汰。只有两样都占的才能留下。 - **`OR`(或):** 比较宽容,满足一个就行。姓王的或者姓李的,都可以留下。 现在,我们要把这个需求翻译成 SQL。很多初学者(包括我自己刚开始的时候 😅)会顺手写出这样一段破绽百出的代码: ```SQL -- ❌ 错误示范,千万别抄! select name from user where score > 60 and address = "翻斗花园" OR name like "王%" or "李%"; ``` 这段代码有两个极其致命的错误: **第一个致命错误:“把电脑当人”。** 结尾的 `or "李%"` 在人类听来很顺畅(姓王或李),但数据库是个笨机器,它不知道“李”要和哪个字段比。在 `OR` 的两边,必须是完整的句子!必须老老实实写成:`or name like "王%" or name like "李%"`。 **第二个致命错误:“优先级的灾难”。** 当 `AND` 和 `OR` 混在一起时,数据库会先算哪一个? 这就要掏出我们小学学的数学定律了:“先乘除,后加减”。 在 SQL 里,**`AND` 的优先级永远高于 `OR`**(我们可以把 `AND` 当成乘法,`OR` 当成加法)。如果不加控制,数据库的理解会和我们的本意十万八千里。 **正确的姿势是:遇到多个条件,永远用括号 `()` 把它们包裹起来,明明白白地告诉数据库先算谁!** ```SQL -- ✅ 正确写法:用括号圈出逻辑边界 select name from user where (score > 60 and address = "翻斗花园") OR (name like "王%" OR name like "李%"); ``` #### SQL 的真实执行顺序: 接着,我们来填一个上一节留下的坑。 在现实逻辑中,我们肯定是:先去仓库找到表格(`FROM`),然后用漏斗把不符合条件的人筛掉(`WHERE`),最后用剪刀剪下我们要的信息(`SELECT`)。 我们在写 SQL 语句时,`WHERE` 也是紧紧跟在 `FROM` 后面的。 **它的真实执行顺序排在第二位:`FROM` -> `WHERE` -> `SELECT`。** 也就是说,数据库在还没有决定用剪刀剪哪一列之前,就已经用 `WHERE` 漏斗把该淘汰的行全部淘汰掉了。这就叫底层的执行效率! --- ### **5. 隐形地雷:NULL 的致命陷阱** 掌握了条件筛选,我们是不是能精准狙击任何数据了?直到有一天,老板让你查一个数据:“找出所有成绩不是 60 分的学生”。 你轻蔑一笑,敲下这行代码: ```SQL select name from user where score != 60; ``` 结果报表交上去,老板大发雷霆:“那几个缺考的人去哪了?他们难道考了 60 分吗?为什么不在名单里!” 你翻烂了代码也找不到 Bug。恭喜你,你踩中了 SQL 世界里最经典的隐形地雷:**NULL(空值)**。 很多小伙伴(包括当年的我)对 NULL 有一种极其致命的误解,认为 NULL 就是 0,或者就是啥也不填的“空格”。 大错特错!我们拿考试来做个比喻: - **`0`**:代表这人参加了考试,但是交了白卷,考了 0 分。 - **`" "`(空格)**:代表这人参加了考试,但在卷子上画了一个隐形的空格交上去了(空格在电脑里是一个实实在在的字符,占内存)。 - **`NULL`**:代表这人**根本没参加考试**,连卷子都不存在! **SQL 独有的“三值逻辑”** 现实世界的编程往往非黑即白:真(True)和假(False)。 但 SQL 的世界有第三种状态:**不知道(Unknown)**。 当我们用 `where score != 60` 去筛人时: - 遇到考 80 分的人,数据库判断:80 != 60 成立(True)。漏斗放行。 - 遇到缺考(NULL)的人,数据库懵了。它会想:“这个人连成绩都没有,我不确定他是不是 60 分。” 于是给出结论:**不知道(Unknown)**。 请死死记住上一节的一句话:**`WHERE` 漏斗极其冷血,它只放行明确为 True 的数据!** 因为缺考的人得出的结论是“不知道”,所以他们被数据库无情地、静默地抹杀了。 **如何把这群“缺考”的人抓出来?** 有小伙伴可能会耍小聪明:既然他们是 NULL,那我直接用等号抓不就行了? ```SQL -- ❌ 极其惨烈的错误示范 select name from user where score = null; ``` 写出这种代码,你这辈子都查不出任何数据。 因为 NULL 是“未知”。你去问数据库:“未知 等于 未知 吗?” 数据库的回答依然是:“不知道!” 在 SQL 里,想要抓捕 NULL,必须抛弃所有的数学符号(`=`, `!=`, `>`, `<`),使用数据库专门为它打造的雷达探测器:**`IS NULL`** 或者 **`IS NOT NULL`**。 ```SQL -- ✅ 正确抓捕缺考人员的姿势 select name from user where score is null; -- ✅ 抓捕所有正常考了试的人 select name from user where score is not null; ```  ### 6. 最后 至此,我们的第一篇《数据捞取指南》正式合龙。回头看看,你已经不再是那个只会用 Excel 鼠标点点点,或者企图把海量数据塞进 Java 里的“麻瓜”了。 你拥有了三件底层兵器: 1. **`FROM`**:精准定位数据源的导航仪。 2. **`WHERE`**:极其严格的水平漏斗(千万当心 `AND/OR` 的优先级,以及 `NULL` 的暗杀)。 3. **`SELECT`**:自上而下的垂直剪刀。 用这三件兵器,你已经能从千万级的数据废料中,精准榨取任何你想要的信号。 **但不要高兴得太早。 我们捞出来的数据,依然是极其粗糙的“底层原石”。 如果产品经理提了一个变态要求:数据库里存的日期是 `2026-03-14 12:39:00`,但他在页面上非要只看 `2026-03-14`;数据库里存的状态是 `1` 和 `2`,但他非要你导出的时候直接变成“正常”和“封禁”。 难道我们又要绝望地把数据拉回后端代码里,去写成百上千行的 `if-else` 转换吗? 我是萍雨,我们下期再见ヾ( ̄▽ ̄)ByeBye~
国庆也不能松懈,来道SQL题😀 表: Calls +--------------+----------+ | Column Name | Type | +--------------+----------+ | caller_id | int | | recipient_id | int | | call_time | datetime | +--------------+----------+ (caller_id, recipient_id, call_time) 是这个表的主键。 每一行所含的时间信息都是关于caller_id 和recipient_id的。 编写一个 SQL 查询来找出那些ID们在任意一天的第一个电话和最后一个电话都是和同一个人的。这些电话不论是拨打者还是接收者都会被记录。 结果请放在一个任意次序约束的表中。 查询结果格式如下所示: 输入: Calls table: +-----------+--------------+---------------------+ | caller_id | recipient_id | call_time | +-----------+--------------+---------------------+ | 8 | 4 | 2021-08-24 17:46:07 | | 4 | 8 | 2021-08-24 19:57:13 | | 5 | 1 | 2021-08-11 05:28:44 | | 8 | 3 | 2021-08-17 04:04:15 | | 11 | 3 | 2021-08-17 13:07:00 | | 8 | 11 | 2021-08-17 22:22:22 | +-----------+--------------+---------------------+ 输出: +---------+ | user_id | +---------+ | 1 | | 4 | | 5 | | 8 | +---------+ 解释: 在 2021-08-24,这天的第一个电话和最后一个电话都是在user 8和user 4之间。user8应该被包含在答案中。 同样的,user 4在2 021-08-24 的第一个电话和最后一个电话都是和user 8的。user 4也应该被包含在答案中。 在 2021-08-11,user 1和5有一个电话。这个电话是他们彼此当天的唯一一个电话。因此这个电话是他们当天的第一个电话也是最后一个电话,他们都应该被包含在答案中。
SQL Server学习笔记
## 数据库管理技术的基本概念 1. 数据 2. 信息 3. 数据处理 ## 数据库管理技术的发展阶段 1. 人工管理 2. 文件系统 3. 数据库系统 4. 大数据 ## 数据库系统的组成 1. 硬件系统 2. 操作系统 3. .数据库 4. 数据库管理系统 5. 数据库应用系统的开发工具及相关接口软件 6. 数据库应用系统 7. 用户 1. 数据库管理员 2. 数据库分析师 3. 应用程序员 4. 终端用户 ## 数据库体系结构 1. 集中式系统 2. 分布式系统 3. 个人计算机系统 4. 客户/服务器系统 5. 浏览器/服务器系统 ## 数据库的三级模式 1. 内模式 2. 模式 3. 外模式 ## 数据库的二级映像 1. 外模式/模式 2. 模式/内模式 ## 数据库管理系统的功能 1. 数据定义 2. 数据操控 3. 数据库运行控制 4. 数据字典 ## 登录方式 1. windows登录,本地登录 2. sqlserver登录,远程登录 ## 数据库备份 mdf数据文件,ldf日志文件 1. 分离->附加 分离后数据库会不可用 2. 备份->还原 保存为.bak文件 任务——>备份 3. 数据库脚本 ## 注释 单行注释 -- 多行注释 /* */ ## 数据库相关 ### 创建数据库 完整语法 ```sql create database DBTEST on --数据文件 ( name = 'DBTEST', --逻辑名称 filename = '', --物理路径和名称 以.mdf结尾 size = 5MB, --文件的初始大小 maxsize = 40MB, --文件的最大大小 filegrowth=2MB, --文件增长方式 可以是大小,也可以是百分比 ) log on --日志文件 ( name = 'DBTEST_log', --逻辑名称 filename = '', --物理路径和名称 以.ldf结尾 size = 5MB, --文件的初始大小 filegrowth=2MB, --文件增长方式 可以是大小,也可以是百分比 ) ``` 简单语法 ```sql create database DBTEST --采用默认值创建 ``` ### 修改数据库 ```sql alter database DNTEST ``` ### 删除数据库 ```sql drop database 数据库名称 ``` ### 切换数据库 ```sql use DBTEST --use 数据库名称 ``` ## 表相关 ### 创建表基础语法 ```sql create table 表名 ( 字段1 数据类型, 字段2 数据类型, 字段3 数据类型, ) ``` #### 约束 - primary key:主建 - references 表名(字段名):外键 - identify(1,1):自动增长,初始值1,增长步长1 - not null:不为空 - check():检查约束 - default():默认值 - unique:唯一约束 #### 数据类型 - int:整型 - float:浮点型 - decimal(n,m):n表示总长度,m表示小数位数 - date:年月日 --getdate() 获取当前时间 - datetime:年月日时分秒 - smalldatetime:范围更小 - char():定长字符串 - varchar():变长字符串 - text:长文本 - nchar,nvarchar,ntext:unicode字符,对中文友好 ### 删除表 ```sql drop table 表名 ``` ### 修改表结构 #### 1.添加列 ```sql alter table 表名 add 新列名 数据类型 ``` #### 2.删除列 ```sql alter table 表名 drop column 列名 ``` #### 3.修改列 --如果已经存在数据了则可能报错,建议建表的时候就有冗余 ```sql alter table 表名 alter column 列名 数据类型 ``` ### 维护约束 #### 1.删除约束 ```sql alter table 表名 drop constraint 约束名 ``` #### 2.添加约束 ##### check约束 ```sql alter table 表名 add constraint 约束名 check(表达式) ``` ##### 主键约束 ```sql alter table 表名 add constraint 约束名 primary key(列名) ``` ##### 唯一约束 ```sql alter table 表名 add constraint 约束名 unique(列名) ``` ##### 默认值约束 ```sql alter table 表名 add constraint 约束名 default 默认值 for 列名 ``` ##### 外键约束 ```sql alter table 表名 add constraint 约束名 foreign key(列名)references 关联表名(列名) ``` ## 数据相关 ### 插入数据 ```sql insert into 表名(字段1,字段2,字段3)values(内容1,内容2,内容3) ``` 简写 --不建议 ```sql insert into 表名 values(内容1,内容2,内容3) ``` 一次性插入多行数据 ```sql insert into 表名(字段1,字段2,字段3) select 内容1,内容2,内容3 union select 内容1,内容2,内容3 union select 内容1,内容2,内容3 ``` ### 修改删除数据 #### 1.修改 ```sql update 表名 set 字段1=值1,字段2=值2 where 条件 ``` #### 2.删除数据 ```sql delete from 表名 where 条件 ``` #### 关于删除 - ```sql drop table 表名 --删除表对象 ``` - ```sql truncate table 表名 --删除数据(清空数据),表对象即表结构依然存在,不能有条件,自动编号存在 ``` - ```sql delete from 表名 --删除数据(清空数据),表对象即表结构依然存在,可以有条件,自动编号清除 ``` ## 查询相关 ### 基本查询 #### 1.查询所有列所有行 ```sql select * from 表名 ``` #### 2.查询指定列 ```sql select 列名1,列名2,列名3 from 表名 ``` #### 3.查询指定列,别名显示 ```sql select 列名1 别名1,列名2 别名2,列名3 别名3 from 表名 ``` #### 4.查询指定列,去重 ```sql select distinct(列名) from 表名 ``` ### 条件查询 ```sql select * from 表名 where 条件 select top 10 * from 表名 where 条件 --前十位 select top 10 percent * from 表名 where 条件 --前百分之十 select * from 表名 where order by 字段 desc/asc ``` ### 模糊查询 - like - %:代表匹配0个字符,一个字符或多个字符 - _:代表匹配有且只有一个字符 - []:代表匹配范围内 - ![]:代表匹配不在范围内 - substring(n,m,t) --n为字符串,m是起始位,t是跨度 #### 聚合函数 - count:求数量 - max:求最大值 - min:求最小值 - sum: 求和 - avg:求平均值 - round:保留小数位 round(2.556,2) ——>2.55 - datediff:求时间差 datediff(year,时间1,时间2) ### 分组查询 ```sql select * from 表名 where 条件 group by 列名 having 条件 ``` --普通条件在where后,聚合条件在 having后 注:分组查询只允许查询分组条件和聚合函数 ### 多表查询 ```sql select * from 表名1,表名2 where 条件 ``` #### 1.内连接 ```sql select * from 表名1 inner join 表名2 on 条件 ``` #### 2.外连接 ```sql select * from 表名1 left join 表名2 on 条件 --左外 select * from 表名1 right join 表名2 on 条件 --右外 select * from 表名1 full join 表名2 on 条件 --全外 ``` #### 3.自连接 ```sql select 别名1.字段,别名2.字段 from 表名 别名1 inner join 表名 别名2 on 条件 ``` ## 数据库设计 ### 1.三范式 - 第一范式:原子性 不可再分 - 第二范式:实体唯一性 - 第三范式:不能有冗余 ### 2.表关系 - 一对一 - 一对多 - 多对多 ## 变量 ### --信息打印: ```sql print'hello,sql' --消息窗口显示 ``` ```sql select'hello,sql' --信息窗口显示 ``` ### --变量: 1. 局部变量:以@开头,先声明,在赋值 - ```sql declare @str varchar(20) set @str='i like sql' --赋值变量指定的值 select @str ='i like sql' --一般用于表中的查询出的数据赋值给变量,如果查询结果有多条,取最后一条赋值 ``` 2. 全局变量:以@@开头,由系统就行定义和维护 ### --go语句: 1. 等待go语句之前的代码执行完之后才能执行后面的代码 2. 批处理结束的一个标志 ## 运算符 ### 强制类型转化 1. Convert(数据类型,变量名) 例:Convert(varchar(10),@zc) 2. cast(变量名 as 数据类型) 例:cast(@zc as varchar(10)) ### 流程控制语句 #### (1)选择分支 case选择: ```sql case when 条件 then 执行1 when 条件 then 执行2 else 最终执行 end ``` if判断语句: ```sql if 条件判断 begin 语句 end else begin 语句 end ``` #### (2)循环 ```sql while 条件 begin 语句 end ``` ## 子查询 ```sql where条件 临时表 exist ``` ## 分页 ### (1)top方案 ```sql declare @PageSize int = 5 declare @PageIndex int = 3 select top(@PageSize)*from Student where StuId not in (select top(@PageSize*(PageIndex-1))StuId from Student) ``` ### (2)使用row_number分页 ```sql declare @PageSize int = 5 declare @PageIndex int = 3 select *from (select ROW_NUMBER() over(order by StuId) RowId,*from Student) Temp where RowId between (@PageIndex-1)*@PageSize+1 and @PageIndex*@PageSize ``` ## 事务 ```sql begin transaction --开始语句 语句 @@ERROR --统计错误 如果出现错误 @@ERROR的值不为零 ``` ```sql commit transaction --提交事务 rollback transaction --回滚事务 ``` ## 索引 1. 聚集索引 --类似于按照拼音查找 只能有一个 2. 非聚集索引 --类似于按照部首查询 可以有多个 ### 添加索引 ```sql create [unique][clustered|nonclustered] index <index name> on <table or view name>(<colum name>[ASC|DESC][,...n]) ``` ### 查看索引 (sys.indexes) ```sql select * from sys.indexes where name = '索引名' ``` ### 删除索引 ```sql drop index 索引名 on 表名 ``` ### 使用索引 ```sql select * from 表名 with (index=索引名) where 条件 ``` ## 视图 ### 创建视图: ```sql create view 视图名 as select语句 go ``` ### 查询视图: ```sql select * from 视图名 ``` ### 删除视图: ```sql drop view 视图名 ``` ## 游标 ### 分类: - 静态游标 - 动态游标 - 键集驱动游标 ### 创建游标(scroll:滚动游标,没有scroll,只进) ```sql declare 游标名 cursor scroll for select 字段名 from 表名 ``` ### 打开游标 ```sql open 游标名 ``` ### 关闭游标 ```sql close 游标名 ``` ### 删除游标 ```sql deallocate 游标名 ``` ### 提取数据 ```sql fetch first from 游标名 --提取第一行 fetch last from 游标名 --提取最后一行 fetch absolute 2 from 游标名 --提取第二行 fetch relative 2 from 游标名 --当前行下移两行 fetch next from 游标名 --下移一行 fetch prior from 游标名 --上移一行 ``` --提取游标数据存放入变量,进行查询所有列信息 ```sql declare @acc varchar(20) fetch absolute 2 from mycur into @acc select * from Menber where MenberAccount=@acc ``` --遍历游标 ```sql @@fetch_status :0提取成功,-1提取失败,-2不存在 declare @acc varchar(20) fetch absolute 2 from mycur into @acc while @@fetch_status=0 begin print'提取成功:'+@acc fetch next from mycur into @acc end ``` --利用游标进行数据的修改和删除 ```sql fetch absolute 2 from mycur update Member set MemberPwd = '654321' where current of mycur fetch absolute 2 from mycur delete from Member where current of mycur ``` --一行多列游标 ```sql declare 游标名 cursor scroll for select 字段1,字段2,字段3 from 表名 ``` ## 函数 1. 系统函数 2. 自定义函数 1. 标量值函数(返回单个值) 2. 表值函数(返回查询结果) ### 创建函数 ```sql create function 函数名(@传入值 传入值类型) returns 返回值类型 as begin declare @返回值 返回值类型 select @返回值 = return @返回值 end ``` #### 返回值类型的情况 1. 可以有其他逻辑代码 ```sql create function 函数名(@传入值 传入值类型) returns @返回值 table ( 字段1 数据类型, 字段2 数据类型, 字段3 数据类型, 字段4 数据类型 ) as begin insert into @返回值 查询语句 return end ``` 2. 函数体只能有return+sql查询结果 ```sql create function 函数名(@传入值 传入值类型) returns table as return 查询语句 go ``` 函数调用 ```sql select dbo.函数名() --返回值为一个值 select * from 函数名() --返回值为一个表 ``` ### 函数删除 ```sql drop function 函数名 ``` ## 触发器 ### 分类 1. instead of 事前触发器 2. after 事后触发器 ### 创建触发器 ```sql create trigger 触发器名称 on 表名 [after|instead of] [insert|delete|update] as 语句 go inserted插入的临时表 deleted删除的临时表 ``` 数据更新实际上是先删除再插入 ## 存储过程 存储过程与函数的主要区别:存储过程可以被其他程序调用,函数一般用于select语句中 ### 创建存储过程 #### 存储过程创建 ```sql create proc 存储过程名 @变量1名 变量类型,@变量2名 变量类型,@变量3名 变量类型 output,@变量4名 变量类型 output as 语句 go ``` #### 存储过程的调用 ```sql exec 存储过程名 变量值1,变量值2,变量名 output,变量名 output --前两个用于输入,后两个用于接收,如果变量名(第三四个)已经赋值了则具有输入输出功能 ``` #### 删除存储过程 ```sql drop proc 存储过程名 ```
《SQL优化打油诗》 执行计划先 EXPLAIN, 慢查病因现眼前: key 看索引命中否, key_len 算长度够不够。 type 查全表有没有, ALL 和 index 赶紧溜; Extra 回表别发愁, 覆盖索引来解忧! 联合索引左前缀, 区分高低顺序配; 长字段用前缀缩, 口诀记牢性能飞!
查所有但只去除一个字段,除了罗列还有什么办法吗
求SQL Server文档类学习资料
学校开设了SQL Server数据库概论课程,请问有学习SQL Server的文档类的学习资源推荐吗?以及学习数据库设计的视频或文档推荐?
老弟想自己做个微信,被我一个问题劝退了。。
大家好,我是程序员鱼皮。最近老弟小阿巴放暑假,想找点事情做,于是就来问我:老鲏,我想做个练手项目,有没有什么好的建议? <p align="center"><img src="https://pic.code-nav.cn/migrate_image/9fad16b278c02b7364c9fc4dcbc1520f.webp" alt="" width="138px" /></p> 我说:练手项目的话,就做个自己感兴趣的呗,想加什么功能就加什么,做起来会更舒服~ 小阿巴:Emm,我感兴趣的太多了,有没有推荐啊? 我说:那就想想自己经常使用的网站或 APP,选个对业务流程相对熟悉的。 小阿巴思考片刻,一拍脑袋:对啊,我天天用微信,那我就做个微信吧!说不定之后大家都在用我做的软件聊天呢? <p align="center"><img src="https://pic.code-nav.cn/migrate_image/f457df026949f809bf2c94e5aeacddee.webp" alt="" width="250px" /></p> 我一听,不禁暗自惊叹,没想到小伙子年纪轻轻,野心很大啊! 我说:想法不错,但想做个微信这样的 IM(即时通讯)项目,可没有那么简单,你有什么实现思路么?说来听听? 小阿巴:微信的核心功能是收发消息,我可以把用户 A 发送的消息保存到数据库中,用户 B 进入聊天界面时,从数据库查询出发给他的消息就行。 我一听这个回答,就知道以小阿巴目前的水平,想做出微信是不太可能了。。。 我问:Emm,暂且不考虑用户体验和性能,我们就先实现基础功能吧,你会怎么让用户查看自己的历史消息呢? 小阿巴思考片刻,然后嘴角微微上扬,露出狡黠的笑容:你是不是以为我会说一次性把所有历史消息全部查出来?可惜啊老鲏,你把我想的太天真了,用户可能有成百上千条历史消息,全量加载会很慢,所以我必然会使用 `分页` 来查询! <p align="center"><img src="https://pic.code-nav.cn/migrate_image/7f6eeb11aca08afcbba99c74dfb9679b.webp" alt="" width="178px" /></p> 我说:行,那你打算怎么分页呢? 小阿巴:这还真难不倒我,这几年我苦练增删改查,分页写得很溜的!纸笔呈上来,看我给你手写 SQL: ```sql select * from message where user = '鱼皮' limit 0, 20; ``` 我说:Emm,老弟啊,听我一句劝,咱先别想着做微信了,先实现一个消息管理系统吧。 小阿巴:怎么说?吾 SQL 不亦精乎? <p align="center"><img src="https://pic.code-nav.cn/migrate_image/0d9197c9fe076e01839f9f876b86fe6a.png" alt="" width="172px" /></p> 其实这也是一道经典的场景题:即时通讯项目中怎么实现历史消息的下拉分页加载? 下面鱼皮给大家讲解一下。 ## 如何实现下拉分页加载? ### 业务场景 一般在即时通讯项目(比如聊天室)中,我们会采用下拉分页的方式让用户加载历史消息记录。 区别于标准分页每次只展示当前页面的数据,下拉分页加载是 **增量加载** 的模式,每次下拉时会请求加载一小部分新数据,并放到已加载的数据列表中,从而形成无限滚动的效果,确保用户体验流畅。 比如用户有 10 条消息记录,以 5 条为单位进行分页,刚进入房间时只会加载最新的 5 条消息: <img src="https://pic.code-nav.cn/migrate_image/31863ca4262e0bffeb02fcbb3a0c2d65.webp" alt="" width="572px" /> 下拉后,会加载历史的第 6 - 10 条消息: <img src="https://pic.code-nav.cn/migrate_image/051e015779c727aa51bda6a76c4fa1eb.webp" alt="" width="574px" /> 理解了业务场景后,再看下实现方案,为什么不建议使用传统分页实现下拉加载。 ### 传统分页的问题 在传统分页中,数据通常是 **基于页码或偏移量** 进行加载的。如果数据在分页过程发生了变化,比如插入新数据、删除老数据,用户看到的分页数据可能会出现不一致,导致用户错过或重复某些数据。 举个例子,对于即时通讯项目,用户可能会持续收到新的消息。如果按照传统分页基于偏移量加载,第一页已经加载了第 1 - 5 行的数据,本来要查询的第二页数据是第 6 - 10 行(对应的 SQL 语句为 limit 5, 5),数据库记录如下: <img src="https://pic.code-nav.cn/migrate_image/6d02fac207d8fe3b0220d79554bfc9ae.webp" alt="" width="100%" /> 结果在查询第二页前,突然用户又收到了 5 条新消息,数据库记录就变成了下面这样。原本的第一页,变成了当前的第二页! <img src="https://pic.code-nav.cn/migrate_image/cb413a574fb8da3b11410687ef8f2459.webp" alt="" width="100%" /> 这样就导致查询出的第二页数据,正好是之前已经查询出的第一页的数据,造成了消息重复加载。所以不建议采用这种方法。 ### 推荐方案 - 游标分页 为了解决这种问题,可以使用游标分页。使用一个游标来跟踪分页位置,而不是基于页码,每次请求从上一次请求的游标开始加载数据。 一般我们会选择数据记录的唯一标识符(主键)、时间戳、或者具有排序能力的字段作为游标。比如即时通讯系统中的每个消息,通常都有一个唯一自增的 id,就可以作为游标。每次查询完当前页面的数据后,可以将最后一条消息记录的 id 作为游标值传递给前端(客户端)。 <img src="https://pic.code-nav.cn/migrate_image/f52586c5099ad9d79be6fa525e5d4c23.webp" alt="" width="100%" /> 当要加载下一页时,前端携带游标值发起查询,后端操作数据库从 id 小于当前游标值的数据开始查询,这样查询结果就不会受到新增数据的影响。 <img src="https://pic.code-nav.cn/migrate_image/e629734d194fb9a3e8161e163e0d88ed.webp" alt="" width="100%" /> 对应的 SQL 语句为: ```sql SELECT * FROM messages WHERE id < :cursorId ORDER BY id DESC LIMIT 5; ``` ### 扩展知识 其实游标分页是一种经典方案,它的应用场景很多,特别适用于增量数据加载、大数据量的高性能查询和处理。除了 IM 系统获取历史消息记录之外,常见场景还有社交媒体信息流、内容推荐系统、数据迁移备份等等。 游标分页还有很多扩展知识,篇幅原因就不在这里展开了,感兴趣的同学可以在我们的 [程序员面试刷题工具 - 面试鸭](https://www.mianshiya.com/bank/1795650093939204097/question/1821408560444809217) 上阅读。 ## 最后 小阿巴听完,长叹道:唉,没想到光是这么一个小功能,就把我难住了。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/871914ce8dab2bf1ec7f161c5603f88d.webp" alt="" width="150px" /></p> 我说:你可别这么想。。。难住你的,可不止这一个小功能啊!想做一个成熟的 IM 系统,除了最基础的消息发送和获取功能外,你得去学习 WebSocket 实时通讯、得考虑到消息收发的性能、得考虑到消息的顺序和一致性、得考虑到消息的存储成本和安全,等等等等。可没那么容易。 小阿巴:得,那我先去做消息管理系统了!🐶 ## 更多 💻 编程学习交流:[编程导航](https://www.codefather.cn) 📃 简历快速制作:[老鱼简历](https://laoyujianli.com) ✏️ 面试刷题神器:[面试鸭](https://mianshiya.com)
学了十几种编程语言后,我终于悟了!
大家好,我是程序员鱼皮。 16 ~ 24 年,算下来我学编程 8 年多了,这期间我学过十几种编程语言,比如 C、C++、Java、Python、JavaScript、Go、PHP、C#、SQL、Scala 等。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/60d6bdc6acc5c2aa2b96559cdfaa06d9.png" alt="" width="162px" /></p> 这么一看,目前排名前 10 的语言除了 Fortran 没接触过外,别的语言或多或少都写过点儿东西。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/f22bca69028c3270e54b0c205203f30e.webp" alt="" width="100%" /></p> Visual Basic 是高中考计算机就学过的了,那会儿我还是计算机课代表来着,掌握着班里电脑的使用权哈哈哈。 学过这么多语言,乍一听好像挺厉害的,但事实上,学过的语言很多,熟练的就那么几个。很多语言学完之后,不写就忘了,就好像从来没有学习过一样。 那我为什么要学这么多编程语言?是怎么学习的?学了这么多语言对我有哪些好处和坏处?现在我们到底应不应该学多门语言呢? 下面分享一下。 ### 为什么学这么多编程语言? 回顾我学语言的过程,无非就是四个原因:感兴趣、学校让学、找工作混饭吃、还有就是项目要用。 老实说,现在计算机专业的同学,光跟着学校学,基本就会好几种语言了吧。 先说兴趣,我在高中时就很想做自己的网站,只不过那会儿是用什么 Front Page,Dreamweaver 拖拖拽拽就搞定了(应该没有多少同学用过吧)。所以上了大学,我第一个自学的就是前端,对应的语言就是 HTML、CSS、JavaScript 三件套,当时的目标就是加入学校的网站建设工作室,能来点儿外快。 那会儿真是青涩啊,手里拿了本 HTML 5 入门教程,感觉拥有了整个世界。现在回想起来,只觉得头顶凉飕飕的~ <p align="center"><img src="https://pic.code-nav.cn/migrate_image/cf5c8a786e881ff4736b7367cb959cab.webp" alt="" width="354px" /></p> 再说说学校教的语言,当时我们刚入学第一个接触的是 C 语言,现在好像很多学校改成了直接讲 C++? 可惜的是,我当时并不开窍,心思也不在学习上,所以学的一塌糊涂,最后写了个能在小黑框里运行的图书管理系统就毕业了。 之后我开始学 C++,因为感觉跟学校课堂学 C 语言时没学到啥(也可能是我比较菜),所以这次我选择上网自学(比如看圆脸妹妹的 C++ 课堂)。大概学了一个月左右,写了一点简单的桌面程序,我就放弃 C++ 了,转战 Java。一方面是听说 Java 好找工作,好混饭吃;另外一方面当时我可能对学习新语言有点上瘾,就想着多尝试尝试。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/633896f9c269e98f594afeb58fddb279.webp" alt="" width="328px" /></p> 刚开始学 Java 就是学基础语法,直到学完我都不知道为啥 Java 好找工作,感觉能做的事和 C++ 差不多。直到后来学了企业 Java 开发,也就是 Java Web,接触了 Spring 之类的框架后,我才感叹到:“Java 真香!” 于是我决定在后端开发这条路上坚持走下去,后来也去学了 SQL(结构化数据查询语言)、Scala 等编程语言。SQL 可是后端程序员的基本功,大家可以用我做的 [免费 SQL 刷题网站](http://sqlmother.yupi.icu/) 多练练。 然后我本来以为今后再也不会用 C++ 了,但听说算法竞赛、保研机试啥的都是它,所以后来就拿 C++ 刷了几百道 OJ 题目。还有个戏剧性的事,打 s 我也没想到最后我的毕设还是拿 C++ 做了个图像处理程序,当时花了一些时间专门学习 OpenCV。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/f462bb373679a347c06d2b252e2beccd.webp" alt="" width="148px" /></p> 再聊聊其他语言,基本都是有项目需求的时候才会学习。比如我大一学 Python,只是为了完成一个数据抓取 + 可视化项目,听说用 Python 简单、做的快,就学了几天,对照着 Demo 删删改改就搞定了;学 Golang 是因为当时在实验室搞区块链,有个框架是用的 Go 语言;学 C# 也是因为在实验室有个桌面端项目要用。后面项目用不到了,这些语言我也就慢慢陌生了。 对了,还有 PHP!有趣的是,我对 PHP 的印象其实发生了好几次转变。 大一的时候,听说它是世界上最好的语言,我就学了。当时因为也没怎么深入学习过别的编程语言,所以觉得 PHP 挺香的。但后来等前端和 Java 熟练一些,感受到前后端分离开发的优势后,我就有点对 PHP 嗤之以鼻了。 由于性能和安全问题,再加上竞争对手的崛起,近几年来 PHP 也一直呈现衰落之势。目前热度排名 16 位,甚至已经被新兴语言 Rust 超越: <p align="center"><img src="https://pic.code-nav.cn/migrate_image/b91cbc6b7cf924e1a453e9ed98a6e5db.webp" alt="" width="100%" /></p> 但最近,我却偶尔会神神叨叨一句:“PHP 是世界上最好的语言!” 为啥呢?因为对于需要 **服务端渲染** 的网页开发场景,PHP 的开发效率的确是高的。 最近我们在做 [面试刷题网站 - 面试鸭](https://mianshiya.com/) 的网页端,就特地使用了服务端渲染技术: <p align="center"><img src="https://pic.code-nav.cn/migrate_image/697797c5b4695d3879723e50e828dba5.webp" alt="" width="100%" /></p> 所谓的服务端渲染,就是在服务器获取到数据并且拼接好页面后,再返回给浏览器,可以提高页面的加载速度,并且非常有利于 SEO! <p align="center"><img src="https://pic.code-nav.cn/migrate_image/edb30c81bda26da63fd48e52b54d6f59.webp" alt="" width="100%" /></p> 但我个人体验下来,开发服务端渲染网站的成本比普通的网站要高很多,我们用的是前端的 Next.js 框架,要专门区分服务端和客户端组件。这个时候,PHP 就乐了:我天然支持服务端渲染! 要不说 PHP 是曾经开发中小型网站的王者呢,知名的博客站点 WordPress 就是使用 PHP 实现。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/36449f97837b17d5f37941fca86917fe.webp" alt="" width="100%" /></p> 所以到底什么语言、什么框架最好? **还是要看具体的需求,语言和框架都只是工具。** 像华为最近新出的仓颉语言,别的不说,肯定是未来开发华为应用的首选对吧~ ### 学太多编程语言的烦恼 看到这,肯定有同学说了:会这么多编程语言,那不是无所不能,肯定很爽吧! 但事实上,编程语言学多了,也让我有了一定的烦恼。 因为每种语言在数据类型、语法和 API 上都有一定的差别,所以会的语言多了后,我时常陷入混乱,把语法给记混。尤其是学 Go 语言的时候,这玩意和其他语言的语法差别贼大,简直让我怀疑人生,大家可以感受一下: <p align="center"><img src="https://pic.code-nav.cn/migrate_image/8d90f35a99491661fa58fed6537ab583.webp" alt="Go 和 Java 语法对比" width="100%" /></p> 我刚开始做全栈开发的时候,又要写 Java、又要写 JavaScript,所以就老是搞混,在 Java 中写 JavaScript 的函数,在 JavaScript 中 new Java 的对象。再加上我白天工作用 Mac 电脑,晚上下班用 Windows,开发工具的快捷键也不一样,感觉像是带了痛苦面具。 所以有的时候,我真的很想专注地去写一门语言。 但是,学了这么多语言后,我变秃了,也的确变强了! <p align="center"><img src="https://pic.code-nav.cn/migrate_image/f121bd9bd9c8207eb2b38234b89dd7b2.webp" alt="" width="100%" /></p> 所以下面分享下学习多门语言的好处。 ### 学多门编程语言的好处 首先,我发现自己摸清了学习语言的套路,无非就是数据类型、流程控制、函数对象和一些特性,因此学新的语言变得越来越轻松快速。比如我之前没有学过 Kotlin,但后来工作中要让我重构 Kotlin 的代码,我没花额外时间去学习,也基本也都能看懂。因为说实在的,绝大多数代码都是 if ... else ... for 组成的业务逻辑。 我学到了更多的设计思想、了解了每种语言更适合做什么,比如什么是解释型语言和编译型语言,有什么区别,又该如何选择。 我看代码时再也不用被语言所束缚,基本啥都能看懂,大家在交流编程语言时,我也有了更多的发言权,啥都能聊两句,也不用再因为争论 “什么是最好的编程语言” 而跟别人打架了。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/0435621cacf11df3d3a94cdc9c854acd.webp" alt="" width="260px" /></p> 此外,我在开发时有了更多的选择。比如处理 Excel 表格,如果我只会 Java,那只能用 Java 相关类库,不够方便;但因为我也会 Python,就有了更多类库可以选择,几行代码就搞定了,效率拉满。 最重要的,也正是学了更多的语言后,我才找到了最适合自己的语言,从前端转到了 Java 后端。 ### 建议 看到这儿,大家觉得有必要学多门编程语言么? 我觉得,技多不压身,会的语言肯定是越多越好。 虽说可能会有点混乱,但是现在编辑器的语法校验功能都很强大了,所以哪怕语法输错了,也能很快纠正过来,起码比你从零开始学的成本要低。还有一种好方法,干脆就别去记忆语法,熟能生巧,哪怕忘了也可以通过搜索引擎、看文档、问 AI 等方式快速找到正确的写法。 不过千万要记住:无论你想学多少种语言,都要先重点学好一门!一门通门门通,深入理解一门语言后,再学别的就很简单了,很多语法概念、编程经验、调试技巧都是相通的。另一方面是时间有限,把所有语言都学的很好是不现实的,对于找工作来说性价比也是不高的。毕竟想学好编程,语言只是最基础的一部分,还有太多技术、工具、方法、思想要学了。如果什么语言都草草地去学一下语法基础、浅尝辄止,那就相当于是在反复横跳,到头来还是不会做项目,找工作时也会吃亏。 对于刚选择计算机专业或者刚入门的同学,可以多尝试学习不同的编程语言,前端、Python、Java、C++ 都是适合的,尽快找到自己真正感兴趣的语言和方向,之后就一条路坚持往前走就好。 大家最近都在学什么编程语言?最喜欢的编程语言是什么呢?欢迎评论区留言交流~ ## 更多 💻 编程学习交流:[编程导航](https://www.codefather.cn) 📃 简历快速制作:[老鱼简历](https://laoyujianli.com) ✏️ 面试刷题神器:[面试鸭](https://mianshiya.com)
一句慢 SQL 执行 600 秒?!你咋不上天呢
大家好,我是程序员鱼皮。最近看了眼我们数据库的监控,属实被吓到了,竟然有这么多慢 SQL! <img src="https://pic.code-nav.cn/migrate_image/97394e3329ffe86ac5168e2b6e305dab.webp" alt="" width="100%" /> 其中,有一条慢 SQL 更是逆天,执行了 600 多秒?! <img src="https://pic.code-nav.cn/migrate_image/ee7586ee44db8c799d6d935eff24773b.webp" alt="" width="100%" /> 看到这,我当时就是这表情: <p align="center"> <img src="https://pic.code-nav.cn/migrate_image/eb59e089445479019d8e8c3ce2cc6d27.webp" alt="" width="216px" /></p> 如果不是我有看监控的意识,估计这种慢 SQL 还在持续拖慢我们的数据库和项目,我团队的后端朋友们还需要锻炼锻炼呀! 借这个机会,给大家分享一点儿慢 SQL 优化的知识。 ## 什么是慢 SQL? 先解释一下,慢 SQL 指的是执行时间较长的 SQL 查询或操作。它不是一个固定的时间定义,而是根据具体的应用场景和性能要求来决定。 比如你要从几万条数据中查出某一条数据,超过几百毫秒就已经算是慢 SQL 了;但如果你要向数据库插入几万条数据,可能几十秒都不算是慢 SQL。慢是相对的,没有固定的标准。 某种情况下,也可以将消耗数据库资源过多的 SQL 归纳为慢 SQL,因为它们的危害是一样的:都会增加数据的负载、降低数据库的响应速度,有些慢 SQL 还可能导致数据库的锁被占用,从而影响到整个数据库的性能。 所以一般情况下,我们要定期检查数据库中的慢 SQL。 ## 如何检测分析慢 SQL? “如何检测分析慢 SQL” 也是一道经典的面试题了,对于 MySQL 来说,可以分析 MySQL 自带的慢查询日志、通过 Explain 查看 SQL 的执行计划、通过 Profiling 分析 MySQL 查询的详细执行信息等。 不过俗话说得好,面试造火箭,工作拧螺丝。 一般有些规模的公司都会有自己的慢 SQL 收集分析平台,哪还需要自己敲命令呢?像我们鱼厂虽然是小公司,也可以用大公司提供的云服务呀!慢日志监控、告警、甚至是优化建议,都帮忙做好了,直接使用美滋滋~ <img src="https://pic.code-nav.cn/migrate_image/9a3c9cf74337e3319b838c96b7ca546c.webp" alt="" width="100%" /> ## 如何优化慢 SQL? 下面就以这句最大执行了 600 多秒的坑爹小 SQL 为例,简单聊一下怎么优化。 <img src="https://pic.code-nav.cn/migrate_image/afefce938e4c4ee829c18500d83b878c.webp" alt="" width="100%" /> 这条 SQL 的作用是查询指定权限的用户在某段时间内发送的消息记录。由于消息记录表(message_record)和用户表(user)的数据量都非常大,再加上有关联查询,导致查询缓慢。 如图,执行 600 多秒的 SQL 扫描了 600 多万行数据: <img src="https://pic.code-nav.cn/migrate_image/7d24f09426ead5f8fc76f447d980a900.webp" alt="" width="100%" /> 这句 SQL 怎么优化呢? 先看这句 SQL 的写法,其实没有什么大问题。虽然确实可以做一些小的改动,比如把 left join 改成 inner join、或者用用子查询之类的,但是优化效果并不明显。 那下一步,就是掏出数据库优化大杀器 —— **索引** ,从相关面试题的数量,就已经能感受到索引的重要性了。 <img src="https://pic.code-nav.cn/migrate_image/b899d7df7e3cbe48f8e18d57b47cbe05.webp" alt="面试鸭上的索引高频面试题" width="100%" /> 其实我用的数据库监控平台已经给出了优化建议,正是添加索引: <img src="https://pic.code-nav.cn/migrate_image/e9c2f12ee9e425e26666c7691f6dce31.webp" alt="" width="100%" /> 添加索引最简单的原则就是:需要根据什么字段查询 / 排序 / 连接 / 分组,就可以考虑增加索引。当然,具体情况具体分析,索引的选择需要根据查询条件、表的大小、数据分布等多方面考虑,比如字段值是唯一的、或者值比较分散的字段更适合加索引,而不建议给性别这种值区分度不高的字段增加索引。 所以对于这句慢 SQL,我选择给 message_record 表的 createTime 字段增加索引,然后使用 Explain 语句查看执行计划,会利用到新增的索引: <img src="https://pic.code-nav.cn/migrate_image/f0131a43620a57d98395bedabd2b918e.webp" alt="" width="100%" /> 该加的索引已经加了,但因为主表和子表的数据量就是比较大,查询还是巨慢,怎么办呢?这种情况下,可以先思考能否 **先通过业务手段解决** 。既然优化不了它,那就干掉它!思考:这句 SQL 能不能不执行?为什么要同时查出这么多用户的消息记录?是不是需求本身就不合理? 如果你把产品经理说服了,OK,愉快摸鱼。 但如果产品经理把你说服了,好吧,那就再考虑下 **能不能通过程序优化** ?比如将查询进行拆分,按照更精确的日期(比如 “天”)多次查询消息、或者指定用户 id 来避免联表查询,并且通过多线程同时并发执行查询。当然这样是否有效还是需要验证的。 还有其他的方法: 1)如果查询是需要频繁(或实时)触发的,可以使用 Redis 或内存来缓存数据,避免重复查库。 2)如果查询是定时触发的,可以考虑降低定时任务的执行频率,并且尽量错峰查询,避免这种慢 SQL 在用户使用高峰期执行。 大家看图也能够发现,我们的慢 SQL 主要就是在凌晨出现的,这是由于凌晨用户比较少,我们选择在这个时间点执行一些比较重的定时任务。 <img src="https://pic.code-nav.cn/migrate_image/81ecded3bd3ea1fdd3ec54406e9238fe.webp" alt="" width="100%" /> 如果无法通过程序优化,那么就从数据库下手吧,比如通过分库分表,将消息记录表按天拆分成多个表,提高单表查询效率;或者将已经被逻辑删除的消息移动到另一张表中进行备份,而不是继续存在于主表中,跟清空电脑上的回收站是类似的道理。但分库分表的代价就是有额外的实现成本和维护成本,建议还是慎用。 还有其他的优化方法,比如升级数据库的配置,甚至是更换一个查询性能更高的数据库(比如适用于大数据分析的 ClickHouse),不过成本有点高,反正对于我们小公司百万量级的数据量来说,还没必要考虑。 --- 以上就是本期分享,大家平时自己写项目的时候可能不太注意 SQL 的写法,觉得项目能跑就行。但其实学习 SQL 优化是很有必要的,因为等你进公司后随着业务数据量的增大,有些慢 SQL 会逐渐暴露出问题。不是不报,时候未到而已。 ## 更多 💻 编程学习交流:[编程导航](https://www.codefather.cn) 📃 简历快速制作:[老鱼简历](https://laoyujianli.com) ✏️ 面试刷题神器:[面试鸭](https://mianshiya.com)
