1

I'm converting a Visual Basic API to .NET 8 using the same SQL Server query with the same parameters, and I noticed that some rows are not being displayed in the response object.

The query uses UNION ALL and the missing rows only exist in the second SELECT statement (Deleted_Appointments table):

SELECT
    A.Appointment_Id,
    A.Appointment_Date,
    C.Client_Id,
    C.Name AS Client_Name,
    S.Service_Id,
    S.Description AS Service_Name,
    CO.Name AS Company_Name,
    CO.Address AS Company_Address,
    '' AS Deleted_By
FROM Appointments A (NOLOCK)
LEFT JOIN Appointment_Services ApS (NOLOCK) ON ApS.Appointment_Id = A.Appointment_Id
LEFT JOIN Services S (NOLOCK) ON S.Service_Id = ApS.Service_Id
LEFT JOIN Clients C (NOLOCK) ON C.Client_Id = A.Client_Id
LEFT JOIN Companies CO (NOLOCK) ON CO.Company_Id = A.Company_Id
LEFT JOIN Users U (NOLOCK) ON U.Id = A.User_Id
WHERE A.Appointment_Date BETWEEN '07/01/2024 00:00:00' AND '07/31/2024 23:59:59'

UNION ALL

SELECT
    DA.Appointment_Id,
    DA.Appointment_Date,
    C.Client_Id,
    C.Name AS Client_Name,
    S.Service_Id,
    S.Description AS Service_Name,
    CO.Name AS Company_Name,
    CO.Address AS Company_Address,
    U.Name AS Deleted_By
FROM Deleted_Appointments DA (NOLOCK)
LEFT JOIN Deleted_Appointment_Services DAS (NOLOCK) ON DAS.Appointment_Id = DA.Appointment_Id
LEFT JOIN Services S (NOLOCK) ON S.Service_Id = DAS.Service_Id
LEFT JOIN Clients C (NOLOCK) ON C.Client_Id = DA.Client_Id
LEFT JOIN Companies CO (NOLOCK) ON CO.Company_Id = DA.Company_Id
LEFT JOIN Users U (NOLOCK) ON U.Id = DA.User_Id
WHERE DA.Appointment_Date BETWEEN '07/01/2024 00:00:00' AND '07/31/2024 23:59:59'

The C# code using Dapper's QueryAsync to retrieve the data:

public async Task<List<AppointmentsResponse>> GetAppointments(
    DateTime startDate,
    DateTime endDate,
    List<long> ClientIds,
    int Status,
    bool IsMobile
)
{
    var sql = new StringBuilder();
    sql.AppendLine(/* the above query */);

    // These statements are repeated for the
    // second SELECT statement with the correct
    // table alias. I know... bad code. It will be fixed.
    if (IsMobile)
        sql.AppendLine("AND A.IsMobile = 1");

    if (ClientIds.Count > 0)
    {
        string inClause = string.Join(", ", ClientIds);
        sql.AppendLine($"AND A.Client_Id IN ({inClause})");
    }

    if (Status > 0)
        sql.AppendLine($"AND A.Appointment_Status = {Status}");

    using var con = connectionFactory.CreateConnection();
    var result = await con.QueryAsync<AppointmentsResponse>(sql.ToString(), new { startDate, endDate });

    return result.ToList();
}

I've tried using foreach to loop through each row and check if they were being returned correctly, and the missing rows are skipped entirely. I've also tried using QueryMultipleAsync but the result remained the same.

I've compared the same query with the one from the Visual Basic API and they both return the data as it should when executed in Microsoft's SSMS.

The VB implementation also returns it correctly.

EDIT: Added the complete query and C# parameters for better context.

10
  • 2
    The nolock hints aren't the automatic "go faster" trick you may have heard. You should remove them. Also, the format for the timestamps is wrong. SQL Server can be particular about this. It should be either 20240701 00:00:00 or 2024-07-01T00:00:00. Oh, and instead of 23:59:59 for the end of the range, you should use an exclusive boundary (< instead of <=) for the start of the next day. Commented Jul 31, 2024 at 18:48
  • But as to the question: I don't really think this will work, but just to be sure, try wrapping the whole UNION in an outer SELECT: SELECT * FROM ( SELECT ... FROM ... UNION ALL SELECT ... FROM ... ) t Commented Jul 31, 2024 at 18:54
  • Oh, it there's no ON for the JOINs. This could be an oversite, but it could be more. Commented Jul 31, 2024 at 18:58
  • Using a StringBuilder for an SQL query is a red flag for me. Are you building it dynamically, if so why? Can you not use proper parameters instead? If it's dynamic can you show the full code that builds it? Commented Aug 1, 2024 at 8:11
  • @JoelCoehoorn Tried wrapping it as well as using a CTE, but neither of them worked. Running each statement separately in SSMS still displays the data correctly. As for the NOLOCK hints, I'll keep that in mind. I just left it in query since I basically just copy and pasted it from VB.NET. Commented Aug 1, 2024 at 11:30

1 Answer 1

0

Won't fully solve the issue, so just community wiki, but I'd write the SQL more like this:

SELECT
    A.Appointment_Id,
    A.Appointment_Date,
    -- other columns
FROM Appointments A 
LEFT JOIN Some_Table ST ON ST.Id = A.Appointment_Id
-- more tables using LEFT JOIN
WHERE A.Appointment_Date >= '20240701 00:00:00' AND A.Appointment_Date < '20240801 00:00:00'

UNION ALL

SELECT
    DA.Appointment_Id,
    DA.Appointment_Date,
    -- other columns
FROM Deleted_Appointments DA (NOLOCK)
LEFT JOIN Some_Table ST ON ST.Id = DA.Appointment_Id
-- more tables using LEFT JOIN
WHERE DA.Appointment_Date >= '20240701 00:00:00' AND DA.Appointment_Date < '20240801 00:00:00'

It might be further tempting to do the union (and date filters) on the Appointments and Deleted_Appointments tables in a subquery or CTE before the joins. This could simplify the query. But without knowing indexes, the contents of the rest of the query, and selectivity or size of those source tables vs the rest of the data it's hard to know if it would be helpful.

Oh, and don't look past removing the (NOLOCK). It's not my first guess, but it is actually possible this was the source of your issue.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.