news 2026/9/30 3:31:59

DQL详解:从SELECT语法到执行顺序、JOIN与窗口函数优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DQL详解:从SELECT语法到执行顺序、JOIN与窗口函数优化

1. DQL是什么,为什么值得花一整篇讲清楚

SQL这门语言,入门容易,精通很难。今天要聊的是DQL(Data Query Language)——数据查询语言,翻译成人话就是SELECT那一整套东西。如果你翻过教科书,DQL通常被压缩成“SELECT查询语句”几个字,但实际干活的时候,DQL是每天的饭碗:后台接口取数、报表统计、数据分析、线上问题排查,全都靠它。所以我决定把DQL从一条SELECT展开,把语法、执行顺序、过滤、分组、连接、去重、分页、窗口函数和慢查询优化串在一起讲,争取让新手能上手,让老手也能检查自己有没有踩坑。

DQL之所以值得单独拿出来讲,是因为它是SQL里真正和“取数据”打交道的部分。你建表、插入数据、修改数据,最终目的都是为了把数据“查出来”用。业务上90%的SQL都是SELECT语句,或者是在SELECT外面包了一层。可以说,弄懂了DQL,你就掌握了SQL的绝大多数日常场景。这篇文章不聊建库建表,也不聊权限管理,只专注于“怎么把想要的数据正确、高效地捞出来”。

1.1 SQL家族里DQL的位置

SQL按功能可以粗略分成几类:DDL(Data Definition Language)负责定义结构,比如CREATE TABLE、ALTER TABLE;DML(Data Manipulation Language)负责增删改,比如INSERT、UPDATE、DELETE;DCL(Data Control Language)负责权限,比如GRANT、REVOKE;TCL(Transaction Control Language)负责事务,比如COMMIT、ROLLBACK。而DQL(Data Query Language)的主角只有一个:SELECT。

你可以把SQL理解成一座房子:DML是修改家具位置,DDL是拆墙装修,而DQL就是打开窗户去看外面的风景。其他语言都是为了“存储”和“维护”服务的,DQL是为了“取用”服务的。没有DQL,数据就是躺在硬盘里的一堆数字,毫无价值。

很多同学学SQL时喜欢先啃DDL,建一堆表,然后插入数据,接着就卡在SELECT上。其实反过来更好:先把DQL吃透,再去补充其他SQL语法,因为你会立刻知道哪些设计会让查询变慢,哪些字段应该加索引,哪些表结构适合业务。这就是为什么我坚持把DQL单独拉出来讲一篇——它是所有SQL操作的“出口”。

1.2 DQL能干什么,适合谁学

DQL能解决的问题,覆盖面比我上面说的还广。后端开发用SELECT取接口要返回的数据;数据分析师用SELECT聚合、分组、计算指标;运维和DBA用SELECT排查慢查询、看系统状态;产品经理如果懂一点DQL,也能自己拉数据验证想法。你会发现,DQL不只是程序员需要,所有和数据打交道的人都需要。

这篇文章适合三类人:第一,刚入门SQL的初学者,你可以把它当成一份带例子、带坑的查询指南;第二,会用SELECT但没系统梳理过执行顺序、索引、窗口函数的同学,可以查漏补缺;第三,准备面试、需要快速复习SQL核心知识点的人,里面的问题速查表和优化原则可以直接拿来用。我不保证你读完能成为DBA,但我保证你能少走很多弯路。

1.3 学习DQL最容易忽略的一件事

真正开始之前,我想先打一个预防针:很多人以为DQL难在语法记不住,其实不是。SQL的语法来来回回就那么几十个关键字,真正难的是“想清楚你要什么”。你和别人用同一个表,同一个查询目的,快的人写出的SQL步骤少、条件准、效率高;慢的人写一堆嵌套子查询,结果还不对。所以这篇文章除了讲语法,还会反复强调“先理清逻辑再写代码”。

比如说,你想查“每个部门的平均薪资,只保留平均薪资大于8000的部门”。这个需求拆开就是三件事:按部门分组、计算平均薪资、过滤分组结果。如果你一上来就写SELECT,很容易把WHERE和HAVING混在一起。我会在后面的章节里把这类拆解思路反复讲,让写SELECT变成一种条件反射。

2. 从五子句到执行顺序:SELECT的骨架

2.1 先把语法骨架写出来

