Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions prqlc/prqlc/src/sql/gen_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,19 @@ fn translate_select_pipeline(
})
}
if order_by.is_empty() {
// When DISTINCT is used, MSSQL requires ORDER BY items to appear
// in the SELECT list. Use the first column from the projection
// instead of (SELECT NULL).
let order_expr = is_distinct
.then(|| first_expr_from_projection(&projection))
.flatten()
.unwrap_or_else(|| {
sql_ast::Expr::Value(
sql_ast::Value::Placeholder("(SELECT NULL)".to_string()).into(),
)
});
order_by.push(sql_ast::OrderByExpr {
expr: sql_ast::Expr::Value(
sql_ast::Value::Placeholder("(SELECT NULL)".to_string()).into(),
),
expr: order_expr,
options: sqlparser::ast::OrderByOptions {
asc: None,
nulls_first: None,
Expand Down Expand Up @@ -726,6 +735,22 @@ fn count_tables(transforms: &[Transform]) -> usize {

count
}

/// Extract the first expression from a projection for use in ORDER BY.
/// Returns None if the projection is empty or only contains wildcards.
fn first_expr_from_projection(projection: &[SelectItem]) -> Option<sql_ast::Expr> {
for item in projection {
match item {
SelectItem::UnnamedExpr(expr) => return Some(expr.clone()),
SelectItem::ExprWithAlias { alias, .. } => {
return Some(sql_ast::Expr::Identifier(alias.clone()));
}
SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _) => continue,
}
}
None
}

#[cfg(test)]
mod test {
use insta::assert_snapshot;
Expand Down
23 changes: 23 additions & 0 deletions prqlc/prqlc/tests/integration/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2365,6 +2365,29 @@ fn test_take_mssql() {
");
}

#[test]
fn test_mssql_distinct_fetch() {
// Issue #5628: MSSQL requires ORDER BY items to appear in SELECT list when DISTINCT is used.
// Using (SELECT NULL) for ORDER BY with DISTINCT is invalid in MSSQL.
assert_snapshot!((compile(r#"
prql target:sql.mssql

from t
take 100
group {this.`District`} (take 1)
select {this.`District`}
"#).unwrap()), @r###"
SELECT
DISTINCT "District"
FROM
t
ORDER BY
"District" OFFSET 0 ROWS
FETCH FIRST
100 ROWS ONLY
"###);
}

#[test]
fn test_distinct_01() {
// window functions cannot materialize into where statement: CTE is needed
Expand Down