在SQL中,SELECT * FROM table
是最基础的查询之一,星号(*)是一个通配符,表示"选择所有列"。虽然通配符查询看起来简单,但在解析器中需要特殊处理。下面详细介绍我们如何实现这一常用功能。
星号与普通列名有本质区别:
id
、name
)特性 | 普通列名 | 星号 |
---|---|---|
语法标记 | 标识符(IDENTIFIER) | 特殊字符(ASTERISK) |
AST节点 | Identifier | AsteriskExpression |
解析方法 | parseIdentifier() | parseAsterisk() |
语义验证 | 需要验证列存在性 | 不需要验证(表示所有列) |
执行时处理 | 读取单个列 | 读取所有列 |
为了支持星号查询,我们需要修改解析器的几个关键部分:
首先,创建一个专用的AST节点类型表示星号:
// AsteriskExpression 表示SQL中的星号(*),用于表示选择所有列
type AsteriskExpression struct{}
func (a *AsteriskExpression) expressionNode() {}
func (a *AsteriskExpression) TokenLiteral() string { return "*" }
func (a *AsteriskExpression) String() string { return "*" }
这个简单的结构体实现了 Expression
接口,可以作为SELECT语句的列表达式。
在解析器初始化时,为星号符号注册专门的解析函数:
// 初始化解析器
func NewParser(l *lexer.Lexer) *Parser {
p := &Parser{
lexer: l,
errors: []string{},
}
// 注册前缀解析函数
p.prefixParseFns = make(map[lexer.TokenType]prefixParseFn)
// ... 其他注册
p.registerPrefix(lexer.ASTERISK, p.parseAsterisk) // 添加对*的解析支持
// ... 其他初始化
return p
}
// parseAsterisk 解析SELECT语句中的星号(*),表示选择所有列
func (p *Parser) parseAsterisk() (ast.Expression, error) {
return &ast.AsteriskExpression{}, nil
}
这个函数非常简单,只需创建并返回一个 AsteriskExpression
实例。
当查询执行器遇到 AsteriskExpression
时,需要:
// 伪代码:执行器如何处理星号
func executeSelect(stmt *ast.SelectStatement, db *Database) *ResultSet {
// ...
// 处理列选择
var columns []Column
for _, colExpr := range stmt.Columns {
switch expr := colExpr.(type) {
case *ast.AsteriskExpression:
// 星号表达式:获取表的所有列
allColumns := db.GetAllColumns(stmt.TableName)
columns = append(columns, allColumns...)
case *ast.Identifier:
// 普通列名:获取单个列
column := db.GetColumn(stmt.TableName, expr.Value)
columns = append(columns, column)
// ... 其他表达式类型
}
}
// ... 继续执行查询
}
对于 SELECT * FROM users WHERE age > 18;
,完整的AST树结构如下:
表格别名与星号结合使用时,如 SELECT u.* FROM users u
,需要特殊处理:
在这种情况下,我们需要一个特殊的AST节点 QualifiedAsteriskExpression
来表示带表格别名的星号:
// QualifiedAsteriskExpression 表示带表格别名的星号,如 t.*
type QualifiedAsteriskExpression struct {
TablePrefix string // 表前缀,如 t
}
func (q *QualifiedAsteriskExpression) expressionNode() {}
func (q *QualifiedAsteriskExpression) TokenLiteral() string { return q.TablePrefix + ".*" }
func (q *QualifiedAsteriskExpression) String() string { return q.TablePrefix + ".*" }
在多表连接中,星号会引入列名冲突问题:
在多表连接的例子中,当使用星号时:
users
表可能有 id
, name
, email
列orders
表可能有 id
, user_id
, product_id
列id
列,会导致名称冲突u.id
, o.id
的完全限定名SQL还允许星号与特定列的混合使用,如 SELECT *, extra_column FROM table
:
这种情况下,执行器需要:
星号查询虽然方便,但存在一些性能和维护方面的注意事项:
使用星号的情况 | 使用具体列名的情况 |
---|---|
代码简洁 | 代码明确表达了需要的数据 |
表结构变更时自动获取新列 | 不会因表结构变更意外获取新列 |
可能获取不需要的数据 | 只获取必要数据 |
列顺序依赖表定义 | 列顺序由查询指定 |
列重命名可能导致代码错误 | 列重命名会导致明确的错误 |
在数据探索阶段,星号查询非常实用:
-- 快速了解表结构
SELECT * FROM users LIMIT 10;
-- 调试连接查询
SELECT * FROM orders o JOIN users u ON o.user_id = u.id LIMIT 5;
星号有时与聚合函数结合使用:
-- 计算总行数
SELECT COUNT(*) FROM users WHERE status = 'active';
-- 注意:这里的*是特殊语法,不同于列选择中的*
这种情况下,COUNT(*)
是一种特殊语法,表示"计算行数",而不是"计算所有列"。在解析器中需要特殊处理这种情况。
星号通配符是SQL中最基础也是最常用的功能之一。尽管语法简单,但在实现上需要特殊处理,从词法分析、语法解析到查询执行的各个环节都有其独特之处。
通过本文介绍的实现方式,我们的SQL解析器现在完全支持通配符查询,不仅处理了基本的 SELECT * FROM table
形式,还能正确解析表别名限定的星号和多表连接中的星号用法。这使我们的解析器功能更加完整和实用,为下一步开发查询执行引擎奠定了基础。
在实际使用中,应根据具体场景权衡是否使用星号查询,以在便利性和性能之间取得平衡。