标准的SELECT语句,不管在MySQL、PostgreSQL还是SQL Server里,核心框架基本都是五子句:

SELECT 列名或表达式 FROM 表名 WHERE 过滤条件 GROUP BY 分组字段 HAVING 分组后的过滤条件 ORDER BY 排序字段 LIMIT 行数限制;

其中GROUP BY和HAVING是可选的,WHERE和ORDER BY也是可选的。最精简的查询甚至可以只有SELECT和FROM,比如SELECT 1;。但真正业务里,我们几乎总是至少用到WHERE和ORDER BY。

这里要插一句:不同数据库分页写法不一样。MySQL用LIMIT n OFFSET m,PostgreSQL也支持LIMIT,但SQL Server历史上用OFFSET FETCH NEXT或者TOP,Oracle以前用ROWNUM。这是DQL里少见的“方言”差异,后面我会单独讲分页时的坑。

2.2 执行顺序:为什么WHERE不能用的条件,HAVING却能用

书写顺序和逻辑执行顺序不一样,这是DQL新手第一个绕不过去的坎。SQL这条查询的逻辑执行顺序大体是:

  • FROM:先确定从哪张表取数据
  • WHERE:对原始数据进行行级过滤
  • GROUP BY:按字段将数据分组
  • 聚合函数:在组内计算
  • HAVING:对分组后的结果进行过滤
  • SELECT:选取要输出的列或表达式
  • ORDER BY:对最终结果排序
  • LIMIT:截取部分行

这个顺序决定了几个高频问题。为什么WHERE里不能直接用聚合函数?因为WHERE在GROUP BY之前执行,还没开始聚合,你没法用COUNT(*) > 5去过滤。为什么SELECT里取的别名不能在WHERE里用?因为SELECT别名是最后才生成的,WHERE执行时它还不存在。但ORDER BY可以用别名,因为排序发生在SELECT之后。这就是为什么我总说,执行顺序才是SQL的“底层逻辑”。

2.3 一条查询的完整生命周期:跟着数据走一遍

就拿“查询订单表里金额大于100元的订单,按用户分组,统计每个用户的订单数和总金额,只保留订单数超过2个的用户,最后按总金额排序”这个需求来演示。

SELECT user_id, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders WHERE amount > 100 GROUP BY user_id HAVING COUNT(*) > 2 ORDER BY total_amount DESC;

它实际干的事是这样的:先从orders表里把amount>100的行筛出来;然后按user_id分组;接着对每个分组执行COUNT和SUM聚合;再通过HAVING把分组结果里订单数不到2的组丢掉;最后SELECT输出列,并按总金额降序排序。如果你只记住了书写的五子句顺序,面对报错和性能问题时会一头雾水,但按这个生命周期去理解,每一步都清清楚楚。

我习惯在调试复杂查询时,先去掉GROUP BY和HAVING,只跑WHERE,看看过滤后的原始数据长什么样;再逐渐加上聚合和排序。这样能很清楚地定位是数据的问题还是SQL写错了。

3. WHERE条件过滤:写错情愿不写

3.1 五种条件类型与常用写法

WHERE是DQL里最常用、也最容易出错的子句。它支持五种典型过滤:

  • 等值条件:WHERE status = 'paid'
  • 范围条件:WHERE create_time BETWEEN '2024-01-01' AND '2024-01-31'
  • 模糊条件:WHERE username LIKE '张%'
  • 空值条件:WHERE remark IS NULL或者IS NOT NULL
  • 集合条件:WHERE city IN ('北京', '上海')

这里最基础也最讨厌的是空值。NULL在SQL里不是一个值,而是“未知”的状态,所以不能用= NULL判断,必须用IS NULL。很多新手的查询结果莫名少了几行,就是因为在空值判断上用了等号。

3.2 条件组合与优先级:AND、OR的顺序别靠“感觉”

多个条件组合时,优先级从高到低是:括号 > NOT > AND > OR。换句话说,不加括号时,WHERE a = 1 OR b = 2 AND c = 3会被解析成a = 1 OR (b = 2 AND c = 3),和你脑子里想的很可能不一样。

我的习惯是:只要表达式里同时出现AND和OR,就老老实实加括号。别觉得自己记得住优先级,三个月后再回来读这段SQL,加括号的版本是给未来的自己减负。举个例子,想查“VIP用户,或者广州用户”的下单记录,正确写法是WHERE (is_vip = 1 OR city = '广州'),如果漏了括号,条件就变成了“VIP且广州”——这个bug只看数据很难发现。

