with user_city_pay as(
	select
		ui.user_id ,
		ui.city ,
		sum(oi.total_amount ) as total_pay
	from user_info ui join order_info oi on ui.user_id = oi.user_id 
	where oi.order_status = 2
	group by ui.city ,ui.user_id 
)
SELECT *
FROM (
	select 
		*,
		rank() over (partition by city order by total_pay desc) as rnk
	from user_city_pay 
) t
where rnk = 1;

关于倒数第二行的t,可以换成其他,或者abc等任意别名。但必须有别名。
所有放在 FROM 后面的子查询(专业名叫:派生表),必须指定一个别名,不写直接报错!
这是 MySQL 语法死规定,和逻辑无关,和函数无关,纯语法要求!
在这种嵌套查询里,子查询结果就像一个临时的 “表”,但数据库需要一个名字来识别它。如果没有别名,当你在外部查询对这个子查询结果进行操作(比如筛选)时,数据库无法确定操作对象,所以别名是必须的,用来给这个临时结果集一个 “身份标识” 。

关于2个select*的问题
SQL 的执行顺序
这是新手最容易踩的坑,我用最简版执行顺序告诉你为什么报错:
先执行:FROM / JOIN → WHERE → GROUP BY → 聚合计算
最后执行:SELECT(计算窗口函数、定义别名)→ ORDER BY

关于rank函数的用法
rank() OVER (
PARTITION BY 分组字段 -- 可选:按字段分组排名(如按班级/科目分组)
ORDER BY 排序字段 DESC -- 必选:指定排序规则(降序DESC/升序ASC)
) AS 别名

关于with函数的用法
WITH [RECURSIVE]
cte_name1 [(col1, col2, ...)] AS (subquery1), -- 第一个CTE
cte_name2 [(col1, col2, ...)] AS (subquery2), -- 多个CTE用逗号分隔
...
SELECT|INSERT|UPDATE|DELETE ... -- 主查询/操作,引用CTE

例:

WITH
  -- 第一个CTE:客户订单统计
  customer_orders AS (
    SELECT customer_id, COUNT(*) AS order_count, SUM(amount) AS total_spent
    FROM orders
    GROUP BY customer_id
  ),
  -- 第二个CTE:筛选高价值客户(订单金额>1000)
  high_value_customers AS (
    SELECT customer_id, order_count, total_spent
    FROM customer_orders
    WHERE total_spent > 1000
  )
-- 主查询:关联客户表获取客户详细信息
SELECT c.customer_name, h.order_count, h.total_spent
FROM customers c
JOIN high_value_customers h ON c.customer_id = h.customer_id;