Mysql where clause syntax - mysql

I was running through some of our code at work and came across this structure for an sql query and was sure it was a typo in the variables of our PHP, I ran it and it works.
Select column from table where value = column;
Anyone I know was always taught the correct syntax is:
Select column from table where column = value;
Is there any reason for this being legal, other than SQL just checks are both sides of an equation equal to each other?
I'm more posting this because I found it really interesting, like a 'the more you know' kind of thing.

The equality operator (=) is symmetric - if a=b is true, then so is b=a. a and b can be column names, values or complex expressions. It's common to use the form column=value, but syntactically speaking, it's completely equivalent to value=column.

Yes, you have it right sql just checks both sides of an equation. The equation could even contain a column on neither side! such as
SELECT column from table where 1=2;

The syntax of an SQL query is :
SELECT what_to_select
FROM which_table
WHERE conditions_to_satisfy;
You can use any condition you want, for example :
SELECT * FROM table WHERE 1
will return you all the rows
while
SELECT * FROM table WHERE 0
will return you nothing
after a WHERE you must have a condition, nothing else

Related

how to check if a column has truthy value using laravel's eloquent

Suppose I have Post model that has is_verified column with smallint datatype, how can I get all records that is verified? One thing to do this is using this:
Post::where('is_verified', true)->get();
The code above will produce the following query:
select * from `posts` where `posts`.`is_verified` = true
... which will get me all verified Post records; in note that is_verified on all existing records is either 0 or 1.
However, after I get myself curious and try to manually change some is_verified's record value from 1 to another truthy number e.g. 2, the above eloquent query didn't work as expected anymore: records with is_verified value of 2 didn't get retrieved.
I tried to execute the sql query directly from HeidiSQL as well, but it was just the same. Then I tried to change the = in the sql query to is, and now it's working as expected i.e. all records with truthy is_verified get retrieved:
select * from `posts` where `posts`.`is_verified` is true
So my questions are:
Does the above behaviour is correct and expected?
How can I execute the last sql query in eloquent? One thing I can think of is where('is_verified', '!=', 0) but that feels weird in terms of readability especially when the query is pretty long and a bit complicated
As I stated before, the is_verified column is a smallint. Does this affects the behaviour? Because this conversation here states that boolean column datatype is typically tinyint, not smallint.
And that's it. Thank you in advance!
It is not the correct way to handle boolean values, you shouldn't save boolean columns as smallint, you can use the explicit boolean column type as described in the documentation.
Once you setup the boolean field correctly the logic you have in place will work. So Post::where('is_verified', true)->get(); will return the expected results.
Yes, the problem is the smallint column type, if you put tinyint it also should work like the boolean column. You can read more about the differences here.
After doing some deeper digging, I would like to write down the things I've found:
I have updated my mysql to the newest version as of now (v8) and boolean datatype defined in migration results in tinyint(1) in the db. This is happening turns out because in mysql bool or boolean are actually just the synonyms of tinyint(1), so that was a totally normal behaviour, not due to lower-version issues.
I found #dz0nika answer that states that smallint and tinyint results in different behaviour in the query to be quite incorrect. The two datatypes simply differ in terms of byte-size while storing integer value.
As of mysql documentation, it is stated that:
A value of zero is considered false. Nonzero values are considered true.
But also that:
However, the values TRUE and FALSE are merely aliases for 1 and 0, respectively.
Meaning that:
select * from `posts` where `posts`.`is_verified` = true;
Is the same as
select * from `posts` where `posts`.`is_verified` = 1;
Thus the query will only get Post records with is_verified value of 1.
To get Post records with truthy is_verified value, wether 1, or 2, or 3, etc; use is instead of = in the query:
select * from `posts` where `posts`.`is_verified` is true;
You can read more about these informations here and here (look for the "boolean" part)
So, how about the eloquent query? How can we get Post with truthy is_verified using eloquent?
I still don't know what's best. But instead of using where('is_verified', '!=', 0) as I stated in my question, I believe it's better to use whereRaw() instead:
Post::whereRaw('posts.is_verified is true')->get();
If you found this information to be quite missing or incorrect, please kindly reply. Your opinion is much appreciated.

How can a SET column be queried in MySQL while ignoring ordering?

I have a MySQL DB with a table that has a SET type column with the following definition:
CREATE TABLE t (
col SET('V','A','L','U','E')
)
I would like to write a SELECT query that returns all the rows where col equals to ('A','L','E')
This can be done by writing the following query:
SELECT * FROM t WHERE c = 'A,L,E'
The query that i would like to write is one that will return the same result also for an non ordered input like 'L','A','E'
I couldn't find an elegant way to do so and couldn't find anything that can help me in the official documentation
You can fix nacho's suggestion using the following:
WHERE floor(pow(2,FIND_IN_SET('A',c)-1))+
floor(pow(2,FIND_IN_SET('L',c)-1))+
floor(pow(2,FIND_IN_SET('E',c)-1))=c
This is by no means an "elegant solution"... I would rather use a simpler one if possible.
FIND_IN_SET provides the position in the enum, so we have to raise 2 by this number to get the internal representation of the SET value.
The floor() function is used to keep the expression 0 when find_in_set returns 0.
Note that you still have the risk of false positives when checking against illegal SET values (e.g. looking for 'A','L','E' and 'X' will return true)
You need to use the FIND IN SET
SELECT *
FROM t
WHERE FIND_IN_SET('A',c)>0 AND FIND_IN_SET('L',c)>0 AND FIND_IN_SET('E',c)>0
I donĀ“t know if this will work but you can also try:
SELECT *
FROM t
WHERE FIND_IN_SET('A,L,E',c)>0
Another possible approach is to check each item separately + check that the sizes of the groups match (the assumption is that the searched set has no repetitions):
SELECT *
FROM t
WHERE FIND_IN_SET('A',c)>0 AND FIND_IN_SET('L',c)>0 AND FIND_IN_SET('E',c)>0 AND BIT_COUNT(c) = 3

SUM(IF(COND,EXPR,NULL)) and IF(COND, SUM(EXPR),NULL)

I'm working of generating sql request by parsing Excel-like formulas.
So for a given formula, I get this request :
SELECT IF(COL1='Y', SUM(EXPR),NULL)
FROM Table
I don't get the results I want. If I manually rewrite the request like this it works :
SELECT SUM(IF(COL1='Y', EXPR, NULL))
FROM Table
Also, the first request produces the right value if I add a GROUP BY statement, for COL1='Y' row :
SELECT IF(COL1='Y', SUM(EXPR),NULL)
FROM Table
GROUP BY COL1
Is there a way to keep the first syntax IF(COND, SUM(EXPR), NULL) and slightly edit it to make it works without a GROUP BY statement ?
You have to use GROUP BY since you are using SUM - otherwise SQL engine is not able to tell how do you want to summarize the column.
Alternatively you could summarize this column only:
SELECT SUM(EXPR)
FROM Table
WHERE COL1='Y'
But then you would have to run separate query for each such column, read: not recommended for performance reasons.

SELECT n-th row WHERE field = x value

format(sql, sizeof(sql), "SELECT * FROM `datab` WHERE License = %s", searchPlate);
Querying with this format will give me all the rows with this result, but what i'm trying to do is take for ex. the third or fifth or even tenth row that has this result, not all of the rows. How can i do this?
Something like this should work in MySQL:
format(sql, sizeof(sql), "SELECT * FROM `datab` WHERE License = %s ORDER BY IDColumnNameGoesHere LIMIT %d, 1", searchPlate, MyDesiredRowInteger);
Some points:
%d might not be right, use the correct symbol for an integer.
You are most definitely using a specific RDBMS, you must look into the documentation and find out. SQL is a standard, MySQL ans SQL-Server etc are implementations of that standard. You must find out which implementation you are using.
Sticking variables into strings like you have done leaves you very vulnerable to SQL injection. You should always parameterize your queries.
LIMIT is specific to MySQL if you are using a different RDBMS you will have to go another route. For example SQL-Server you can use TOP, but as this only has one parameter you will need to use min or max in addition to only get the one record you desire.

Union (or Concat, etc..) with Constant values and projection

I've discovered a very nasty gotcha with Linq-to-sql, and i'm not sure what the best solution is.
If you take a simple L2S Union statement, and include L2S code in one side, and constants in the other, then the constants do not get included in the SQL Union and are only projected into the output after the SQL, resulting in SQL errors about the number of columns not mathching for the union.
As an example:
(from d in dc.mytable where foo == "bar" select new {First = d.Foo, Second = d.Roo})
.Union(from e in dc.mytable where foo == "roo" select new {First= "", Second = e.Roo})
This will generate an error "All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
This is particularly insidious (and maddening) because there are obviously the same number of expressions in the list, but when you look at the SQL, you will notice that it does not generate a column for "First" in the second half of the Union. This is because "First" is inserted into the projection AFTER the query.
Ok, the easy solution is to just convert each part into Enumerables or Lists or something and then do the union in memory rather than SQL, and that's fine if you're dealing with a small amount of data. However, if you're working with a large set of data, which you then plan to further filter (in sql) before returning it this is not ideal.
I guess what i'm looking for is a way to force L2S to include the column in the SQL. Is that possible?
UPDATE:
While not an exact duplicate, this error is similar to This Question and has similar solutions. So i'm closing, but not deleting this question because it may help someone else come to posible solutions from a different way.
Unfortunately, L2S is too smart of it's own good sometimes.
I've decided that the only real solution is to use a stored proc. Hope this helps.
This is a bug in the Linq2SQL provider.
In LinqPad you can clearly see the bug.
(from d in dc.mytable where foo == "bar" select new {First = d.Foo, Second = d.Roo})
.Union(from e in dc.mytable where foo == "roo" select new {First= "", Second = e.Roo})
Will server side produce something like this:
SELECT [t2].[Foo], [t2].[Roo]
FROM (
SELECT [t0].[Foo], #p0 AS [value]
FROM [dc].[Mytable] AS [t0]
UNION ALL
SELECT [t1].[Foo], [t1].[Roo]
FROM [dc].[Mytable] AS [t1]
) AS [t2]
This will be a problem because the union will name the second column "value" instead of "Roo", which will cause the outer query to fail.
If you, however, switch the order of the two tables
(from e in dc.mytable where foo == "roo" select new {First= "", Second = e.Roo})
.Union(from d in dc.mytable where foo == "bar" select new {First = d.Foo, Second = d.Roo})
So that the constant assignment within the generated T-SQL comes in the non-first table, then things may work because T-SQL ignores the column names of subsequent tables.
Note: The first table in a union decides both column name and type. So would be smart to get LinqPad anyway.