3.3 三个“看似正常但结果不对”的过滤坑

我挑三个实战高频坑讲,每一个都让某条生产SQL悄悄出过问题。

第一个坑是WHERE里写函数。例如查2024年的订单,很多人写成WHERE YEAR(create_time) = 2024,这样功能上没错,但会让create_time上的索引失效,扫全表。更推荐写成WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01',这种范围写法既能利用索引,语义也更清晰。

第二个坑是隐式类型转换。假设user_id在表里是VARCHAR,但你传入的是数字,比如WHERE user_id = 123,数据库会把字段隐式转成数字比较,结果索引失效,还可能带来类型转换的精度问题。最好的办法是参数类型和字段类型保持一致。

第三个坑是在可空字段上使用NOT IN。如果子查询返回的结果里包含NULL,NOT IN会匹配不到任何行,因为NOT IN遇到NULL时,结果不确定。遇到这种情况,要么用NOT EXISTS,要么先过滤掉NULL。这个坑特别隐蔽,我见过不止一次因此导致报表少数据的案例。

4. 聚合与分组:从COUNT到HAVING

4.1 聚合函数不是你想的那么简单

聚合函数包括COUNT、SUM、AVG、MAX、MIN。它们最大的特点是:把多行压成一行。使用时要留意几件事:

  • COUNT(*)统计行数,COUNT(列名)只统计该列非NULL的行数。语义完全不同。
  • SUM和AVG会忽略NULL,但如果全是NULL,SUM返回NULL而不是0。
  • MAX和MIN不会忽略NULL,但结果通常不受影响。

举个例子,统计订单表行数COUNT(*),和统计有实际支付金额的订单数COUNT(pay_amount),结果经常差很多。想统计“有多少用户填了手机号”,就该用COUNT(phone),而不是COUNT(*)。很多人测试数据相同,换到生产环境数据一有NULL,结果立刻出错。

4.2 GROUP BY:分组之后,世界就不一样了

GROUP BY的作用是“把相同的值并成一组”,组内可以用聚合函数计算汇总指标。比如按部门统计人数:SELECT dept_id, COUNT(*) FROM employees GROUP BY dept_id。

一个核心规则:SELECT的列,要么出现在GROUP BY里,要么被聚合函数包裹。比如SELECT user_id, status, COUNT(*) FROM orders GROUP BY user_id——在严格的SQL模式下会直接报错,因为status既不在分组字段里,也没有被聚合,数据库不知道要展示哪一行的status。MySQL某些版本默认关闭了ONLY_FULL_GROUP_BY,不会报错,但返回的status是组内随机值,这是非常危险的“宽松模式”。

4.3 HAVING与WHERE的职责边界

很多新手分不清WHERE和HAVING,其实按执行顺序理解就很清晰:WHERE在分组前过滤原始行,HAVING在分组后过滤整组。WHERE里不能写聚合函数,HAVING里也不能写未聚合的普通字段(除非这个字段也出现在GROUP BY里,但那样语义会很绕)。

实际业务里,我习惯把能做掉的过滤都放在WHERE,这样参与分组的数据量更小,聚合更快。HAVING只保留那些“必须分组后才知道”的条件,比如HAVING COUNT(*) > 5。如果你既能用WHERE又能用HAVING,优先WHERE,这是效率习惯。

5. JOIN多表连接:数据关系的基本功

5.1 三种JOIN的区别,一张表说清楚

DQL里真正让人头疼的地方,多表连接绝对算一个。最基本的三种JOIN是:

JOIN类型返回内容
INNER JOIN只返回两边都能匹配上的行
LEFT JOIN返回左表全部行,右表没有匹配则补NULL
RIGHT JOIN返回右表全部行,左表没有匹配则补NULL
FULL OUTER JOIN两边全部返回,没有匹配的补NULL

很多教程喜欢画两个圆圈做交集,但我更喜欢用“订单-用户”来类比:INNER JOIN是只留下有下单用户和有效订单;LEFT JOIN是所有用户都出现,哪怕没下过单;FULL OUTER JOIN是用户和订单两边都完整保留,像两个团队拉群对名单,只要在任意一边就出现。

5.2 ON与WHERE的分工:写错位置结果就变了

