Repository: Azure/data-api-builder
Pull request #2631: Improved N-1 join query performance for DW SQL
Author: login: lingxiao-microsoft
Created: 2025-03-21T23:41:37Z | Merged: 2025-04-02T18:58:19Z

## Why make this change?
- Back 1 year ago, as DW SQL does not support `JSON PATH` for converting the execution to json format. Hence we had to use `STRING_AGG` as the workaround. 

- Recently, we've noticed `JSON PATH` is now supported for outer query in DW, and we can use `JSON_OBJECT` + `JSON_PATH` to address the json conversion for N-to-1 relations, which can optimize the performance. 

- For N-N relations, we're still looking into any resolutions as `JSON_ARRAYAGG` does not provide much performance improvements. 
- 
- For other scenarios when joins are not needed for a simple SELECT, we will `JSON PATH` instead of `STRING_AGG` for better performance.


## What is this change?

This PR covers
1. Introduced a feature flag to safeguard the changes, the feature flag is default as False when not provided to avoid any regressions. It will be removed once the changes are validated in production with scoped audiences. 
2. For DW query builder, use `JSON_OBJECT` to generate the columns for sub-queries and applied `JSON PATH` to handle outer query, which fully replace the need of `STRING_AGG`.
3. Also, for non-join queries (in which we don't need to handle the relations), used `JSON PATH` to replace the need of `STRING_AGG` for better performance as well. This will have impact on aggregations, non-join queries and pagination. 
4. Added some helper functions into the unit tests module, which aims to compare the results from GraphQL & DB engine easily for deeply nested queries. 

## How was this tested?

- [x] Unit Tests
  - As this change does not introduce new scenarios, so mostly added some new test cases to get more coverage when M-M / M-1 join queries are needed.

- [x] Integration Tests

### Manual Testing - Join Scenarios
#### Query 1-1 relation - As expected, optimization applied
![image](https://github.com/user-attachments/assets/514626e6-bcf5-4c37-a82e-0de13d947fd7)
![image](https://github.com/user-attachments/assets/a1921dfb-689b-496d-9f2e-520944c44ac8)

#### Query N-1 relation - As expected, optimization applied
![image](https://github.com/user-attachments/assets/86b6e25d-6f1a-48de-8ac5-f0e0ea6dd555)
![image](https://github.com/user-attachments/assets/8f9d47ff-6d4a-4207-843f-fdcf7d45a3a3)


#### Query 1-N Relation - As expected, optimization not applied
![image](https://github.com/user-attachments/assets/bea0d845-2e74-4a37-9410-9133f52fcdc5)
![image](https://github.com/user-attachments/assets/bd8d3a78-3d12-4909-abcc-4f6957e84564)

#### Query N-N Relation - As expected, optimization not applied
![image](https://github.com/user-attachments/assets/8e5777c1-34ba-433e-a7b2-4c0a806533ea)
![image](https://github.com/user-attachments/assets/5660ec06-e63b-4db7-be4a-9dfbd828c95a)

### Other Scenarios
We've applied the `JSON PATH` when there is no join in the query to replace the `STRING_AGG` for better performance. 

#### Aggregation
![image](https://github.com/user-attachments/assets/0adc0f75-92a6-418e-a756-22d349baec94)

#### Non-Join Query
![image](https://github.com/user-attachments/assets/8b0f799a-a424-44cb-975d-d346eb1ae5b0)


#### Pagination
- N to 1, total items: 3
![image](https://github.com/user-attachments/assets/b7ac611b-af4f-46de-b023-24d9f77acdae)
![image](https://github.com/user-attachments/assets/4b7a028d-255b-41da-b085-b7197bfef181)

- N to N
![image](https://github.com/user-attachments/assets/ec611346-5fcf-47a4-894b-454547fc99a6)
![image](https://github.com/user-attachments/assets/9714bdac-6467-492b-b15f-02a2cb2939c4)
![image](https://github.com/user-attachments/assets/a193b95d-d37e-4879-b43b-6402d758e3bf)




Diff:
diff --git a/src/Cli.Tests/ModuleInitializer.cs b/src/Cli.Tests/ModuleInitializer.cs
index 2db365fd02..4d408e4917 100644
--- a/src/Cli.Tests/ModuleInitializer.cs
+++ b/src/Cli.Tests/ModuleInitializer.cs
@@ -79,6 +79,10 @@ public static void Init()
         VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableAggregation);
         // Ignore the EnableAggregation as that's unimportant from a test standpoint.
         VerifierSettings.IgnoreMember<GraphQLRuntimeOptions>(options => options.EnableAggregation);
+        // Ignore the EnableDwNto1JoinOpt as that's unimportant from a test standpoint.
+        VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableDwNto1JoinOpt);
+        // Ignore the FeatureFlags as that's unimportant from a test standpoint.
+        VerifierSettings.IgnoreMember<GraphQLRuntimeOptions>(options => options.FeatureFlags);
         // Ignore the JSON schema path as that's unimportant from a test standpoint.
         VerifierSettings.IgnoreMember<RuntimeConfig>(config => config.Schema);
         // Ignore the message as that's not serialized in our config file anyway.
diff --git a/src/Config/ObjectModel/FeatureFlags.cs b/src/Config/ObjectModel/FeatureFlags.cs
new file mode 100644
index 0000000000..11436e67a0
--- /dev/null
+++ b/src/Config/ObjectModel/FeatureFlags.cs
@@ -0,0 +1,19 @@
+namespace Azure.DataApiBuilder.Config.ObjectModel
+{
+    /// <summary>
+    /// The class is used for ephemeral feature flags to turn on/off features in development
+    /// </summary>
+    public class FeatureFlags
+    {
+        /// <summary>
+        /// By default EnableDwNto1JoinQueryOptimization is disabled
+        /// We should change the default as True once got more confidence with the fix
+        /// </summary>
+        public bool EnableDwNto1JoinQueryOptimization { get; set; }
+
+        public FeatureFlags()
+        {
+            this.EnableDwNto1JoinQueryOptimization = false;
+        }
+    }
+}
diff --git a/src/Config/ObjectModel/GraphQLRuntimeOptions.cs b/src/Config/ObjectModel/GraphQLRuntimeOptions.cs
index d842eebfa6..64c5e58980 100644
--- a/src/Config/ObjectModel/GraphQLRuntimeOptions.cs
+++ b/src/Config/ObjectModel/GraphQLRuntimeOptions.cs
@@ -11,7 +11,8 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
                                     bool AllowIntrospection = true,
                                     int? DepthLimit = null,
                                     MultipleMutationOptions? MultipleMutationOptions = null,
-                                    bool EnableAggregation = true)
+                                    bool EnableAggregation = true,
+                                    FeatureFlags? FeatureFlags = null)
 {
     public const string DEFAULT_PATH = "/graphql";
 
@@ -24,4 +25,10 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
     [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
     [MemberNotNullWhen(true, nameof(DepthLimit))]
     public bool UserProvidedDepthLimit { get; init; } = false;
+
+    /// <summary>
+    /// Feature flag contains ephemeral flags passed in to init the runtime options 
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
+    public FeatureFlags FeatureFlags { get; init; } = FeatureFlags ?? new FeatureFlags();
 }
diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs
index 8b7329250a..e86766968a 100644
--- a/src/Config/ObjectModel/RuntimeConfig.cs
+++ b/src/Config/ObjectModel/RuntimeConfig.cs
@@ -150,6 +150,16 @@ Runtime is not null &&
         Runtime.GraphQL is not null &&
         Runtime.GraphQL.EnableAggregation;
 
+    /// <summary>
+    /// Retrieves the value of runtime.graphql.dwnto1joinopt.enabled property if present, default is false.
+    /// </summary>
+    [JsonIgnore]
+    public bool EnableDwNto1JoinOpt =>
+        Runtime is not null &&
+        Runtime.GraphQL is not null &&
+        Runtime.GraphQL.FeatureFlags is not null &&
+        Runtime.GraphQL.FeatureFlags.EnableDwNto1JoinQueryOptimization;
+
     private Dictionary<string, DataSource> _dataSourceNameToDataSource;
 
     private Dictionary<string, string> _entityNameToDataSourceName = new();
diff --git a/src/Core/Resolvers/BaseTSqlQueryBuilder.cs b/src/Core/Resolvers/BaseTSqlQueryBuilder.cs
new file mode 100644
index 0000000000..1bf7e73ef4
--- /dev/null
+++ b/src/Core/Resolvers/BaseTSqlQueryBuilder.cs
@@ -0,0 +1,132 @@
+using Azure.DataApiBuilder.Config.ObjectModel;
+using Azure.DataApiBuilder.Core.Models;
+
+namespace Azure.DataApiBuilder.Core.Resolvers
+{
+    /// <summary>
+    /// Base query builder class for T-SQL engine
+    /// Can be used by dwsql and mssql
+    /// </summary>
+    public abstract class BaseTSqlQueryBuilder : BaseSqlQueryBuilder
+    {
+        protected const string FOR_JSON_SUFFIX = " FOR JSON PATH, INCLUDE_NULL_VALUES";
+        protected const string WITHOUT_ARRAY_WRAPPER_SUFFIX = "WITHOUT_ARRAY_WRAPPER";
+
+        /// <summary>
+        /// Build the Json Path query needed to append to the main query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>SQL query with JSON PATH format</returns>
+        protected virtual string BuildJsonPath(SqlQueryStructure structure)
+        {
+            string query = string.Empty;
+            query += FOR_JSON_SUFFIX;
+            if (!structure.IsListQuery)
+            {
+                query += "," + WITHOUT_ARRAY_WRAPPER_SUFFIX;
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Build the predicates query needed to append to the main query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>SQL query with predicates</returns>
+        protected virtual string BuildPredicates(SqlQueryStructure structure)
+        {
+            return JoinPredicateStrings(
+                          structure.GetDbPolicyForOperation(EntityActionOperation.Read),
+                          structure.FilterPredicates,
+                          Build(structure.Predicates),
+                          Build(structure.PaginationMetadata.PaginationPredicate));
+        }
+
+        /// <summary>
+        /// Build the Group By Clause needed to append to the main query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>SQL query with group-by clause</returns>
+        protected virtual string BuildGroupBy(SqlQueryStructure structure)
+        {
+            // Add GROUP BY clause if there are any group by columns
+            if (structure.GroupByMetadata.Fields.Any())
+            {
+                return $" GROUP BY {string.Join(", ", structure.GroupByMetadata.Fields.Values.Select(c => Build(c)))}";
+            }
+
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// Build the Having clause needed to append to the main query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>SQL query with having clause</returns>
+        protected virtual string BuildHaving(SqlQueryStructure structure)
+        {
+            if (structure.GroupByMetadata.Aggregations.Count > 0)
+            {
+                List<Predicate>? havingPredicates = structure.GroupByMetadata.Aggregations
+                      .SelectMany(aggregation => aggregation.HavingPredicates ?? new List<Predicate>())
+                      .ToList();
+
+                if (havingPredicates.Any())
+                {
+                    return $" HAVING {Build(havingPredicates)}";
+                }
+            }
+
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// Build the Order By clause needed to append to the main query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>SQL query with order-by clause</returns>
+        protected virtual string BuildOrderBy(SqlQueryStructure structure)
+        {
+            if (structure.OrderByColumns.Any())
+            {
+                return $" ORDER BY {Build(structure.OrderByColumns)}";
+            }
+
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// Build the aggregation columns needed to append to the main query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>SQL query with aggregation columns</returns>
+        protected virtual string BuildAggregationColumns(SqlQueryStructure structure)
+        {
+            string aggregations = string.Empty;
+            if (structure.GroupByMetadata.Aggregations.Count > 0)
+            {
+                if (structure.Columns.Any())
+                {
+                    aggregations = $",{BuildAggregationColumns(structure.GroupByMetadata)}";
+                }
+                else
+                {
+                    aggregations = $"{BuildAggregationColumns(structure.GroupByMetadata)}";
+                }
+            }
+
+            return aggregations;
+        }
+
+        /// <summary>
+        /// Build the aggregation columns needed to append to the main query
+        /// </summary>
+        /// <param name="metadata">GroupByMetadata</param>
+        /// <returns>SQL query with aggregation columns</returns>
+        protected virtual string BuildAggregationColumns(GroupByMetadata metadata)
+        {
+            return string.Join(", ", metadata.Aggregations.Select(aggregation => Build(aggregation.Column, useAlias: true)));
+        }
+    }
+}
diff --git a/src/Core/Resolvers/DWSqlQueryBuilder.cs b/src/Core/Resolvers/DWSqlQueryBuilder.cs
index 5215deae18..510155fbfb 100644
--- a/src/Core/Resolvers/DWSqlQueryBuilder.cs
+++ b/src/Core/Resolvers/DWSqlQueryBuilder.cs
@@ -12,11 +12,18 @@ namespace Azure.DataApiBuilder.Core.Resolvers
     /// <summary>
     /// Class for building DwSql queries.
     /// </summary>
-    public class DwSqlQueryBuilder : BaseSqlQueryBuilder, IQueryBuilder
+    public class DwSqlQueryBuilder : BaseTSqlQueryBuilder, IQueryBuilder
     {
         private static DbCommandBuilder _builder = new SqlCommandBuilder();
+        private readonly bool _enableNto1JoinOpt;
         public const string COUNT_ROWS_WITH_GIVEN_PK = "cnt_rows_to_update";
 
+        public DwSqlQueryBuilder(bool enableNto1JoinOpt = false)
+        {
+            // flag to enable the optimization for N to 1 join queries
+            this._enableNto1JoinOpt = enableNto1JoinOpt;
+        }
+
         /// <inheritdoc />
         public override string QuoteIdentifier(string ident)
         {
@@ -33,18 +40,135 @@ public override string QuoteIdentifier(string ident)
         ///     WHERE 1 = 1 ORDER BY [table0].[id] ASC
         ///     ) AS [table0]
         /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>Generated sql queries based on the SqlQueryStructure</returns>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns>True if the query structure only has N-1 relations or no relations at all</returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns>The sql queries built with json functions instead of string_agg</returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            StringBuilder query = new();
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = $"SELECT {GenerateColumnsAsJsonObject(structure)}";
+                string fromSql = $" FROM ({BuildWithJsonFunc(structure)}) AS {QuoteIdentifier(structure.SourceAlias)}";
+
+                query.Append(columns)
+                    .Append(fromSql);
+            }
+            else
+            {
+                query.Append(BuildWithJsonFunc(structure))
+                    .Append(BuildJsonPath(structure));
+            }
+
+            return query.ToString();
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns>The sql queries built with json functions instead of string_agg</returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure)
+        {
+            string dataIdent = QuoteIdentifier(SqlQueryStructure.DATA_IDENT);
+            string fromSql = $"{QuoteIdentifier(structure.DatabaseObject.SchemaName)}.{QuoteIdentifier(structure.DatabaseObject.Name)} " +
+                             $"AS {QuoteIdentifier($"{structure.SourceAlias}")}{Build(structure.Joins)}";
+
+            fromSql += string.Join(
+                    "",
+                    structure.JoinQueries.Select(
+                        x => $" OUTER APPLY ({BuildWithJsonFunc(x.Value, true)}) AS {QuoteIdentifier(x.Key)}({dataIdent})"));
+
+            string predicates = BuildPredicates(structure);
+
+            string aggregations = BuildAggregationColumns(structure);
+
+            StringBuilder query = new();
+
+            query.Append($"SELECT TOP {structure.Limit()} {WrappedColumns(structure)} {aggregations}")
+                .Append($" FROM {fromSql}")
+                .Append($" WHERE {predicates}")
+                .Append(BuildGroupBy(structure))
+                .Append(BuildHaving(structure))
+                .Append(BuildOrderBy(structure));
+
+            return query.ToString();
+        }
+
+        /// <summary>
+        /// Builds the sql query that will return the json result for the sql query using string_agg
         /// </summary>
         /// <param name="structure">Sql query structure to build query on.</param>
         /// <param name="subQueryStructure">if this is a sub query executed under outerapply.</param>
         /// <returns></returns>
-        private string BuildAsJson(SqlQueryStructure structure, bool subQueryStructure = false)
+        private string BuildWithStringAgg(SqlQueryStructure structure, bool subQueryStructure = false)
         {
             string columns = GenerateColumnsAsJson(structure, subQueryStructure);
             string fromSql = $"{BuildSqlQuery(structure)}";
@@ -75,7 +199,7 @@ private string BuildSqlQuery(SqlQueryStructure structure)
             fromSql.Append(string.Join(
                     "",
                     structure.JoinQueries.Select(
-                        x => $" OUTER APPLY ({BuildAsJson(x.Value, true)}) AS {QuoteIdentifier(x.Key)}({dataIdent})")));
+                        x => $" OUTER APPLY ({BuildWithStringAgg(x.Value, true)}) AS {QuoteIdentifier(x.Key)}({dataIdent})")));
 
             string predicates = JoinPredicateStrings(
                                     structure.GetDbPolicyForOperation(EntityActionOperation.Read),
@@ -129,6 +253,41 @@ private string BuildSqlQuery(SqlQueryStructure structure)
             return query;
         }
 
+        /// <summary>
+        /// Generate the columns selected and wrap them with JSON_OBJECT
+        /// Example:
+        /// SELECT JSON_OBJECT('id': [id]) 
+        ///  FROM
+        ///    (
+        ///        SELECT TOP 1 
+        ///            [table1].[id] AS [id]
+        ///        FROM
+        ///            [dbo].[book_website_placements] AS [table1]
+        ///        WHERE
+        ///            [table0].[id] = [table1].[book_id]
+        ///            AND[table1].[book_id] = [table0].[id]
+        ///        ORDER BY
+        ///            [table1].[id] ASC
+        ///    ) AS[table1]
+        /// </summary>
+        /// <param name="structure">Sql query structure to generate the columns with JSON_OBJECT</param>
+        /// <returns></returns>
+        private static string GenerateColumnsAsJsonObject(SqlQueryStructure structure)
+        {
+            List<string> columns = new();
+            foreach (LabelledColumn column in structure.Columns)
+            {
+                string col_value = $"\'{column.Label}\': [{column.Label}]";
+                columns.Add(col_value);
+            }
+
+            string joinedColumns = columns.Count > 1 ?
+                 string.Join(",", columns) :
+                 columns[0];
+
+            return $"JSON_OBJECT({joinedColumns})";
+        }
+
         private static string GenerateColumnsAsJson(SqlQueryStructure structure, bool subQueryStructure = false)
         {
             string columns;
@@ -425,14 +584,6 @@ private static string BuildProcedureParameterList(Dictionary<string, object> pro
             return parameterList.Length > 0 ? parameterList[..^2] : parameterList;
         }
 
-        /// <summary>
-        /// Builds the aggregation columns part of the SELECT clause
-        /// </summary>
-        private string BuildAggregationColumns(GroupByMetadata metadata)
-        {
-            return string.Join(", ", metadata.Aggregations.Select(aggregation => Build(aggregation.Column, useAlias: true)));
-        }
-
         public string QuoteTableNameAsDBConnectionParam(string param)
         {
             // Table names in DWSql should not be quoted when used as DB Connection Params.
diff --git a/src/Core/Resolvers/Factories/QueryManagerFactory.cs b/src/Core/Resolvers/Factories/QueryManagerFactory.cs
index 209f0d70c0..72c99124c0 100644
--- a/src/Core/Resolvers/Factories/QueryManagerFactory.cs
+++ b/src/Core/Resolvers/Factories/QueryManagerFactory.cs
@@ -86,7 +86,7 @@ private void ConfigureQueryManagerFactory()
                         queryExecutor = new PostgreSqlQueryExecutor(_runtimeConfigProvider, exceptionParser, _logger, _contextAccessor, _handler);
                         break;
                     case DatabaseType.DWSQL:
-                        queryBuilder = new DwSqlQueryBuilder();
+                        queryBuilder = new DwSqlQueryBuilder(enableNto1JoinOpt: _runtimeConfigProvider.GetConfig().EnableDwNto1JoinOpt);
                         exceptionParser = new MsSqlDbExceptionParser(_runtimeConfigProvider);
                         queryExecutor = new MsSqlQueryExecutor(_runtimeConfigProvider, exceptionParser, _logger, _contextAccessor, _handler);
                         break;
diff --git a/src/Core/Resolvers/MsSqlQueryBuilder.cs b/src/Core/Resolvers/MsSqlQueryBuilder.cs
index 40f201387f..798c20975a 100644
--- a/src/Core/Resolvers/MsSqlQueryBuilder.cs
+++ b/src/Core/Resolvers/MsSqlQueryBuilder.cs
@@ -14,10 +14,8 @@ namespace Azure.DataApiBuilder.Core.Resolvers
     /// <summary>
     /// Class for building MsSql queries.
     /// </summary>
-    public class MsSqlQueryBuilder : BaseSqlQueryBuilder, IQueryBuilder
+    public class MsSqlQueryBuilder : BaseTSqlQueryBuilder, IQueryBuilder
     {
-        private const string FOR_JSON_SUFFIX = " FOR JSON PATH, INCLUDE_NULL_VALUES";
-        private const string WITHOUT_ARRAY_WRAPPER_SUFFIX = "WITHOUT_ARRAY_WRAPPER";
         private const string MSSQL_ESCAPE_CHAR = "\\";
 
         // Name of the column which stores the number of records with given PK. Used in Upsert queries.
@@ -43,85 +41,21 @@ public string Build(SqlQueryStructure structure)
                     structure.JoinQueries.Select(
                         x => $" OUTER APPLY ({Build(x.Value)}) AS {QuoteIdentifier(x.Key)}({dataIdent})"));
 
-            string predicates;
+            string predicates = BuildPredicates(structure);
 
-            if (structure.IsMultipleCreateOperation)
-            {
-                predicates = JoinPredicateStrings(
-                                    structure.GetDbPolicyForOperation(EntityActionOperation.Read),
-                                    structure.FilterPredicates,
-                                    Build(structure.Predicates, " OR ", isMultipleCreateOperation: true),
-                                    Build(structure.PaginationMetadata.PaginationPredicate));
-            }
-            else
-            {
-                predicates = JoinPredicateStrings(
-                                    structure.GetDbPolicyForOperation(EntityActionOperation.Read),
-                                    structure.FilterPredicates,
-                                    Build(structure.Predicates),
-                                    Build(structure.PaginationMetadata.PaginationPredicate));
-            }
+            string aggregations = BuildAggregationColumns(structure);
 
-            // we add '\' character to escape the special characters in the string, but if special characters are needed to be searched
-            // as literal characters we need to escape the '\' character itself. Since we add `\` only for LIKE, so we search if the query
-            // contains LIKE and add the ESCAPE clause accordingly.
-            predicates = AddEscapeToLikeClauses(predicates);
-
-            string aggregations = string.Empty;
-            if (structure.GroupByMetadata.Aggregations.Count > 0)
-            {
-                if (structure.Columns.Any())
-                {
-                    aggregations = $",{BuildAggregationColumns(structure.GroupByMetadata)}";
-                }
-                else
-                {
-                    aggregations = $"{BuildAggregationColumns(structure.GroupByMetadata)}";
-                }
-            }
+            StringBuilder query = new();
 
-            string query = $"SELECT TOP {structure.Limit()} {WrappedColumns(structure)} {aggregations}"
-                + $" FROM {fromSql}"
-                + $" WHERE {predicates}";
-
-            // Add GROUP BY clause if there are any group by columns
-            if (structure.GroupByMetadata.Fields.Any())
-            {
-                query += $" GROUP BY {string.Join(", ", structure.GroupByMetadata.Fields.Values.Select(c => Build(c)))}";
-            }
-
-            if (structure.GroupByMetadata.Aggregations.Count > 0)
-            {
-                List<Predicate>? havingPredicates = structure.GroupByMetadata.Aggregations
-                      .SelectMany(aggregation => aggregation.HavingPredicates ?? new List<Predicate>())
-                      .ToList();
-
-                if (havingPredicates.Any())
-                {
-                    query += $" HAVING {Build(havingPredicates)}";
-                }
-            }
+            query.Append($"SELECT TOP {structure.Limit()} {WrappedColumns(structure)} {aggregations}")
+                .Append($" FROM {fromSql}")
+                .Append($" WHERE {predicates}")
+                .Append(BuildGroupBy(structure))
+                .Append(BuildHaving(structure))
+                .Append(BuildOrderBy(structure))
+                .Append(BuildJsonPath(structure));
 
-            if (structure.OrderByColumns.Any())
-            {
-                query += $" ORDER BY {Build(structure.OrderByColumns)}";
-            }
-
-            query += FOR_JSON_SUFFIX;
-            if (!structure.IsListQuery)
-            {
-                query += "," + WITHOUT_ARRAY_WRAPPER_SUFFIX;
-            }
-
-            return query;
-        }
-
-        /// <summary>
-        /// Builds the aggregation columns part of the SELECT clause
-        /// </summary>
-        private string BuildAggregationColumns(GroupByMetadata metadata)
-        {
-            return string.Join(", ", metadata.Aggregations.Select(aggregation => Build(aggregation.Column, useAlias: true)));
+            return query.ToString();
         }
 
         /// <summary>
@@ -600,5 +534,31 @@ public string QuoteTableNameAsDBConnectionParam(string param)
             // Table names in MSSQL should not be quoted when used as DB Connection Params.
             return param;
         }
+
+        protected override string BuildPredicates(SqlQueryStructure structure)
+        {
+            string predicates;
+            if (structure.IsMultipleCreateOperation)
+            {
+                predicates = JoinPredicateStrings(
+                                    structure.GetDbPolicyForOperation(EntityActionOperation.Read),
+                                    structure.FilterPredicates,
+                                    Build(structure.Predicates, " OR ", isMultipleCreateOperation: true),
+                                    Build(structure.PaginationMetadata.PaginationPredicate));
+            }
+            else
+            {
+                predicates = JoinPredicateStrings(
+                                    structure.GetDbPolicyForOperation(EntityActionOperation.Read),
+                                    structure.FilterPredicates,
+                                    Build(structure.Predicates),
+                                    Build(structure.PaginationMetadata.PaginationPredicate));
+            }
+
+            // we add '\' character to escape the special characters in the string, but if special characters are needed to be searched
+            // as literal characters we need to escape the '\' character itself. Since we add `\` only for LIKE, so we search if the query
+            // contains LIKE and add the ESCAPE clause accordingly.
+            return AddEscapeToLikeClauses(predicates);
+        }
     }
 }
diff --git a/src/Service.Tests/ModuleInitializer.cs b/src/Service.Tests/ModuleInitializer.cs
index 7a6317790b..2bc1f3fad7 100644
--- a/src/Service.Tests/ModuleInitializer.cs
+++ b/src/Service.Tests/ModuleInitializer.cs
@@ -85,6 +85,10 @@ public static void Init()
         VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableAggregation);
         // Ignore the EnableAggregation as that's unimportant from a test standpoint.
         VerifierSettings.IgnoreMember<GraphQLRuntimeOptions>(options => options.EnableAggregation);
+        // Ignore the EnableDwNto1JoinOpt as that's unimportant from a test standpoint.
+        VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableDwNto1JoinOpt);
+        // Ignore the FeatureFlags as that's unimportant from a test standpoint.
+        VerifierSettings.IgnoreMember<GraphQLRuntimeOptions>(options => options.FeatureFlags);
         // Ignore the message as that's not serialized in our config file anyway.
         VerifierSettings.IgnoreMember<DataSource>(dataSource => dataSource.DatabaseTypeNotSupportedMessage);
         // Ignore DefaultDataSourceName as that's not serialized in our config file.
diff --git a/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs b/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
index 5ba20affdb..c1b6ff9b27 100644
--- a/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
+++ b/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
@@ -2,7 +2,9 @@
 // Licensed under the MIT License.
 
 using System;
+using System.Collections.Generic;
 using System.Text.Json;
+using System.Text.Json.Nodes;
 using System.Threading.Tasks;
 using Azure.DataApiBuilder.Config.ObjectModel;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -97,6 +99,341 @@ ORDER BY [table0].[id] ASC
             await OneToOneJoinQuery(dwSqlQuery);
         }
 
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure the results are identical with using STRING_AGG
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async override Task DeeplyNestedManyToOneJoinQuery()
+        {
+            string dwSqlQuery = @"
+            SELECT COALESCE(
+                '[' + STRING_AGG(
+                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                    ', '
+                ) + ']', 
+                '[]'
+            )
+            FROM (
+                SELECT TOP 5 
+                    [table0].[title] AS [title], 
+                    ([table1_subq].[data]) AS [publishers]
+                FROM [dbo].[books] AS [table0]
+                OUTER APPLY (
+                    SELECT STRING_AGG(
+                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + ',' +
+                        N'""books"":' + ISNULL('""' + STRING_ESCAPE([books], 'json') + '""', 'null') + '}', 
+                        ', '
+                    )
+                    FROM (
+                        SELECT TOP 1 
+                            [table1].[name] AS [name], 
+                            (COALESCE([table2_subq].[data], '[]')) AS [books]
+                        FROM [dbo].[publishers] AS [table1]
+                        OUTER APPLY (
+                            SELECT COALESCE(
+                                '[' + STRING_AGG(
+                                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                                    ', '
+                                ) + ']', 
+                                '[]'
+                            )
+                            FROM (
+                                SELECT TOP 4 
+                                    [table2].[title] AS [title], 
+                                    ([table3_subq].[data]) AS [publishers]
+                                FROM [dbo].[books] AS [table2]
+                                OUTER APPLY (
+                                    SELECT STRING_AGG(
+                                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + ',' +
+                                        N'""books"":' + ISNULL('""' + STRING_ESCAPE([books], 'json') + '""', 'null') + '}', 
+                                        ', '
+                                    )
+                                    FROM (
+                                        SELECT TOP 1 
+                                            [table3].[name] AS [name], 
+                                            (COALESCE([table4_subq].[data], '[]')) AS [books]
+                                        FROM [dbo].[publishers] AS [table3]
+                                        OUTER APPLY (
+                                            SELECT COALESCE(
+                                                '[' + STRING_AGG(
+                                                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                                                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                                                    ', '
+                                                ) + ']', 
+                                                '[]'
+                                            )
+                                            FROM (
+                                                SELECT TOP 3 
+                                                    [table4].[title] AS [title], 
+                                                    ([table5_subq].[data]) AS [publishers]
+                                                FROM [dbo].[books] AS [table4]
+                                                OUTER APPLY (
+                                                    SELECT STRING_AGG(
+                                                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + '}', 
+                                                        ', '
+                                                    )
+                                                    FROM (
+                                                        SELECT TOP 1 
+                                                            [table5].[name] AS [name]
+                                                        FROM [dbo].[publishers] AS [table5]
+                                                        WHERE [table4].[publisher_id] = [table5].[id] 
+                                                          AND [table5].[id] = [table4].[publisher_id]
+                                                        ORDER BY [table5].[id] ASC
+                                                    ) AS [table5]
+                                                ) AS [table5_subq]([data])
+                                                WHERE [table4].[publisher_id] = [table3].[id]
+                                                ORDER BY [table4].[id] ASC
+                                            ) AS [table4]
+                                        ) AS [table4_subq]([data])
+                                        WHERE [table2].[publisher_id] = [table3].[id] 
+                                          AND [table3].[id] = [table2].[publisher_id]
+                                        ORDER BY [table3].[id] ASC
+                                    ) AS [table3]
+                                ) AS [table3_subq]([data])
+                                WHERE [table2].[publisher_id] = [table1].[id]
+                                ORDER BY [table2].[id] ASC
+                            ) AS [table2]
+                        ) AS [table2_subq]([data])
+                        WHERE [table0].[publisher_id] = [table1].[id] 
+                          AND [table1].[id] = [table0].[publisher_id]
+                        ORDER BY [table1].[id] ASC
+                    ) AS [table1]
+                ) AS [table1_subq]([data])
+                WHERE 1 = 1
+                ORDER BY [table0].[id] ASC
+            ) AS [table0]
+            ";
+
+            string graphQLQueryName = "books";
+            string graphQLQuery = @"{
+              books(first: 5) {
+                items {
+                  title
+                  publishers {
+                    name
+                    books(first: 4) {
+                      items {
+                        title
+                        publishers {
+                          name
+                          books(first: 3) {
+                            items {
+                              title
+                              publishers {
+                                name
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }";
+
+            JsonElement actualGraphQLResults = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false);
+            string cleanedActualGraphQLResults = SqlTestHelper.RemoveItemsKeyFromJson(actualGraphQLResults.GetProperty("items")).ToString();
+
+            string expected = await GetDatabaseResultAsync(dwSqlQuery);
+
+            SqlTestHelper.PerformTestEqualJsonStringsForNestedQueries(expected, cleanedActualGraphQLResults);
+        }
+
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure for To-N joins, the query builder will fallback to use STRING_AGG
+        /// And the results are consistent
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async Task OneToManyJoinQuery()
+        {
+            string dwSqlQuery = @"
+                SELECT 
+                    COALESCE(
+                        '[' + STRING_AGG(
+                            '{' + 
+                            N'""id"":' + ISNULL(STRING_ESCAPE(CONVERT(NVARCHAR(MAX), [id]), 'json'), 'null') + ',' +
+                            N'""reviews"":' + ISNULL('""' + STRING_ESCAPE([reviews], 'json') + '""', 'null') + 
+                            '}', 
+                            ', '
+                        ) + ']', 
+                        '[]'
+                    ) 
+                FROM 
+                    (
+                        SELECT TOP 2 
+                            [table0].[id] AS [id], 
+                            COALESCE([table1_subq].[data], '[]') AS [reviews]
+                        FROM 
+                            [dbo].[books] AS [table0]
+                        OUTER APPLY 
+                            (
+                                SELECT 
+                                    COALESCE(
+                                        '[' + STRING_AGG(
+                                            '{' + 
+                                            N'""content"":' + ISNULL('""' + STRING_ESCAPE([content], 'json') + '""', 'null') + 
+                                            '}', 
+                                            ', '
+                                        ) + ']', 
+                                        '[]'
+                                    ) 
+                                FROM 
+                                    (
+                                        SELECT TOP 100 
+                                            [table1].[content] AS [content]
+                                        FROM 
+                                            [dbo].[reviews] AS [table1]
+                                        WHERE 
+                                            [table1].[book_id] = [table0].[id]
+                                        ORDER BY 
+                                            [table1].[book_id] ASC, 
+                                            [table1].[id] ASC
+                                    ) AS [table1]
+                            ) AS [table1_subq]([data])
+                        WHERE 
+                            1 = 1
+                        ORDER BY 
+                            [table0].[id] ASC
+                    ) AS [table0]";
+
+            string graphQLQueryName = "books";
+            string graphQLQuery = @"
+               query {
+                  books (first: 2) {
+                    items {
+                      id,
+                      reviews {
+                        items {
+                          content
+                        }
+                      }
+                    }
+                  }
+                }";
+
+            JsonElement actual = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false);
+            JsonNode cleanedActual = SqlTestHelper.RemoveItemsKeyFromJson(actual.GetProperty("items"));
+
+            string expected = await GetDatabaseResultAsync(dwSqlQuery);
+
+            SqlTestHelper.PerformTestEqualJsonStringsForNestedQueries(expected, cleanedActual.ToString());
+        }
+
+        /// <summary>
+        /// Added more complicated cases when queries are deeply nested and compare the results. 
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async override Task DeeplyNestedManyToManyJoinQuery()
+        {
+            string dwSqlQuery = @"
+        SELECT COALESCE(
+            '[' + STRING_AGG(
+                '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                N'""authors"":' + ISNULL('""' + STRING_ESCAPE([authors], 'json') + '""', 'null') + '}', ', '
+            ) + ']', '[]'
+        )
+        FROM (
+            SELECT TOP 5 
+                [table0].[title] AS [title], 
+                COALESCE([table1_subq].[data], '[]') AS [authors]
+            FROM [dbo].[books] AS [table0]
+            OUTER APPLY (
+                SELECT COALESCE(
+                    '[' + STRING_AGG(
+                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + ',' +
+                        N'""books"":' + ISNULL('""' + STRING_ESCAPE([books], 'json') + '""', 'null') + '}', ', '
+                    ) + ']', '[]'
+                )
+                FROM (
+                    SELECT TOP 4 
+                        [table1].[name] AS [name], 
+                        COALESCE([table2_subq].[data], '[]') AS [books]
+                    FROM [dbo].[authors] AS [table1]
+                    INNER JOIN [dbo].[book_author_link] AS [table11] 
+                        ON [table11].[author_id] = [table1].[id]
+                    OUTER APPLY (
+                        SELECT COALESCE(
+                            '[' + STRING_AGG(
+                                '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                                N'""authors"":' + ISNULL('""' + STRING_ESCAPE([authors], 'json') + '""', 'null') + '}', ', '
+                            ) + ']', '[]'
+                        )
+                        FROM (
+                            SELECT TOP 3 
+                                [table2].[title] AS [title], 
+                                COALESCE([table3_subq].[data], '[]') AS [authors]
+                            FROM [dbo].[books] AS [table2]
+                            INNER JOIN [dbo].[book_author_link] AS [table8] 
+                                ON [table8].[book_id] = [table2].[id]
+                            OUTER APPLY (
+                                SELECT COALESCE(
+                                    '[' + STRING_AGG(
+                                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + '}', ', '
+                                    ) + ']', '[]'
+                                )
+                                FROM (
+                                    SELECT TOP 2 
+                                        [table3].[name] AS [name]
+                                    FROM [dbo].[authors] AS [table3]
+                                    INNER JOIN [dbo].[book_author_link] AS [table5] 
+                                        ON [table5].[author_id] = [table3].[id]
+                                    WHERE [table5].[book_id] = [table2].[id]
+                                    ORDER BY [table3].[id] ASC
+                                ) AS [table3]
+                            ) AS [table3_subq]([data])
+                            WHERE [table8].[author_id] = [table1].[id]
+                            ORDER BY [table2].[id] ASC
+                        ) AS [table2]
+                    ) AS [table2_subq]([data])
+                    WHERE [table11].[book_id] = [table0].[id]
+                    ORDER BY [table1].[id] ASC
+                ) AS [table1]
+            ) AS [table1_subq]([data])
+            WHERE 1 = 1
+            ORDER BY [table0].[id] ASC
+        ) AS [table0]";
+
+            string graphQLQueryName = "books";
+            string graphQLQuery = @"
+            {
+                books(first: 5) {
+                    items {
+                        title
+                        authors(first: 4) {
+                            items {
+                                name
+                                books(first: 3) {
+                                    items {
+                                        title
+                                        authors(first: 2) {
+                                           items {
+                                                name
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }";
+
+            JsonElement actualGraphQLResults = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false);
+            string cleanedActualGraphQLResults = SqlTestHelper.RemoveItemsKeyFromJson(actualGraphQLResults.GetProperty("items")).ToString();
+
+            string expected = await GetDatabaseResultAsync(dwSqlQuery);
+
+            SqlTestHelper.PerformTestEqualJsonStringsForNestedQueries(expected, cleanedActualGraphQLResults);
+        }
+
         /// <summary>
         /// Test query on One-To-One relationship when the fields defining
         /// the relationship in the entity include fields that are mapped in
@@ -754,6 +1091,55 @@ public override async Task TestNoAggregationOptionsForTableWithoutNumericFields(
         {
             await base.TestNoAggregationOptionsForTableWithoutNumericFields();
         }
+
+        /// <summary>
+        /// When the feature flag object is passed to build GraphQL runtime objects
+        /// Runtime config can correctly get the value
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public void TestEnableDwNto1JoinQueryFeatureFlagLoadedFromRuntime()
+        {
+            RuntimeConfig mockConfig = new(
+               Schema: "",
+               DataSource: new(DatabaseType.MySQL, string.Empty, new()),
+               Runtime: new(
+                   Rest: new(),
+                   GraphQL: new(FeatureFlags: new()
+                   {
+                       EnableDwNto1JoinQueryOptimization = true
+                   }),
+                   Host: new(null, null)
+               ),
+               Entities: new(new Dictionary<string, Entity>())
+            );
+
+            Assert.IsTrue(mockConfig.EnableDwNto1JoinOpt);
+        }
+
+        /// <summary>
+        /// When the feature flag object is NOT passed to build GraphQL runtime objects
+        /// Runtime config can correctly get the default value instead
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public void TestEnableDwNto1JoinQueryFeatureFlagDefaultValueLoaded()
+        {
+            RuntimeConfig mockConfig = new(
+               Schema: "",
+               DataSource: new(DatabaseType.MySQL, string.Empty, new()),
+               Runtime: new(
+                   Rest: new(),
+                   GraphQL: new(),
+                   Host: new(null, null)
+               ),
+               Entities: new(new Dictionary<string, Entity>())
+            );
+
+            FeatureFlags expect = new();
+
+            Assert.AreEqual(expect.EnableDwNto1JoinQueryOptimization, mockConfig.EnableDwNto1JoinOpt);
+        }
         #endregion
     }
 }
diff --git a/src/Service.Tests/SqlTests/SqlTestHelper.cs b/src/Service.Tests/SqlTests/SqlTestHelper.cs
index dafd3ead30..051ddea957 100644
--- a/src/Service.Tests/SqlTests/SqlTestHelper.cs
+++ b/src/Service.Tests/SqlTests/SqlTestHelper.cs
@@ -8,6 +8,7 @@
 using System.Net.Http;
 using System.Reflection;
 using System.Text;
+using System.Text.Encodings.Web;
 using System.Text.Json;
 using System.Text.Json.Nodes;
 using System.Text.RegularExpressions;
@@ -64,6 +65,20 @@ public static bool JsonStringsDeepEqual(string jsonString1, string jsonString2)
                 JToken.DeepEquals(JToken.Parse(jsonString1), JToken.Parse(jsonString2));
         }
 
+        /// <summary>
+        /// For nested queries results from direct db call, it will append a whitespace for each item in an array
+        /// e.g. [{address: 1},<whitespace>{address: 2}]
+        /// Removing all whitespaces when comparing with results from GraphQL
+        /// </summary>
+        /// <param name="expected"></param>
+        /// <param name="actual"></param>
+        public static void PerformTestEqualJsonStringsForNestedQueries(string expected, string actual)
+        {
+            PerformTestEqualJsonStrings(
+                expected.Trim().Replace(" ", ""),
+                actual.Trim().Replace(" ", ""));
+        }
+
         /// <summary>
         /// Compares two JSON strings for equality after converting all DateTime values if present to a consistent format.
         /// Also Adds a useful failure message around the expected == actual operation.
@@ -151,6 +166,93 @@ public static void TestForErrorInGraphQLResponse(string response, string message
             }
         }
 
+        /// <summary>
+        /// For the results from GraphQL execution response, it will contains the Items key for 1-Many relations
+        /// However for direct db query execution response, it will not have the Item key.
+        /// This function will remove the Item key and aggregate child objs to parent which makes it easier to compare two results
+        /// books {                    books {
+        ///     items: {
+        ///         authors {    ==>  
+        ///               id                  authors {
+        ///                                     id
+        ///                                 }
+        ///                             }
+        ///     }
+        /// }
+        /// }
+        /// </summary>
+        /// <param name="element"></param>
+        /// <returns></returns>
+        public static JsonNode RemoveItemsKeyFromJson(JsonElement element)
+        {
+            if (element.ValueKind == JsonValueKind.Object)
+            {
+                JsonObject jsonObject = new();
+
+                foreach (JsonProperty property in element.EnumerateObject())
+                {
+                    if (property.Name == "items")
+                    {
+                        if (property.Value.ValueKind == JsonValueKind.Array)
+                        {
+                            // If "items" contains an array, return its elements merged into the parent
+                            JsonArray itemsArray = new();
+                            foreach (JsonElement item in property.Value.EnumerateArray())
+                            {
+                                itemsArray.Add(RemoveItemsKeyFromJson(item));
+                            }
+
+                            return itemsArray;
+
+                        }
+                        else if (property.Value.ValueKind == JsonValueKind.Object)
+                        {
+                            // If "items" contains an object, merge its properties into the parent
+                            JsonObject nestedObject = RemoveItemsKeyFromJson(property.Value) as JsonObject;
+                            foreach (KeyValuePair<string, JsonNode> kvp in nestedObject)
+                            {
+                                jsonObject[kvp.Key] = kvp.Value;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        JsonNode node = RemoveItemsKeyFromJson(property.Value);
+
+                        if (node.GetValueKind() == JsonValueKind.String || node.GetValueKind() == JsonValueKind.Number)
+                        {
+                            jsonObject[property.Name] = node;
+                        }
+                        else
+                        {
+                            // serialize the array to be in same format as the query results from DB
+                            jsonObject[property.Name] = JsonSerializer.Serialize(node, new JsonSerializerOptions
+                            {
+                                // avoid \u0022 when a json string is serialized for multiple times due to recursion
+                                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                            });
+                        }
+                    }
+                }
+
+                return jsonObject;
+            }
+            else if (element.ValueKind == JsonValueKind.Array)
+            {
+                JsonArray jsonArray = new();
+                foreach (JsonElement item in element.EnumerateArray())
+                {
+                    jsonArray.Add(RemoveItemsKeyFromJson(item));
+                }
+
+                return jsonArray;
+            }
+            else
+            {
+                return JsonValue.Create(element);
+            }
+        }
+
         /// <summary>
         /// Validates the result from the sql db matches the graphql result returned by the engine.
         /// </summary>


Reviews:
user: login: vadeveka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T19:43:07Z
user: login: vadeveka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T19:51:02Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:03:24Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:04:45Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:05:23Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:07:14Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:11:18Z
user: login: rohkhann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T21:49:53Z
user: login: vadeveka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T22:02:45Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T22:05:42Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T00:43:46Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T00:56:53Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:56:39Z
user: login: JerryNixon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T20:30:02Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T23:53:18Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T23:55:25Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T00:19:23Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T00:20:25Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T00:26:22Z
user: login: rohkhann
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T19:43:39Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T16:48:03Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T16:51:45Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T16:57:14Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T16:58:17Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T17:09:20Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T17:10:10Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T17:15:31Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T17:25:20Z
user: login: Aniruddh25
body: Overall looks good, but looking for better testing. And making sure we still continue to test when the feature flag is off because thats the default behavior today. 
Turning it on always by default in testing keeps us at risk of regression in the false scenario.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T17:28:50Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:44:42Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:44:50Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:44:56Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:45:08Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:45:13Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:45:24Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T20:46:44Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T00:02:08Z
user: login: Aniruddh25
body: LGTM, thanks for addressing all comments and answering the questions! We should look at better DwSql testing (not using SQL2019 instance for e.g.) as a follow up to this PR.
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-04-02T00:05:23Z
user: login: rohkhann
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T16:46:39Z
user: login: Aniruddh25
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-02T16:49:26Z
user: login: lingxiao-microsoft
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T18:44:07Z

Review comments:
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: vadeveka
body: what is a list query and its relevance to type of relationship?
created_at: 2025-03-26T17:55:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += FOR_JSON_SUFFIX;
+                if (!structure.IsListQuery)
+                {
+                    query += "," + WITHOUT_ARRAY_WRAPPER_SUFFIX;
+                }
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: vadeveka
body: NIT: could we showcase a sample query generated either here or as partial queries in within this method for relevant statements?
created_at: 2025-03-26T18:12:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: vadeveka
body: NIT: add param descriptions for all the new methods added?
created_at: 2025-03-26T19:38:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +98,341 @@ ORDER BY [table0].[id] ASC
             await OneToOneJoinQuery(dwSqlQuery);
         }
 
+        /// <summary>
path: src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
user: login: vadeveka
body: Can we add a test case where JSON PATH gets applied?
created_at: 2025-03-26T19:50:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +98,341 @@ ORDER BY [table0].[id] ASC
             await OneToOneJoinQuery(dwSqlQuery);
         }
 
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure the results are identical with using STRING_AGG
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async override Task DeeplyNestedManyToOneJoinQuery()
path: src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
user: login: vadeveka
body: Should we be having JSON PATH applied in this case for the outer query?
created_at: 2025-03-26T19:50:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Will do
created_at: 2025-03-26T20:03:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: if it is a list query which means it is a to-N relation, it equals to the `items` keywords in the graphql query. 
created_at: 2025-03-26T20:04:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += FOR_JSON_SUFFIX;
+                if (!structure.IsListQuery)
+                {
+                    query += "," + WITHOUT_ARRAY_WRAPPER_SUFFIX;
+                }
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Willl update
created_at: 2025-03-26T20:05:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +98,341 @@ ORDER BY [table0].[id] ASC
             await OneToOneJoinQuery(dwSqlQuery);
         }
 
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure the results are identical with using STRING_AGG
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async override Task DeeplyNestedManyToOneJoinQuery()
path: src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
user: login: lingxiao-microsoft
body: Yes, but the idea here is to do one step further to make sure the results we generated using JSON PATH is same as the results generated previously using STRING AGG to ensure backward compatibility. 
created_at: 2025-03-26T20:07:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +98,341 @@ ORDER BY [table0].[id] ASC
             await OneToOneJoinQuery(dwSqlQuery);
         }
 
+        /// <summary>
path: src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
user: login: lingxiao-microsoft
body: There are [test cases](https://github.com/Azure/data-api-builder/blob/aee9690761018b2995642ca925764eeec0ebdaa4/src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs#L143) against queries using `JSON PATH` for non-join queries. 

We should already be covered by it. 


created_at: 2025-03-26T20:11:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,6 +53,10 @@ public static void Init()
         VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableAggregation);
         // Ignore the EnableAggregation as that's unimportant from a test standpoint.
         VerifierSettings.IgnoreMember<GraphQLRuntimeOptions>(options => options.EnableAggregation);
+        // Ignore the EnableDwNto1JoinOpt as that's unimportant from a test standpoint.
+        VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableDwNto1JoinOpt);
path: src/Cli.Tests/ModuleInitializer.cs
user: login: rohkhann
body: Adding to runtime config makes this publicly facing and something users should be aware of @JerryNixon on his inputs here.
created_at: 2025-03-26T21:44:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,8 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
                                     bool AllowIntrospection = true,
                                     int? DepthLimit = null,
                                     MultipleMutationOptions? MultipleMutationOptions = null,
-                                    bool EnableAggregation = true)
+                                    bool EnableAggregation = true,
+                                    bool EnableDwNto1JoinOpt = false)
path: src/Config/ObjectModel/GraphQLRuntimeOptions.cs
user: login: rohkhann
body: Dab users would not see the benefits of this perf improvement and it is a very unintuitive thing to go to config file and enable. @aaronburtle any other way of having a feature flag apart from adding it in the runtime config?
created_at: 2025-03-26T21:45:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,8 +15,17 @@ namespace Azure.DataApiBuilder.Core.Resolvers
     public class DwSqlQueryBuilder : BaseSqlQueryBuilder, IQueryBuilder
     {
         private static DbCommandBuilder _builder = new SqlCommandBuilder();
+        private readonly bool _enableNto1JoinOpt;
+        private const string FOR_JSON_SUFFIX = " FOR JSON PATH, INCLUDE_NULL_VALUES";
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: rohkhann
body: nit: for other constants we dont prefix it with space (see the WITHOUT_ARRAY_WRAPPER) should probably stick to ensure we follow same format and there are not ghost spaces added.
created_at: 2025-03-26T21:46:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += FOR_JSON_SUFFIX;
+                if (!structure.IsListQuery)
+                {
+                    query += "," + WITHOUT_ARRAY_WRAPPER_SUFFIX;
+                }
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure)
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: rohkhann
body: My understanding is this should exactly mimic the logic for mssql file, any chance of code reuse to prevent having to maintain two version os essentially the same TSQL code?
created_at: 2025-03-26T21:48:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +277,27 @@ private string BuildSqlQuery(SqlQueryStructure structure)
             return query;
         }
 
+        /// <summary>
+        /// Generate the columns selected and wrap them with JSON_OBJECT
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <returns></returns>
+        private static string GenerateColumnsAsJsonObject(SqlQueryStructure structure)
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: rohkhann
body: Same as above, lets see what we can reuse from the mssql and the dwsql classes. we can even have an intermediate TSQL queryBuilder class with common functions where mssql and dwsql inherit and overrride off of it if necessary.
created_at: 2025-03-26T21:49:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,8 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
                                     bool AllowIntrospection = true,
                                     int? DepthLimit = null,
                                     MultipleMutationOptions? MultipleMutationOptions = null,
-                                    bool EnableAggregation = true)
+                                    bool EnableAggregation = true,
+                                    bool EnableDwNto1JoinOpt = false)
path: src/Config/ObjectModel/GraphQLRuntimeOptions.cs
user: login: lingxiao-microsoft
body: We can flip this flag to always true once we've verified with customers. So that everyone else can benefit from it without flip it in config file

@aaronburtle , pls let me know if there is a better way to achieve the purpose without making it public configuration to everyone
created_at: 2025-03-26T22:01:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,8 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
                                     bool AllowIntrospection = true,
                                     int? DepthLimit = null,
                                     MultipleMutationOptions? MultipleMutationOptions = null,
-                                    bool EnableAggregation = true)
+                                    bool EnableAggregation = true,
+                                    bool EnableDwNto1JoinOpt = false)
path: src/Config/ObjectModel/GraphQLRuntimeOptions.cs
user: login: vadeveka
body: DAB users will benefit if they use dwsql with Synapse or fabric warehouses/SAE. It doesnt need to be a public configuration though. We should enable this optimization by a default after building confidence in GraphQL workload.

created_at: 2025-03-26T22:02:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,8 +15,17 @@ namespace Azure.DataApiBuilder.Core.Resolvers
     public class DwSqlQueryBuilder : BaseSqlQueryBuilder, IQueryBuilder
     {
         private static DbCommandBuilder _builder = new SqlCommandBuilder();
+        private readonly bool _enableNto1JoinOpt;
+        private const string FOR_JSON_SUFFIX = " FOR JSON PATH, INCLUDE_NULL_VALUES";
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: This is actually needed similar to a ` FROM`, else we will get error like '"Incorrect syntax near 'ASCFOR'."'
created_at: 2025-03-26T22:49:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +277,27 @@ private string BuildSqlQuery(SqlQueryStructure structure)
             return query;
         }
 
+        /// <summary>
+        /// Generate the columns selected and wrap them with JSON_OBJECT
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <returns></returns>
+        private static string GenerateColumnsAsJsonObject(SqlQueryStructure structure)
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: For `GenerateColumnsAsJsonObject` it is only used in dwsql, as for mssql `FOR JSON PATH` is supported for both inner and outer query, so no need to use JSON OBJECT.

But I have added common functions to use for other scenarios
created_at: 2025-03-26T22:53:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += FOR_JSON_SUFFIX;
+                if (!structure.IsListQuery)
+                {
+                    query += "," + WITHOUT_ARRAY_WRAPPER_SUFFIX;
+                }
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure)
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Valid point, I was thinking the same. 

There are 3 differences comparing with mssql
1. dwsql does not support mutation, so there is no `MultipleCreateOption` [available ](https://github.com/Azure/data-api-builder/blob/4d7207e5fb210cacfd68c573f6c1574df1bddff6/src/Core/Resolvers/MsSqlQueryBuilder.cs#L48C1-L49C1)
2. dwsql never adopts the [AddEscapeToLikeClauses](https://github.com/Azure/data-api-builder/blob/4d7207e5fb210cacfd68c573f6c1574df1bddff6/src/Core/Resolvers/MsSqlQueryBuilder.cs#L68C26-L68C48)
3. mssql can directly [append ](https://github.com/Azure/data-api-builder/blob/4d7207e5fb210cacfd68c573f6c1574df1bddff6/src/Core/Resolvers/MsSqlQueryBuilder.cs#L110)the FOR JSON to their sub-query and use it for recursive calls, for dwsql, it has to be treated differently. 

I've built a new base class `BaseTSqlQueryBuilder` for dwsql and mssql to better concise the code
created_at: 2025-03-26T23:15:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Done
created_at: 2025-03-27T00:41:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -353,6 +356,123 @@ protected virtual string Build(Predicate? predicate)
             }
         }
 
+        /// <summary>
+        /// Build the Json Path query needed to append to the main query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns></returns>
+        protected virtual string BuildJsonPath(SqlQueryStructure structure)
path: src/Core/Resolvers/BaseSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Centralizing the functions so it can be used or override by both dwsql and mssql query builder.
created_at: 2025-03-27T00:56:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,132 @@
+using Azure.DataApiBuilder.Config.ObjectModel;
+using Azure.DataApiBuilder.Core.Models;
+
+namespace Azure.DataApiBuilder.Core.Resolvers
+{
+    /// <summary>
+    /// Base query builder class for T-SQL engine
+    /// Can be used by dwsql and mssql
+    /// </summary>
+    public abstract class BaseTSqlQueryBuilder : BaseSqlQueryBuilder
path: src/Core/Resolvers/BaseTSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: I've built a new base class `BaseTSqlQueryBuilder` for dwsql and mssql to better concise the code
created_at: 2025-03-27T16:56:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,8 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
                                     bool AllowIntrospection = true,
                                     int? DepthLimit = null,
                                     MultipleMutationOptions? MultipleMutationOptions = null,
-                                    bool EnableAggregation = true)
+                                    bool EnableAggregation = true,
+                                    bool EnableDwNto1JoinOpt = false)
path: src/Config/ObjectModel/GraphQLRuntimeOptions.cs
user: login: JerryNixon
body: ```json
{
    "feature-flags": {
        "enable-sqldw-join-rewrite": true
    }
    "runtime": {
        ...
    }
}
```
Great. Let's not document these or add them to `dab validate` or `dab config`. I think feature flags are temporary and might be removed later. So, docs would confuse the matter. I think updating JSON schema for `feature-flags` still makes sense so schema validation can still pass. Meaning, allowing /anything/ inside `feature-flags`.
created_at: 2025-03-27T20:29:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +113,17 @@ internal GraphQLRuntimeOptionsConverter(bool replaceEnvVar)
                                 throw new JsonException($"Unexpected type of value entered for enable-aggregation: {reader.TokenType}");
                             }
 
+                            break;
+                        case "enable-dw-nto1joinopt":
path: src/Config/Converters/GraphQLRuntimeOptionsConverterFactory.cs
user: login: Aniruddh25
body: based on our discussion, this may not be needed. 
created_at: 2025-03-27T23:53:17Z
author_association: COLLABORATOR
diff_hunk: @@ -11,7 +11,8 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
                                     bool AllowIntrospection = true,
                                     int? DepthLimit = null,
                                     MultipleMutationOptions? MultipleMutationOptions = null,
-                                    bool EnableAggregation = true)
+                                    bool EnableAggregation = true,
+                                    bool EnableDwNto1JoinOpt = false)
path: src/Config/ObjectModel/GraphQLRuntimeOptions.cs
user: login: Aniruddh25
body: latest update: looks like we dont necessarily need this option altogether in the config/schema. So, @lingxiao-microsoft is looking at if he can get away without adding the `feature-flags` section altogether
created_at: 2025-03-27T23:55:25Z
author_association: COLLABORATOR
diff_hunk: @@ -53,6 +53,10 @@ public static void Init()
         VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableAggregation);
         // Ignore the EnableAggregation as that's unimportant from a test standpoint.
         VerifierSettings.IgnoreMember<GraphQLRuntimeOptions>(options => options.EnableAggregation);
+        // Ignore the EnableDwNto1JoinOpt as that's unimportant from a test standpoint.
+        VerifierSettings.IgnoreMember<RuntimeConfig>(options => options.EnableDwNto1JoinOpt);
path: src/Cli.Tests/ModuleInitializer.cs
user: login: lingxiao-microsoft
body: Checked with Jerry, comments provided in the other thread
created_at: 2025-03-28T20:47:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +113,17 @@ internal GraphQLRuntimeOptionsConverter(bool replaceEnvVar)
                                 throw new JsonException($"Unexpected type of value entered for enable-aggregation: {reader.TokenType}");
                             }
 
+                            break;
+                        case "enable-dw-nto1joinopt":
path: src/Config/Converters/GraphQLRuntimeOptionsConverterFactory.cs
user: login: lingxiao-microsoft
body: Removed the support of read/write from the dab config
created_at: 2025-03-29T00:20:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,8 @@ public record GraphQLRuntimeOptions(bool Enabled = true,
                                     bool AllowIntrospection = true,
                                     int? DepthLimit = null,
                                     MultipleMutationOptions? MultipleMutationOptions = null,
-                                    bool EnableAggregation = true)
+                                    bool EnableAggregation = true,
+                                    bool EnableDwNto1JoinOpt = false)
path: src/Config/ObjectModel/GraphQLRuntimeOptions.cs
user: login: lingxiao-microsoft
body: Thanks for the feedback! Done. Updated the PR.

To summarize the changes
1. Removed the support of applying this flag from dab config.
2. Used `FeatureFlags` class as the container for ephemeral flags that are passed into the GraphQLRuntimeOptions. In this way, every time when a new flag is needed for the same purpose, we don't need to change the signature every time. 
3. When we're confident about the change, we can flip the flag in the `FeatureFlags` container as always True so that the rest of DAB users can benefit from it. 

@Aniruddh25 @vadeveka @rohkhann please review it again when you have some time!
created_at: 2025-03-29T00:26:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -341,7 +344,8 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
                     break;
                 case TestCategory.DWSQL:
                     Mock<ILogger<MsSqlQueryExecutor>> DwSqlQueryExecutorLogger = new();
-                    _queryBuilder = new DwSqlQueryBuilder();
+                    // enable the feature by default for testing purpose. 
+                    _queryBuilder = new DwSqlQueryBuilder(enableNto1JoinOpt: true);
path: src/Service.Tests/SqlTests/SqlTestBase.cs
user: login: Aniruddh25
body: If we keep it enabled all the time, we are loosing the ability to test when it is disabled. Can we run all the tests, when it is disabled as well?
created_at: 2025-04-01T16:48:03Z
author_association: COLLABORATOR
diff_hunk: @@ -35,16 +44,155 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: Aniruddh25
body: Name is added but the description of the parameter is still not added. e.g.
```suggestion
        /// <param name="structure">Sql query structure to build query on</param>
```
created_at: 2025-04-01T16:51:45Z
author_association: COLLABORATOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: Aniruddh25
body: nit: mention what is the return value.
created_at: 2025-04-01T16:57:14Z
author_association: COLLABORATOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: Aniruddh25
body: Please use `StringBuilder query` for better performance while doing concatenation +=
created_at: 2025-04-01T16:58:16Z
author_association: COLLABORATOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += BuildJsonPath(structure);
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure)
+        {
+            string dataIdent = QuoteIdentifier(SqlQueryStructure.DATA_IDENT);
+            string fromSql = $"{QuoteIdentifier(structure.DatabaseObject.SchemaName)}.{QuoteIdentifier(structure.DatabaseObject.Name)} " +
+                             $"AS {QuoteIdentifier($"{structure.SourceAlias}")}{Build(structure.Joins)}";
+
+            fromSql += string.Join(
+                    "",
+                    structure.JoinQueries.Select(
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: Aniruddh25
body: Does this cater for the condition when `JoinQueries` is null?
created_at: 2025-04-01T17:09:20Z
author_association: COLLABORATOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += BuildJsonPath(structure);
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure)
+        {
+            string dataIdent = QuoteIdentifier(SqlQueryStructure.DATA_IDENT);
+            string fromSql = $"{QuoteIdentifier(structure.DatabaseObject.SchemaName)}.{QuoteIdentifier(structure.DatabaseObject.Name)} " +
+                             $"AS {QuoteIdentifier($"{structure.SourceAlias}")}{Build(structure.Joins)}";
+
+            fromSql += string.Join(
+                    "",
+                    structure.JoinQueries.Select(
+                        x => $" OUTER APPLY ({BuildWithJsonFunc(x.Value, true)}) AS {QuoteIdentifier(x.Key)}({dataIdent})"));
+
+            string predicates = BuildPredicates(structure);
+
+            string aggregations = BuildAggregationColumns(structure);
+
+            string query = $"SELECT TOP {structure.Limit()} {WrappedColumns(structure)} {aggregations}"
+                + $" FROM {fromSql}"
+                + $" WHERE {predicates}";
+
+            query += BuildGroupBy(structure);
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: Aniruddh25
body: Please use `StringBuilder` for `query`
created_at: 2025-04-01T17:10:10Z
author_association: COLLABORATOR
diff_hunk: @@ -129,6 +251,41 @@ private string BuildSqlQuery(SqlQueryStructure structure)
             return query;
         }
 
+        /// <summary>
+        /// Generate the columns selected and wrap them with JSON_OBJECT
+        /// Example:
+        /// SELECT JSON_OBJECT('id': [id]) 
+        ///  FROM
+        ///    (
+        ///        SELECT TOP 1 
+        ///            [table1].[id] AS [id]
+        ///        FROM
+        ///            [dbo].[book_website_placements] AS [table1]
+        ///        WHERE
+        ///            [table0].[id] = [table1].[book_id]
+        ///            AND[table1].[book_id] = [table0].[id]
+        ///        ORDER BY
+        ///            [table1].[id] ASC
+        ///    ) AS[table1]
+        /// </summary>
+        /// <param name="structure">Sql query structure to generate the columns with JSON_OBJECT</param>
+        /// <returns></returns>
+        private static string GenerateColumnsAsJsonObject(SqlQueryStructure structure)
+        {
+            List<string> columns = new();
+            foreach (LabelledColumn column in structure.Columns)
+            {
+                string col_value = $"\'{column.Label}\': [{column.TableAlias}].[{column.Label}]";
+                columns.Add(col_value);
+            }
+
+            string? joinedColumns = columns.Count > 1 ?
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: Aniruddh25
body: Is columns guaranteed to contain at least one column? If yes, do we need to define joinedColumns as `nullable`? 
If its only because of the use of `FirstOrDefault()`, can we instead use some other function to get the First such that we dont have to keep this nullable. 
created_at: 2025-04-01T17:15:31Z
author_association: COLLABORATOR
diff_hunk: @@ -97,6 +99,341 @@ ORDER BY [table0].[id] ASC
             await OneToOneJoinQuery(dwSqlQuery);
         }
 
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure the results are identical with using STRING_AGG
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async override Task DeeplyNestedManyToOneJoinQuery()
+        {
+            string dwSqlQuery = @"
+            SELECT COALESCE(
+                '[' + STRING_AGG(
+                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                    ', '
+                ) + ']', 
+                '[]'
+            )
+            FROM (
+                SELECT TOP 5 
+                    [table0].[title] AS [title], 
+                    ([table1_subq].[data]) AS [publishers]
+                FROM [dbo].[books] AS [table0]
+                OUTER APPLY (
+                    SELECT STRING_AGG(
+                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + ',' +
+                        N'""books"":' + ISNULL('""' + STRING_ESCAPE([books], 'json') + '""', 'null') + '}', 
+                        ', '
+                    )
+                    FROM (
+                        SELECT TOP 1 
+                            [table1].[name] AS [name], 
+                            (COALESCE([table2_subq].[data], '[]')) AS [books]
+                        FROM [dbo].[publishers] AS [table1]
+                        OUTER APPLY (
+                            SELECT COALESCE(
+                                '[' + STRING_AGG(
+                                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                                    ', '
+                                ) + ']', 
+                                '[]'
+                            )
+                            FROM (
+                                SELECT TOP 4 
+                                    [table2].[title] AS [title], 
+                                    ([table3_subq].[data]) AS [publishers]
+                                FROM [dbo].[books] AS [table2]
+                                OUTER APPLY (
+                                    SELECT STRING_AGG(
+                                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + ',' +
+                                        N'""books"":' + ISNULL('""' + STRING_ESCAPE([books], 'json') + '""', 'null') + '}', 
+                                        ', '
+                                    )
+                                    FROM (
+                                        SELECT TOP 1 
+                                            [table3].[name] AS [name], 
+                                            (COALESCE([table4_subq].[data], '[]')) AS [books]
+                                        FROM [dbo].[publishers] AS [table3]
+                                        OUTER APPLY (
+                                            SELECT COALESCE(
+                                                '[' + STRING_AGG(
+                                                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                                                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                                                    ', '
+                                                ) + ']', 
+                                                '[]'
+                                            )
+                                            FROM (
+                                                SELECT TOP 3 
+                                                    [table4].[title] AS [title], 
+                                                    ([table5_subq].[data]) AS [publishers]
+                                                FROM [dbo].[books] AS [table4]
+                                                OUTER APPLY (
+                                                    SELECT STRING_AGG(
+                                                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + '}', 
+                                                        ', '
+                                                    )
+                                                    FROM (
+                                                        SELECT TOP 1 
+                                                            [table5].[name] AS [name]
+                                                        FROM [dbo].[publishers] AS [table5]
+                                                        WHERE [table4].[publisher_id] = [table5].[id] 
+                                                          AND [table5].[id] = [table4].[publisher_id]
+                                                        ORDER BY [table5].[id] ASC
+                                                    ) AS [table5]
+                                                ) AS [table5_subq]([data])
+                                                WHERE [table4].[publisher_id] = [table3].[id]
+                                                ORDER BY [table4].[id] ASC
+                                            ) AS [table4]
+                                        ) AS [table4_subq]([data])
+                                        WHERE [table2].[publisher_id] = [table3].[id] 
+                                          AND [table3].[id] = [table2].[publisher_id]
+                                        ORDER BY [table3].[id] ASC
+                                    ) AS [table3]
+                                ) AS [table3_subq]([data])
+                                WHERE [table2].[publisher_id] = [table1].[id]
+                                ORDER BY [table2].[id] ASC
+                            ) AS [table2]
+                        ) AS [table2_subq]([data])
+                        WHERE [table0].[publisher_id] = [table1].[id] 
+                          AND [table1].[id] = [table0].[publisher_id]
+                        ORDER BY [table1].[id] ASC
+                    ) AS [table1]
+                ) AS [table1_subq]([data])
+                WHERE 1 = 1
+                ORDER BY [table0].[id] ASC
+            ) AS [table0]
+            ";
+
+            string graphQLQueryName = "books";
+            string graphQLQuery = @"{
+              books(first: 5) {
+                items {
+                  title
+                  publishers {
+                    name
+                    books(first: 4) {
+                      items {
+                        title
+                        publishers {
+                          name
+                          books(first: 3) {
+                            items {
+                              title
+                              publishers {
+                                name
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }";
+
+            JsonElement actualGraphQLResults = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false);
+            string cleanedActualGraphQLResults = SqlTestHelper.RemoveItemsKeyFromJson(actualGraphQLResults.GetProperty("items")).ToString();
+
+            string expected = await GetDatabaseResultAsync(dwSqlQuery);
+
+            SqlTestHelper.PerformTestEqualJsonStringsForNestedQueries(expected, cleanedActualGraphQLResults);
+        }
+
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure for To-N joins, the query builder will fallback to use STRING_AGG
+        /// And the results are consistent
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async Task OneToManyJoinQuery()
+        {
+            string dwSqlQuery = @"
+                SELECT 
+                    COALESCE(
+                        '[' + STRING_AGG(
+                            '{' + 
+                            N'""id"":' + ISNULL(STRING_ESCAPE(CONVERT(NVARCHAR(MAX), [id]), 'json'), 'null') + ',' +
+                            N'""reviews"":' + ISNULL('""' + STRING_ESCAPE([reviews], 'json') + '""', 'null') + 
+                            '}', 
+                            ', '
+                        ) + ']', 
+                        '[]'
+                    ) 
+                FROM 
+                    (
+                        SELECT TOP 2 
+                            [table0].[id] AS [id], 
+                            COALESCE([table1_subq].[data], '[]') AS [reviews]
+                        FROM 
+                            [dbo].[books] AS [table0]
+                        OUTER APPLY 
+                            (
+                                SELECT 
+                                    COALESCE(
+                                        '[' + STRING_AGG(
+                                            '{' + 
+                                            N'""content"":' + ISNULL('""' + STRING_ESCAPE([content], 'json') + '""', 'null') + 
+                                            '}', 
+                                            ', '
+                                        ) + ']', 
+                                        '[]'
+                                    ) 
+                                FROM 
+                                    (
+                                        SELECT TOP 100 
+                                            [table1].[content] AS [content]
+                                        FROM 
+                                            [dbo].[reviews] AS [table1]
+                                        WHERE 
+                                            [table1].[book_id] = [table0].[id]
+                                        ORDER BY 
+                                            [table1].[book_id] ASC, 
+                                            [table1].[id] ASC
+                                    ) AS [table1]
+                            ) AS [table1_subq]([data])
+                        WHERE 
+                            1 = 1
+                        ORDER BY 
+                            [table0].[id] ASC
+                    ) AS [table0]";
+
+            string graphQLQueryName = "books";
+            string graphQLQuery = @"
+               query {
+                  books (first: 2) {
+                    items {
+                      id,
+                      reviews {
+                        items {
+                          content
+                        }
+                      }
+                    }
+                  }
+                }";
+
+            JsonElement actual = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false);
+            JsonNode cleanedActual = SqlTestHelper.RemoveItemsKeyFromJson(actual.GetProperty("items"));
+
+            string expected = await GetDatabaseResultAsync(dwSqlQuery);
+
+            SqlTestHelper.PerformTestEqualJsonStringsForNestedQueries(expected, cleanedActual.ToString());
path: src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
user: login: Aniruddh25
body: Here, we are testing whether the results returned are same or not, but this doesnt test whether the query we generated used the JsonFunc or used the StringAgg. 

Can we test the query Structure created instead of the actual results when the feature is enabled? 
created_at: 2025-04-01T17:25:20Z
author_association: COLLABORATOR
diff_hunk: @@ -341,7 +344,8 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
                     break;
                 case TestCategory.DWSQL:
                     Mock<ILogger<MsSqlQueryExecutor>> DwSqlQueryExecutorLogger = new();
-                    _queryBuilder = new DwSqlQueryBuilder();
+                    // enable the feature by default for testing purpose. 
+                    _queryBuilder = new DwSqlQueryBuilder(enableNto1JoinOpt: true);
path: src/Service.Tests/SqlTests/SqlTestBase.cs
user: login: lingxiao-microsoft
body: Thanks for bring it up! Actually this line should be removed.  Even if we specify it here as `true`, the tests will still run as disabled.

The reasons are due to constraints below
1. When initializing the test base, the test will generate a runtime config in [memory](https://github.com/Azure/data-api-builder/blob/97c25dbfd9abd8507d9222b65a96c52f4fb27446/src/Service.Tests/SqlTests/SqlTestBase.cs#L130C1-L131C1)
![image](https://github.com/user-attachments/assets/55c34324-e97d-4bcb-87fb-87012e421739)

2. Within it, it will build a mock file system and convert the defined runtime config[ to JSON ](https://github.com/Azure/data-api-builder/blob/97c25dbfd9abd8507d9222b65a96c52f4fb27446/src/Service.Tests/TestHelper.cs#L306C88-L306C110)and load it to memory. 
![image](https://github.com/user-attachments/assets/193f3c73-1f5d-4560-84ac-bbb68714908e)

3. As it is invoking the `ToJSON` function, since we're completely removing the function of read/write from the dab-config file for the feature flag `enableNto1JoinOpt`, the property defined in the [RuntimeConfig ](https://github.com/Azure/data-api-builder/blob/eaf9501c010467ba5c682bd77bb9d32b0c4e9156/src/Config/ObjectModel/GraphQLRuntimeOptions.cs#L32)will be ignored after the json serialization is invoked. 

4. As a result of that, even if we define a `True` or `False` value in the RuntimeConfig for testing, it will be overridden as the serialization excluded it.  

I've done two things to ensure the chance of regressions are avoided
1. Currently in `FeatureFlag` class, the default value is disabled, which means when running the integration tests in pipeline, it is running against the scenario when the flag is disabled. All tests are passing
2. I've forced the value as `enabled` and pushed the changes to let the integration tests ran against it, there were 5 [failed ](https://dev.azure.com/sqldab/Data%20API%20builder%20Dependency%20Packages/_build/results?buildId=9657&view=logs&j=2d2b3007-3c5c-5840-9bb0-2b1ea49925f3&t=f2696cc9-a35b-5158-31dc-9689759fe573&l=335) tests in `DWQueryBuilderTest `  which I found it was due to when running the DW SQL tests, a MSSQL engine is used instead and in SQL 2019, the `JSON_OBJECT` function is not supported. 
3. I've ran the DWQueryBuilderTest locally and all of them are passing
![image](https://github.com/user-attachments/assets/ae22f8eb-2950-4bea-a141-6d7515994a54)

5. This can be fixed later once we've flipped the values as `True` in DAB and use the right engine for integration testing for DW. And since it is disabled by default right now, we can still ensure regressions can be detected.

This is so far the best workaround I could think of having an ephemeral feature flag which serves the purpose and at the meantime not exposing it in dab config files. 

Please let me know if that does not make sense
created_at: 2025-04-01T20:44:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Done!
created_at: 2025-04-01T20:44:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Updated the dwsql and mssql query builder for consistency. 
created_at: 2025-04-01T20:44:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += BuildJsonPath(structure);
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure)
+        {
+            string dataIdent = QuoteIdentifier(SqlQueryStructure.DATA_IDENT);
+            string fromSql = $"{QuoteIdentifier(structure.DatabaseObject.SchemaName)}.{QuoteIdentifier(structure.DatabaseObject.Name)} " +
+                             $"AS {QuoteIdentifier($"{structure.SourceAlias}")}{Build(structure.Joins)}";
+
+            fromSql += string.Join(
+                    "",
+                    structure.JoinQueries.Select(
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Yes - it is initialized as an empty object https://github.com/Azure/data-api-builder/blob/97c25dbfd9abd8507d9222b65a96c52f4fb27446/src/Core/Resolvers/Sql%20Query%20Structures/SqlQueryStructure.cs#L556C1-L557C1
created_at: 2025-04-01T20:45:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,16 +42,131 @@ public override string QuoteIdentifier(string ident)
         /// </summary>
         public string Build(SqlQueryStructure structure)
         {
-            return BuildAsJson(structure);
+            if (this._enableNto1JoinOpt && HasToOneOrNoRelation(structure, false))
+            {
+                return BuildWithJsonFunc(structure, isSubQuery: false);
+            }
+            else
+            {
+                return BuildWithStringAgg(structure);
+            }
+        }
+
+        /// <summary>
+        /// Recursively checks the structure to see if
+        /// 1. It only has to-1 relations
+        /// 2. It does not have any relations, which means it is a simple query against one table
+        /// We should apply the json funcs instead of string_agg for both cases
+        /// </summary>
+        /// <param name="structure"></param>
+        /// <param name="isSubQuery"></param>
+        /// <returns></returns>
+        private static bool HasToOneOrNoRelation(SqlQueryStructure structure, bool isSubQuery)
+        {
+            if (structure?.JoinQueries?.Values == null)
+            {
+                // if there is no sub-queries, use JSON PATH for performance improvements as well
+                return true;
+            }
+
+            if (structure.IsListQuery && isSubQuery)
+            {
+                // If it is a list query in sub-query, then it is not a to-1 relation
+                return false;
+            }
+
+            foreach (SqlQueryStructure subQueries in structure.JoinQueries.Values)
+            {
+                if (!HasToOneOrNoRelation(subQueries, true))
+                {
+                    return false;
+                }
+            }
+
+            return true;
         }
 
         /// <summary>
-        /// Builds the sql query that will return the json result for the sql query.
+        /// Build the query recursively with
+        /// 1. JSON PATH for outer query
+        /// 2. JSON OBJECT for inner query
+        /// Example:
+        /// SELECT TOP M 
+        ///    <Columns>
+        /// FROM
+        ///    <Tables>
+        /// OUTER APPLY
+        ///    <Sub-query-tables>
+        /// WHERE
+        ///    <Conditions>
+        /// ORDER BY
+        ///    <Conditions>
+        /// FOR JSON PATH, INCLUDE_NULL_VALUES;
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <param name="isSubQuery">Used for recursive call purpose to generated different queries for sub-queries</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure, bool isSubQuery)
+        {
+            string query;
+
+            if (isSubQuery)
+            {
+                // convert the columns to JSON Object for sub queries
+                string columns = GenerateColumnsAsJsonObject(structure);
+                string fromSql = $"{BuildWithJsonFunc(structure)}";
+                query = $"SELECT {columns}"
+                    + $" FROM ({fromSql}) AS {QuoteIdentifier(structure.SourceAlias)}";
+            }
+            else
+            {
+                query = BuildWithJsonFunc(structure);
+                query += BuildJsonPath(structure);
+            }
+
+            return query;
+        }
+
+        /// <summary>
+        /// Helper function for BuildWithJsonFunc that generates "FROM" portion of the query
+        /// </summary>
+        /// <param name="structure">Sql query structure to build query on</param>
+        /// <returns></returns>
+        private string BuildWithJsonFunc(SqlQueryStructure structure)
+        {
+            string dataIdent = QuoteIdentifier(SqlQueryStructure.DATA_IDENT);
+            string fromSql = $"{QuoteIdentifier(structure.DatabaseObject.SchemaName)}.{QuoteIdentifier(structure.DatabaseObject.Name)} " +
+                             $"AS {QuoteIdentifier($"{structure.SourceAlias}")}{Build(structure.Joins)}";
+
+            fromSql += string.Join(
+                    "",
+                    structure.JoinQueries.Select(
+                        x => $" OUTER APPLY ({BuildWithJsonFunc(x.Value, true)}) AS {QuoteIdentifier(x.Key)}({dataIdent})"));
+
+            string predicates = BuildPredicates(structure);
+
+            string aggregations = BuildAggregationColumns(structure);
+
+            string query = $"SELECT TOP {structure.Limit()} {WrappedColumns(structure)} {aggregations}"
+                + $" FROM {fromSql}"
+                + $" WHERE {predicates}";
+
+            query += BuildGroupBy(structure);
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: Updated the PR. 
created_at: 2025-04-01T20:45:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +251,41 @@ private string BuildSqlQuery(SqlQueryStructure structure)
             return query;
         }
 
+        /// <summary>
+        /// Generate the columns selected and wrap them with JSON_OBJECT
+        /// Example:
+        /// SELECT JSON_OBJECT('id': [id]) 
+        ///  FROM
+        ///    (
+        ///        SELECT TOP 1 
+        ///            [table1].[id] AS [id]
+        ///        FROM
+        ///            [dbo].[book_website_placements] AS [table1]
+        ///        WHERE
+        ///            [table0].[id] = [table1].[book_id]
+        ///            AND[table1].[book_id] = [table0].[id]
+        ///        ORDER BY
+        ///            [table1].[id] ASC
+        ///    ) AS[table1]
+        /// </summary>
+        /// <param name="structure">Sql query structure to generate the columns with JSON_OBJECT</param>
+        /// <returns></returns>
+        private static string GenerateColumnsAsJsonObject(SqlQueryStructure structure)
+        {
+            List<string> columns = new();
+            foreach (LabelledColumn column in structure.Columns)
+            {
+                string col_value = $"\'{column.Label}\': [{column.TableAlias}].[{column.Label}]";
+                columns.Add(col_value);
+            }
+
+            string? joinedColumns = columns.Count > 1 ?
path: src/Core/Resolvers/DWSqlQueryBuilder.cs
user: login: lingxiao-microsoft
body: To my best understanding, it should at least have one column, as in
1. It only applies when N-1 join happens
2. Clients needs to specify at least one column for the leaf 
3. If it is a nested query (>=3 table joins), there will always be a column in 2nd layer created for aggregating the results from the 3rd table. 

I've updated the PR to avoid making the variable as nullable 
created_at: 2025-04-01T20:45:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,6 +99,341 @@ ORDER BY [table0].[id] ASC
             await OneToOneJoinQuery(dwSqlQuery);
         }
 
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure the results are identical with using STRING_AGG
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async override Task DeeplyNestedManyToOneJoinQuery()
+        {
+            string dwSqlQuery = @"
+            SELECT COALESCE(
+                '[' + STRING_AGG(
+                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                    ', '
+                ) + ']', 
+                '[]'
+            )
+            FROM (
+                SELECT TOP 5 
+                    [table0].[title] AS [title], 
+                    ([table1_subq].[data]) AS [publishers]
+                FROM [dbo].[books] AS [table0]
+                OUTER APPLY (
+                    SELECT STRING_AGG(
+                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + ',' +
+                        N'""books"":' + ISNULL('""' + STRING_ESCAPE([books], 'json') + '""', 'null') + '}', 
+                        ', '
+                    )
+                    FROM (
+                        SELECT TOP 1 
+                            [table1].[name] AS [name], 
+                            (COALESCE([table2_subq].[data], '[]')) AS [books]
+                        FROM [dbo].[publishers] AS [table1]
+                        OUTER APPLY (
+                            SELECT COALESCE(
+                                '[' + STRING_AGG(
+                                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                                    ', '
+                                ) + ']', 
+                                '[]'
+                            )
+                            FROM (
+                                SELECT TOP 4 
+                                    [table2].[title] AS [title], 
+                                    ([table3_subq].[data]) AS [publishers]
+                                FROM [dbo].[books] AS [table2]
+                                OUTER APPLY (
+                                    SELECT STRING_AGG(
+                                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + ',' +
+                                        N'""books"":' + ISNULL('""' + STRING_ESCAPE([books], 'json') + '""', 'null') + '}', 
+                                        ', '
+                                    )
+                                    FROM (
+                                        SELECT TOP 1 
+                                            [table3].[name] AS [name], 
+                                            (COALESCE([table4_subq].[data], '[]')) AS [books]
+                                        FROM [dbo].[publishers] AS [table3]
+                                        OUTER APPLY (
+                                            SELECT COALESCE(
+                                                '[' + STRING_AGG(
+                                                    '{' + N'""title"":' + ISNULL('""' + STRING_ESCAPE([title], 'json') + '""', 'null') + ',' +
+                                                    N'""publishers"":' + ISNULL('""' + STRING_ESCAPE([publishers], 'json') + '""', 'null') + '}', 
+                                                    ', '
+                                                ) + ']', 
+                                                '[]'
+                                            )
+                                            FROM (
+                                                SELECT TOP 3 
+                                                    [table4].[title] AS [title], 
+                                                    ([table5_subq].[data]) AS [publishers]
+                                                FROM [dbo].[books] AS [table4]
+                                                OUTER APPLY (
+                                                    SELECT STRING_AGG(
+                                                        '{' + N'""name"":' + ISNULL('""' + STRING_ESCAPE([name], 'json') + '""', 'null') + '}', 
+                                                        ', '
+                                                    )
+                                                    FROM (
+                                                        SELECT TOP 1 
+                                                            [table5].[name] AS [name]
+                                                        FROM [dbo].[publishers] AS [table5]
+                                                        WHERE [table4].[publisher_id] = [table5].[id] 
+                                                          AND [table5].[id] = [table4].[publisher_id]
+                                                        ORDER BY [table5].[id] ASC
+                                                    ) AS [table5]
+                                                ) AS [table5_subq]([data])
+                                                WHERE [table4].[publisher_id] = [table3].[id]
+                                                ORDER BY [table4].[id] ASC
+                                            ) AS [table4]
+                                        ) AS [table4_subq]([data])
+                                        WHERE [table2].[publisher_id] = [table3].[id] 
+                                          AND [table3].[id] = [table2].[publisher_id]
+                                        ORDER BY [table3].[id] ASC
+                                    ) AS [table3]
+                                ) AS [table3_subq]([data])
+                                WHERE [table2].[publisher_id] = [table1].[id]
+                                ORDER BY [table2].[id] ASC
+                            ) AS [table2]
+                        ) AS [table2_subq]([data])
+                        WHERE [table0].[publisher_id] = [table1].[id] 
+                          AND [table1].[id] = [table0].[publisher_id]
+                        ORDER BY [table1].[id] ASC
+                    ) AS [table1]
+                ) AS [table1_subq]([data])
+                WHERE 1 = 1
+                ORDER BY [table0].[id] ASC
+            ) AS [table0]
+            ";
+
+            string graphQLQueryName = "books";
+            string graphQLQuery = @"{
+              books(first: 5) {
+                items {
+                  title
+                  publishers {
+                    name
+                    books(first: 4) {
+                      items {
+                        title
+                        publishers {
+                          name
+                          books(first: 3) {
+                            items {
+                              title
+                              publishers {
+                                name
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }";
+
+            JsonElement actualGraphQLResults = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false);
+            string cleanedActualGraphQLResults = SqlTestHelper.RemoveItemsKeyFromJson(actualGraphQLResults.GetProperty("items")).ToString();
+
+            string expected = await GetDatabaseResultAsync(dwSqlQuery);
+
+            SqlTestHelper.PerformTestEqualJsonStringsForNestedQueries(expected, cleanedActualGraphQLResults);
+        }
+
+        /// <summary>
+        /// DwNTo1JoinOpt is enabled by default for testing
+        /// Below test case will ensure for To-N joins, the query builder will fallback to use STRING_AGG
+        /// And the results are consistent
+        /// </summary>
+        /// <returns></returns>
+        [TestMethod]
+        public async Task OneToManyJoinQuery()
+        {
+            string dwSqlQuery = @"
+                SELECT 
+                    COALESCE(
+                        '[' + STRING_AGG(
+                            '{' + 
+                            N'""id"":' + ISNULL(STRING_ESCAPE(CONVERT(NVARCHAR(MAX), [id]), 'json'), 'null') + ',' +
+                            N'""reviews"":' + ISNULL('""' + STRING_ESCAPE([reviews], 'json') + '""', 'null') + 
+                            '}', 
+                            ', '
+                        ) + ']', 
+                        '[]'
+                    ) 
+                FROM 
+                    (
+                        SELECT TOP 2 
+                            [table0].[id] AS [id], 
+                            COALESCE([table1_subq].[data], '[]') AS [reviews]
+                        FROM 
+                            [dbo].[books] AS [table0]
+                        OUTER APPLY 
+                            (
+                                SELECT 
+                                    COALESCE(
+                                        '[' + STRING_AGG(
+                                            '{' + 
+                                            N'""content"":' + ISNULL('""' + STRING_ESCAPE([content], 'json') + '""', 'null') + 
+                                            '}', 
+                                            ', '
+                                        ) + ']', 
+                                        '[]'
+                                    ) 
+                                FROM 
+                                    (
+                                        SELECT TOP 100 
+                                            [table1].[content] AS [content]
+                                        FROM 
+                                            [dbo].[reviews] AS [table1]
+                                        WHERE 
+                                            [table1].[book_id] = [table0].[id]
+                                        ORDER BY 
+                                            [table1].[book_id] ASC, 
+                                            [table1].[id] ASC
+                                    ) AS [table1]
+                            ) AS [table1_subq]([data])
+                        WHERE 
+                            1 = 1
+                        ORDER BY 
+                            [table0].[id] ASC
+                    ) AS [table0]";
+
+            string graphQLQueryName = "books";
+            string graphQLQuery = @"
+               query {
+                  books (first: 2) {
+                    items {
+                      id,
+                      reviews {
+                        items {
+                          content
+                        }
+                      }
+                    }
+                  }
+                }";
+
+            JsonElement actual = await ExecuteGraphQLRequestAsync(graphQLQuery, graphQLQueryName, isAuthenticated: false);
+            JsonNode cleanedActual = SqlTestHelper.RemoveItemsKeyFromJson(actual.GetProperty("items"));
+
+            string expected = await GetDatabaseResultAsync(dwSqlQuery);
+
+            SqlTestHelper.PerformTestEqualJsonStringsForNestedQueries(expected, cleanedActual.ToString());
path: src/Service.Tests/SqlTests/GraphQLQueryTests/DwSqlGraphQLQueryTests.cs
user: login: lingxiao-microsoft
body: There are reasons that blocking us to test when the feature is disabled / enabled, I've replied with some details in https://github.com/Azure/data-api-builder/pull/2631#discussion_r2023652394

We can add more tests once this feature is enabled by default in DAB
created_at: 2025-04-01T20:46:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -341,7 +344,8 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
                     break;
                 case TestCategory.DWSQL:
                     Mock<ILogger<MsSqlQueryExecutor>> DwSqlQueryExecutorLogger = new();
-                    _queryBuilder = new DwSqlQueryBuilder();
+                    // enable the feature by default for testing purpose. 
+                    _queryBuilder = new DwSqlQueryBuilder(enableNto1JoinOpt: true);
path: src/Service.Tests/SqlTests/SqlTestBase.cs
user: login: Aniruddh25
body: Makes sense, thanks for testing this. Could you please follow up with better testing of DW - by checking how to add a DW instance?
created_at: 2025-04-02T00:02:08Z
author_association: COLLABORATOR
diff_hunk: @@ -341,7 +344,8 @@ protected static void SetUpSQLMetadataProvider(RuntimeConfigProvider runtimeConf
                     break;
                 case TestCategory.DWSQL:
                     Mock<ILogger<MsSqlQueryExecutor>> DwSqlQueryExecutorLogger = new();
-                    _queryBuilder = new DwSqlQueryBuilder();
+                    // enable the feature by default for testing purpose. 
+                    _queryBuilder = new DwSqlQueryBuilder(enableNto1JoinOpt: true);
path: src/Service.Tests/SqlTests/SqlTestBase.cs
user: login: lingxiao-microsoft
body: Per discussion offline, we should use the right SQL version when the flag is enabled in DAB
created_at: 2025-04-02T18:44:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: lingxiao-microsoft
created_at: 2025-03-28T20:45:21Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-03-28T20:45:41Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-03-28T22:28:15Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-03-28T22:28:36Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-03-29T00:18:05Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-03-29T00:18:24Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-03-31T17:29:42Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-03-31T17:30:04Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-04-01T21:46:47Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-01T21:47:07Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-04-01T22:33:40Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-01T22:34:08Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-04-02T00:08:11Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-02T00:08:37Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-04-02T16:12:03Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-02T16:12:28Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-04-02T16:37:05Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-02T16:37:26Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: lingxiao-microsoft
created_at: 2025-04-02T17:16:36Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-02T17:16:55Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE