I was writing some SQL that returned the description of a product based on a given code. I prepared my query with the assumption that codes with different capitalization could coexist. However, while filtering for results of my main table, I would like for my result to be case-sensitive. That is, searching some lowercase code would only return lowercase codes, and not the uppercase equivalents.
What I came to find, however, is that depending on the capitalization of the WHERE clause's condition, the result would change. I have looked into each table, and each has a different collation. I have tested with a RIGHT JOIN instead, and it properly joined the tables in both character cases. Furthermore, there was never a need to check for different cases: all codes should be uppercase by our system's standards and validations. So while fixing this was as simple as making sure my WHERE clause was uppercase, I was still left wondering why the query returned different results. I was taught that, during the SQL's query processing, the JOIN clause would run before the WHERE clause, ensuring that the latter would look through the joined result.
To reproduce this bug, firstly, I created a database with DEFAULT CHARACTER SET UTF8 COLLATION UNICODE_CI_AI.
Then, I created each table as such:
CREATE TABLE MAIN_TABLE (
val VARCHAR(40) NOT NULL PRIMARY KEY,
code VARCHAR(40) NOT NULL COLLATE UNICODE_CI
);
CREATE TABLE PRODUCTS (
name VARCHAR(40) NOT NULL PRIMARY KEY,
code VARCHAR(40) NOT NULL COLLATE UNICODE
);
I then inserted the following test entries:
INSERT INTO MAIN_TABLE (val, code) VALUES ('This value is returned', 'ABC');
INSERT INTO PRODUCTS (name, code) VALUES ('My product', 'ABC');
And lastly, I performed the following query:
SELECT * FROM MAIN_TABLE
LEFT JOIN PRODUCTS
ON MAIN_TABLE.code = PRODUCTS.code
WHERE MAIN_TABLE.code LIKE '%abc%'
Which resulted in:
MAIN_TABLE.code | MAIN_TABLE.val | PRODUCTS.code | PRODUCTS.name
----------------+------------------------+---------------+---------------
ABC | This value is returned | null | null
Notice that, although my query did find a result in MAIN_TABLE, the LEFT JOIN result came as null.
However, the exact same query, change the WHERE clause, returns a different result. So the query:
SELECT * FROM MAIN_TABLE
LEFT JOIN PRODUCTS
ON MAIN_TABLE.code = PRODUCTS.code
WHERE MAIN_TABLE.code LIKE '%ABC%'
Ended up returning:
MAIN_TABLE.code | MAIN_TABLE.val | PRODUCTS.code | PRODUCTS.name
----------------+------------------------+---------------+---------------
ABC | This value is returned | ABC | My product
I have wondered--Was my understanding of the order of operations wrong? Does the database server read through the query, identifies that the WHERE clause's column (MAIN_TABLE.code) is the same one from the JOIN, and then changes how the JOIN is processed internally (for optimization or otherwise)? Or was this merely a bug with how Firebird interprets queries? I did expect some weird behavior, given the different collations, but I wasn't sure if it was a feature of sorts.
Why did my WHERE clause affect my LEFT JOIN?
I am not looking for ways to fix it, as I have found plenty - changing the collations, uppercasing my query, validating the code beforehand, etc.
My database runs on Firebird 3.0. I checked options for showing all messages, checked logs, and checked the query variants that did work. I saw nothing there that could give me an idea of why that happened.