连接条件写在ON里,过滤条件写在WHERE里,这句话每个DQL学习者都该刻在脑门上。尤其在做LEFT JOIN时,如果你把右表的过滤条件写在WHERE里,比如LEFT JOIN orders ON users.id = orders.user_id WHERE orders.amount > 100,那么没下过单的用户因为orders.amount是NULL,会被WHERE筛掉,左连接就悄悄变成了内连接。

正确做法是:如果想让“用户全部保留,只统计满足金额条件的订单”,过滤条件应该写在ON里:LEFT JOIN orders ON users.id = orders.user_id AND orders.amount > 100。这个区别是生产环境常见的逻辑bug来源,查的时候尤其要小心。

5.3 避坑:笛卡尔积和重复行

JOIN最常见的两个坑:一个是忘记写ON,或者ON条件写错,产生笛卡尔积——两张表数据行数相乘,数据爆炸;另一个是关联字段有重复,一边有重复行,JOIN后就会翻倍。

我曾经处理过一个案例:用户表和订单表关联,一个用户有多张订单,LEFT JOIN之后,用户信息被迫重复出现多行。如果直接在这上面COUNT,结果全是错的。所以做多表JOIN之前,先单独看一眼两张表的关联字段是否有重复,再决定是否需要先聚合再JOIN,或者用DISTINCT去重。宁可多写两步,也不要让数据悄悄翻倍。

6. 排序、去重与分页:查询收尾三板斧

6.1 ORDER BY排序的高级细节

排序看着简单:ORDER BY col ASC/DESC。但有几个细节值得注意。首先,多字段排序时,从左到右生效:ORDER BY status ASC, create_time DESC意思是status升序,status相同再看create_time降序。其次,NULL的排位在不同数据库里不一致,MySQL里NULL默认排在最前面,SQL Server里通常排在最后,如果业务对排序有严格要求,可以用ORDER BY col IS NULL, col来显式控制。

中文字段排序也容易踩坑。数据库默认排序可能按二进制编码,结果不是你期望的拼音顺序。需要按拼音排时,得使用特定的collation或函数,比如MySQL里ORDER BY CONVERT(name USING gbk)。这些细节在报表场景里非常影响用户体验。

6.2 DISTINCT去重:边界和陷阱

SELECT DISTINCT col1, col2 FROM table会按多列的组合值去重,而不是只对col1去重。比如你想知道表里有多少个不同的城市,但DISTINCT city, province会按“城市+省份”的组合去重,可能返回多行。如果只想对某个字段去重,就要用COUNT(DISTINCT city)这样的聚合写法。

DISTINCT的另一个问题是,它会把所有查出来的列组合在一起做去重,等于额外做了一次排序,数据量大时性能并不好。如果表本身有唯一键,或者你只需要一个粗略的数字,DISTINCT能用;但如果要精确去重的明细数据,一定要想清楚是对“单列”还是“组合列”去重。还有一点,DISTINCT对NULL的处理也容易迷惑,多个NULL行在DISTINCT后只保留一行,但COUNT(DISTINCT col)不计NULL,这两者并不矛盾,却常常让人疑惑。

6.3 深分页的优化:别再用OFFSET翻到底

MySQL的经典分页写法是LIMIT 10 OFFSET 10000,意思是跳过头10000行,取10行。听起来没问题,但随着页数变大,OFFSET越来越大,数据库还是要从头数过前面所有行,速度和资源消耗都直线上升。这就是为什么很多后台管理页面翻到第100页时慢到无法忍受。

更推荐“键集分页”:记住上一页最后一条数据的排序字段值,下一页直接WHERE id > 上一页最大id ORDER BY id ASC LIMIT 10。这种方式不需要跳行,走索引就能快速定位,翻页越深优势越明显。唯一的代价是前端不能随便跳页,但对大多数“瀑布流”和“列表加载更多”场景完全够用。

7. 窗口函数:进阶必学的查询利器

7.1 窗口函数和GROUP BY的本质区别

窗口函数(也叫分析函数)是DQL里非常实用的进阶语法。它同样可以分组、排序、聚合,但最大的区别是:窗口函数不会把多行合并成一行,而是“在每一行旁边多算一列结果”。用OVER(PARTITION BY xxx ORDER BY yyy)来指定窗口范围。

举个例子,想算每一个员工在自己部门内的薪资排名,用GROUP BY根本做不到,因为GROUP BY会把部门内多行压成一行,你失去每个员工的明细。而窗口函数可以做到:SELECT name, dept_id, salary, RANK() OVER(PARTITION BY dept_id ORDER BY salary DESC) AS rk FROM employees,每一行还在,但多了一列部门排名。

7.2 三个高频场景:排名、累计、移动平均

窗口函数最常见的场景有三个。

排名:ROW_NUMBER()按顺序给每一行一个唯一编号;RANK()遇到相同值会跳号;DENSE_RANK()遇到相同值不跳号。想取每个部门薪资前三,就在外面包一层子查询,然后WHERE rk <= 3。

累计值:SUM(amount) OVER(PARTITION BY user_id ORDER BY create_time)可以算出每个用户订单金额从最早的订单累加到当前的累计值,做“进度条”或“余额变化”时非常好用。

移动平均:AVG(price) OVER(ORDER BY create_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)可以计算最近三笔订单的平均价格,用来做平滑分析。这种计算用GROUP BY写会极其痛苦,窗口函数一分钟就搞定。

7.3 窗口函数的使用注意点

窗口函数也有自己的坑。第一,窗口结果是在WHERE和GROUP BY之后计算的,所以你不能直接在WHERE里用rk = 1来筛选,必须套一层子查询。第二,PARTITION BY和ORDER BY在OVER里是两码事:PARTITION BY负责分区,ORDER BY负责窗口内的排序。只写ORDER BY不写PARTITION,表示对全表排序后的窗口,别搞混。第三,不同数据库窗口函数的语法大致相同,但函数名可能会有差异,例如有些数据库用RANK(),有些老版本还不支持,写之前一定确认版本。

8. 慢查询与索引优化:DQL的隐形战斗力

8.1 如何定位慢SQL

DQL写对了,结果正确了,还不够。线上查询如果几秒钟才返回,业务就崩了。定位慢SQL的第一习惯是看它的执行计划。MySQL里在查询前加EXPLAIN,SQL Server里是“显示执行计划”,PostgreSQL先用EXPLAIN ANALYZE。重点看几个列:

  • type:从好到差大致是const、eq_ref、ref、range、index、ALL
  • possible_keys:可能用到的索引
  • key:真正用到的索引
  • rows:预估扫描行数

如果一个查询的type是ALL(全表扫描),且rows特别大,那它就是慢查询的头号嫌疑。我的顺序是:先看rows大小,再看key是否用到索引,然后针对性地改写SQL或加索引。

8.2 索引失效的常见场景

索引让查询变快,但也很容易失效。常见的失效场景包括:

  • WHERE字段上使用了函数或运算,比如WHERE YEAR(date)=2024
  • 隐式类型转换,比如字符串字段和数字比较
  • 使用LIKE但通配符在开头,比如LIKE '%关键字'
  • OR连接的多个条件里,只要有一个字段没有索引,优化器可能放弃索引
  • 对可空字段做IS NOT NULL,某些数据库下也可能影响

复合索引还要注意最左前缀原则:比如建立了(a, b, c)索引,查询条件里如果跳过a直接WHERE b=2,这个复合索引就用不上。所以建复合索引时,字段顺序往往决定了它能服务多少条查询。

8.3 几条立竿见影的优化原则

在深入调优之前,先做这三件事,往往就能解决大半慢SQL。

第一,不要SELECT *。只查需要的列,既减少网络传输,也可能让覆盖索引发挥作用。第二,控制结果集大小,线上查询尽量加LIMIT或者条件限制,别让一次性吐几十万行。第三,减少不必要的子查询和嵌套,能用JOIN就用JOIN,但连接顺序不对时,优先检查关联字段是否有索引。

当然,优化不是越复杂越好。我以前见过有人为了避开一条子查询,硬是写了三层JOIN,结果更慢。最优方案永远是“先看懂执行计划再动手”,别靠感觉。

9. 常见问题与排查技巧实录

9.1 问题速查表

现象可能原因解决方向
查询结果行数变少HAVING误用、JOIN条件把左表数据过滤了检查过滤条件位置,确认是WHERE还是ON
查询结果重复关联字段有重复先查关联字段是否唯一,必要时先聚合再JOIN
COUNT统计不准COUNT(列)忽略NULL、JOIN产生笛卡尔积确认是否要用COUNT(*),JOIN前查重复
GROUP BY报错非聚合列没分组要么加进GROUP BY,要么聚合
速度越来越慢深分页OFFSET太大改用键集分页,或加索引
使用NOT IN没结果子查询含NULL改用NOT EXISTS或过滤NULL
WHERE里用了函数导致慢索引失效改为范围条件

这张表我实际工作中经常翻。很多问题不是语法不会,而是踩过一次坑后都长记性了。

9.2 几条实操心得

最后分享几条我个人的习惯,不一定标准,但确实帮忙避了很多坑。

第一,写查询之前,先在草稿纸上写下“我需要哪张表、要哪几个列、过滤哪几个条件、需不需要分组”。哪怕写一行注释也好,这个拆解动作能让SQL清晰很多。第二,尽量把查询分步验证。复杂查询里,先用一个子查询验证中间结果,确认部分数据正确后再继续组装。配合临时表或CTE,可读性会大幅提高,后续维护也容易。第三,线上查询宁可保守一点,加LIMIT,加明确条件,也不要图一口气查完所有数据。第四,学会看执行计划之后,每次写完慢SQL都顺手看一眼,长此以往,你对“哪段SQL应该走哪种索引”会形成直觉。

DQL的核心从来不是背语法,而是保持对数据的敏感——知道每一行结果是怎么来的,知道每一条条件会怎样影响最终输出。这也是我工作多年下来最深的一点体会。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 3:31:59

从伐木工到二分答案:单调性、整数二分与溢出边界

第一次在题单里翻到这道编号 1908 的"伐木工"&#xff0c;我下意识觉得这是道模拟题——题目描述那么直白&#xff0c;照着砍一遍不就行了&#xff1f;可等我把数据范围那一栏读完&#xff0c;这个念头立刻被打消了。树的数量动辄上万&#xff0c;单棵树的高度又能到…

作者头像 李华
网站建设 2026/9/30 3:31:27

PyQt5桌面天气应用开发实战:从API接入到打包发布全流程

最近各种工具都在往桌面端挤&#xff0c;AI 编程助手出桌面版、大模型客户端出桌面版&#xff0c;GitHub Desktop、Docker Desktop 这些老牌工具更不用说了。这股风潮带火了一个问题&#xff1a;桌面端软件到底怎么做&#xff1f;我正好花两个晚上搓了一个桌面版天气预报应用&a…

作者头像 李华
网站建设 2026/9/30 3:31:11

Excel手搓概率纸图:中位秩、NORM.S.INV与B10寿命计算

手里的数据就那么几组&#xff0c;想看看它们是不是服从正态分布&#xff0c;或者想估个 B10 寿命&#xff0c;最直观的干法是画一张概率纸图。可现实是&#xff0c;概率纸这玩意儿现在很难买到&#xff0c;就算买到&#xff0c;手描点也基本靠肉眼&#xff0c;误差大得离谱。所…

作者头像 李华
网站建设 2026/9/30 3:30:47

全覆盖路径规划CCPP实战:从Matlab仿真到真实机器人部署

1. 为什么“全覆盖”不是画个圈就完事&#xff1f;从扫地机器人卡在沙发底说起你有没有遇到过这样的场景&#xff1a;刚买回来的扫地机器人&#xff0c;标榜“全屋覆盖”&#xff0c;结果跑了一小时&#xff0c;厨房油污区没扫、沙发底下积灰照旧、地毯边缘反复打滑——它确实“…

作者头像 李华
网站建设 2026/9/30 3:30:17

从零开始:Flutter 三方库 pmtiles 的鸿蒙化适配全记录

从零开始&#xff1a;Flutter 三方库 pmtiles 的鸿蒙化适配全记录聊到地图开发&#xff0c;大家第一反应大多是高德、百度或者 Mapbox&#xff0c;处理在线瓦片也顺手&#xff0c;但一旦遇到弱网、本地部署、海量数据检索这些场景&#xff0c;整个思路就得换个方向。pmtiles 这…

作者头像 李华
网站建设 2026/9/30 3:30:15

基于BPSO的电力系统PMU最优配置方法详解

1. 项目概述&#xff1a;OPP问题到底在解决什么做电力系统的人对PMU应该都不陌生。相量测量单元&#xff08;PMU&#xff09;是广域测量系统&#xff08;WAMS&#xff09;的核心设备&#xff0c;能以微秒级时标同步测量母线电压相量和支路电流相量。但PMU本身加上配套的授时、通…

作者头像 李华