Unexpected NOT EQUAL TO NULL comparison in MySQL [duplicate] - mysql

This question already has answers here:
What's the difference between " = null" and " IS NULL"?
(4 answers)
Closed 5 years ago.
I have below table in MySQL.
city_data
+------+-----------+-------------+
| id | city_code | city_name |
+------+-----------+-------------+
| 4830 | BHR | Bharatpur |
| 4831 | KEP | Nepalgunj |
| 4833 | OHS | Sohar |
| 4834 | NULL | Shirdi |
+------+-----------+-------------+
and below query.
select id,city_code,city_name from city_data where city_code != 'BHR';
I was expecting 3 rows.
| 4831 | KEP | Nepalgunj |
| 4833 | OHS | Sohar |
| 4834 | NULL | Shirdi |
+------+-----------+-------------+
But getting only 2 rows.
| 4831 | KEP | Nepalgunj |
| 4833 | OHS | Sohar |
+------+-----------+-------------+
I am not able to understand why the row
| 4834 | NULL | Shirdi |
Not includes in the result of my query. The where condition(NULL != 'BHR') should have been passed.
Please, someone, help to clear the doubt.

According to MySQL Reference Manual, section 3.3.4.6: Working with NULL values the following is why:
Because the result of any arithmetic comparison with NULL is also
NULL, you cannot obtain any meaningful results from such comparisons.
In MySQL, 0 or NULL means false and anything else means true. The
default truth value from a boolean operation is 1.
This means that NULL != 'BHR' will evaluate to NULL, which in turn will mean false to MySQL. In order for the query to work as you want, you have to append OR city_code IS NULL to your query.

You cannot compare null values with !=, because it is null, use IS NULL predicate instead:
select id,city_code,city_name
from city_data
where city_code != 'BHR' OR city_code IS NULL;

It is not possible to test for NULL values with comparison operators, such as =, <, or <>. Therefore query is confusing and NULL record is being ignored. for more info go to https://www.w3schools.com/sql/sql_null_values.asp

Related

Truncate column names in SELECT (MySQL client)

When I'm looking into new databases to explore what is there, usually I get tables with long column names but short contents, like:
mysql> select * from Seat limit 2;
+---------+---------------------+---------------+------------------+--------------+---------------+--------------+-------------+--------------+-------------+---------+---------+----------+------------+---------------+------------------+-----------+-------------+---------------+-----------------+---------------------+-------------------+-----------------+
| seat_id | seat_created | seat_event_id | seat_category_id | seat_user_id | seat_order_id | seat_item_id | seat_row_nr | seat_zone_id | seat_pmp_id | seat_nr | seat_ts | seat_sid | seat_price | seat_discount | seat_discount_id | seat_code | seat_status | seat_sales_id | seat_checked_by | seat_checked_date | seat_old_order_id | seat_old_status |
+---------+---------------------+---------------+------------------+--------------+---------------+--------------+-------------+--------------+-------------+---------+---------+----------+------------+---------------+------------------+-----------+-------------+---------------+-----------------+---------------------+-------------------+-----------------+
| 4897 | 2016-09-01 00:05:54 | 330 | 331 | NULL | NULL | NULL | 0 | NULL | NULL | 0 | NULL | NULL | NULL | 0.00 | NULL | NULL | free | NULL | NULL | 0000-00-00 00:00:00 | NULL | NULL |
| 4898 | 2016-09-01 00:05:54 | 330 | 331 | NULL | NULL | NULL | 0 | NULL | NULL | 0 | NULL | NULL | NULL | 0.00 | NULL | NULL | free | NULL | NULL | 0000-00-00 00:00:00 | NULL | NULL |
+---------+---------------------+---------------+------------------+--------------+---------------+--------------+-------------+--------------+-------------+---------+---------+----------+------------+---------------+------------------+-----------+-------------+---------------+-----------------+---------------------+-------------------+-----------------+
Since the length of the header is longer that the contents of each row, I see a unformatted output which is hard to standard, specially when you search for little clues like fields that aren't being used and so on.
Is there any way to tell mysql client to truncate column names automatically, for example, to 10 characters as maximum? With the first 10 character is usually enough to know which column they refer to.
Of course I could stablish column aliases for that with AS, but if there's too much columns and you want to do a fast exploration, that would take too long for each table.
Other solution will be to tell mysql to remove the prefix seat_ for each column for example (of course, for each column I would need to change the used prefix).
I don't think there's any way to do that automatically. Some options are:
1) Use a graphical UI such as PhpMyAdmin to view the table contents. These typically allow you to adjust column widths.
2) End the query with \G instead of ;:
mysql> SELECT * FROM seat LIMIT 2\G
This will display the columns horizontally instead of vertically:
seat_id: 4897
seat_created: 2016-09-01 00:05:54
seat_event_id: 330
...
I often use the latter for tables with lots of columns because reading the horizontal format can be difficult, especially when it wraps around on the terminal.
3) Use the less pager in a mode that doesn't wrap lines. You can then scroll left and right with the arrow keys.
mysql> pager less -S
See How to better display MySQL table on Terminal
You can skip the column names completely by running the MySQL client with the -N or --skip-column-names option. Then the width of your columns will be determined by the widest data, not the column name. But there would be no row for the column names.
You can also use column aliases to set your own column names, but you'd have to enter these yourself manually.

MySql LEFT OUTER JOIN causing duplicate rows

Im running a query to grab the first 10 profiles (think of them as an article that shows when a shop opens and holds information about that shop). I'm using the OUTER JOIN to select * images that belong to the profile PK.
Im running the following query, the main part I'm trying to focus on is the JOIN. I won't post the whole query as it's just a whole bunch of 'table'.'colname' = 'table.colname'.
But here is where the magic happens during my outer join.
LEFT JOIN `content_image` AS `image` ON `profile`.`content_ptr_id` = `image`.`content_id`
Full Query:
I've formatted like this so everyone can see the query without scrolling endlessly to the right.
select `profile`.`content_ptr_id` AS `profile.content_ptr_id`,
`profile`.`body` AS `profile.body`,
`profile`.`web_site` AS `profile.web_site`,
`profile`.`email` AS `profile.email`,
`profile`.`hours` AS `profile.hours`,
`profile`.`price_range` AS `profile.price_range`,
`profile`.`price_range_high` AS `profile.price_range_high`,
`profile`.`primary_category_id` AS `profile.primary_category_id`,
`profile`.`business_contact_email` AS `profile.business_contact_email`,
`profile`.`business_contact_phone` AS `profile.business_contact_phone`,
`profile`.`show_in_directory` AS `profile.show_in_directory`,
`image`.`id` AS `image.id`,
`image`.`content_id` AS `image.content_id`,
`image`.`type` AS `image.type`,
`image`.`order` AS `image.order`,
`image`.`caption` AS `image.caption`,
`image`.`author_id` AS `image.author_id`,
`image`.`image` AS `image.image`,
`image`.`link_url` AS `image.link_url`
FROM content_profile AS profile
LEFT JOIN `content_image` AS `image` ON `profile`.`content_ptr_id` = `image`.`content_id`
GROUP BY profile.content_ptr_id
LIMIT 10, 12
Is there a way I can group my results per profile? E.g all images will show in the one profile result? I can't use group by as I'm getting an error
Error: ER_WRONG_FIELD_WITH_GROUP: Expression #12 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'broadsheet.image.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by]
code: 'ER_WRONG_FIELD_WITH_GROUP',
errno: 1055,
sqlState: '42000',
index: 0 }
Is there a possible way around this group by error or another query I could run?
Tables:
content_image
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| content_id | int(11) | NO | MUL | NULL | |
| type | varchar(255) | NO | | NULL | |
| order | int(11) | NO | | NULL | |
| caption | longtext | NO | | NULL | |
| author_id | int(11) | YES | MUL | NULL | |
| image | varchar(255) | YES | | NULL | |
| link_url | varchar(200) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
content_profile
+------------------------+----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+----------------------+------+-----+---------+-------+
| content_ptr_id | int(11) | NO | PRI | NULL | |
| body | longtext | NO | | NULL | |
| web_site | varchar(200) | NO | | NULL | |
| email | varchar(75) | NO | | NULL | |
| menu | longtext | NO | | NULL | |
| hours | longtext | NO | | NULL | |
| price_range | smallint(5) unsigned | YES | MUL | NULL | |
| price_range_high | smallint(5) unsigned | YES | | NULL | |
| primary_category_id | int(11) | NO | | NULL | |
| business_contact_name | varchar(255) | NO | | NULL | |
| business_contact_email | varchar(75) | NO | | NULL | |
| business_contact_phone | varchar(20) | NO | | NULL | |
| show_in_directory | tinyint(1) | NO | | NULL | |
+------------------------+----------------------+------+-----+---------+-------+
From reading your question, I think you don't have a grasp of how the GROUP BY clause works.
So the short summary of my answer is: learn the fundamentals of the GROUP BY clause.
I will use only a small number of columns to make the explanation easier.
The first problem with your query is that you are not using the group by clause properly - when using a group by clause, all columns that are selected must be either in the group by clause OR be selected with an aggregate function.
Lets suppose these are the only columns you are selecting:
profile.content_ptr_id
profile.body
profile.web_site
image.id
image.content_id
And the query looked like this:
SELECT `profile.content_ptr_id`, `profile.body`, `profile.web_site`, `image.id`, `image.content_id`
FROM ...
GROUP BY `profile.content_ptr_id`
This query will error out as you did not specify how you want to consolidate multiple rows to one row for profile.body, profile.web_site, image.id, image.content_id. The database does not know how you want to consolidate the other columns as you can group, or use aggregate functions such as min(), max(), count(), etc.
So one solution to fix the error raised in the query above would be the following:
SELECT `profile.content_ptr_id`, `profile.body`, `profile.web_site`, `image.id`, `image.content_id`
FROM ...
GROUP BY `profile.content_ptr_id`, `profile.body`, `profile.web_site`, `image.id`, `image.content_id`
Here, I put all the columns in the group by clause which makes the query group and select all the unique combinations of profile.content_ptr_id, profile.body, profile.web_site, image.id, image.content_id columns.
Following is an example query which does not have all the columns included in the group by clause:
Lets say, you want to find out how many images there are for each of the profiles. You can use a query such as the following:
SELECT `profile.content_ptr_id`, `profile.body`, `profile.web_site`, COUNT(`image.id`)
FROM ...
GROUP BY `profile.content_ptr_id`, `profile.body`, `profile.web_site`
This query lets you find out how many images there are for every unique combination of profile.content_ptr_id, profile.body, profile.web_site columns.
Be aware that in my previous two examples, all the columns that are selected are either included in the group by clause or are selected with an aggregate function. This is a rule all queries need to follow when using the group by clause, otherwise an error will be raised by the database.
Now, lets get onto answering your question:
"Is there a way I can group my results per profile? E.g all images will show in the one profile result?"
I will use the following mock data to explain:
profile
+----------------+--------------+---------------+
| content_ptr_id | body | web_site |
+----------------+--------------+---------------+
| 100 | body1 | web1 |
+----------------+--------------+---------------+
image
+--------+-------------+
| id | content_id |
+--------+-------------+
| iid1 | 100 |
| iid2 | 100 |
+--------+-------------+
Following would be what the result would look like if you don't do a join:
SELECT `profile.content_ptr_id`, `profile.body`, `profile.web_site`, `image.id`, `image.content_id`
FROM ...
+----------------+--------------+---------------+--------+-------------+
| content_ptr_id | body | web_site | id | content_id |
+----------------+--------------+---------------+--------+-------------+
| 100 | body1 | web1 | iid1 | 100 |
| 100 | body1 | web1 | iid2 | 100 |
+----------------+--------------+---------------+--------+-------------+
You can't achieve your objective of grouping your results per profile (combining to only show one line per profile) by grouping by all the columns as the result will be the same:
SELECT `profile.content_ptr_id`, `profile.body`, `profile.web_site`, `image.id`, `image.content_id`
FROM ...
GROUP BY `profile.content_ptr_id`, `profile.body`, `profile.web_site`, `image.id`, `image.content_id`
will return
+----------------+--------------+---------------+--------+-------------+
| content_ptr_id | body | web_site | id | content_id |
+----------------+--------------+---------------+--------+-------------+
| 100 | body1 | web1 | iid1 | 100 |
| 100 | body1 | web1 | iid2 | 100 |
+----------------+--------------+---------------+--------+-------------+
The question you need to answer is how you want to display the non-unique columns you want to combine - in this case image.id. You can use count, but this will only return you a number. If you want to display all the text, you can use GROUP_CONCAT() which will concatenate all the values delimited by comma by default. If you use GROUP_CONCAT() the result will look like the following:
SELECT `profile.content_ptr_id`, `profile.body`, `profile.web_site`, GROUP_CONCAT(`image.id`), GROUP_CONCAT(`image.content_id`)
FROM ...
GROUP BY `profile.content_ptr_id`, `profile.body`, `profile.web_site`
This query will return:
+----------------+--------------+---------------+--------------------+-------------+
| content_ptr_id | body | web_site | GROUP_CONCAT(id) | content_id |
+----------------+--------------+---------------+--------------------+-------------+
| 100 | body1 | web1 | iid1,iid2 | 100 |
+----------------+--------------+---------------+--------------------+-------------+
If GROUP_CONCAT() is what you want to use for all the image columns, then go ahead, but doing this for many columns consolidating many rows may make the table less readable. But either way, I would suggest you read some articles to familiarise yourself with how the GROUP BY clause works.
Remove the GROUP BY clause.
I suspect you didn't want to do a GROUP BY operation, given that the expression in the group by is the PRIMARY KEY of the content_profile table.
What is up with all the single quotes? Those are used to enclose string literals, not identifiers.
Thank for sparing us from "scrolling endlessly to the right".
Are you aware that spaces and linebreaks can be included in the SQL text, without altering the meaning of the statement? The parser can easily deal with extra whitespace, and adding the extra whitespace to format the statement can make it much easier for a human reader to decipher.
It's not at all clear why the statement is skipping over the first ten rows, and then returning the next twelve. Very strange.
SELECT p.content_ptr_id AS `profile.content_ptr_id`
, p.body AS `profile.body`
, p.web_site AS `profile.web_site`
, p.email AS `profile.email`
, p.hours AS `profile.hours`
, p.price_range AS `profile.price_range`
, p.price_range_high AS `profile.price_range_high`
, p.primary_category_id AS `profile.primary_category_id`
, p.business_contact_email AS `profile.business_contact_email`
, p.business_contact_phone AS `profile.business_contact_phone`
, p.show_in_directory AS `profile.show_in_directory`
, i.id AS `image.id`
, i.content_id AS `image.content_id`
, i.type AS `image.type`
, i.order AS `image.order`
, i.caption AS `image.caption`
, i.author_id AS `image.author_id`
, i.image AS `image.image`
, i.link_url AS `image.link_url`
FROM `content_profile` p
LEFT
JOIN `content_image` i
ON i.content_id = p.content_ptr_id
ORDER
BY p.content_ptr_id
, i.id
Because content_id is not unique in the content_image table, duplicate rows from content_profile are the expected result.
If your code can't handle the "duplicate" rows, i.e. identifying when the row that was just fetched has the same value for content_ptr_id as the previous row, then your SQL shouldn't do a join operation that creates the duplicated values.

SQL query, treat NULL as zero [duplicate]

This question already has an answer here:
MySQL: Typecasting NULL to 0
(1 answer)
Closed 7 years ago.
I'm learning SQL (using MySQL) and have a very simple question. I have a table with salary and bonus information on employees, and I'd like to sum the two, but have MySQL return the value zero when at least one of the summands is NULL, instead of returning NULL. What's the easiest way to do this?
mysql> SELECT SALARY, BONUS FROM EMPLOYEE_PAY_TBL;
+----------+---------+
| SALARY | BONUS |
+----------+---------+
| 30000.00 | 2000.00 |
| NULL | NULL |
| 40000.00 | NULL |
| 20000.00 | 1000.00 |
| NULL | NULL |
| NULL | NULL |
+----------+---------+
mysql> SELECT SALARY + BONUS FROM EMPLOYEE_PAY_TBL;
+----------------+
| SALARY + BONUS |
+----------------+
| 32000.00 |
| NULL |
| NULL |
| 21000.00 |
| NULL |
| NULL |
+----------------+
You can use COALESCE. It accepts a number of arguments and returns the first one that is not null.
You can use IFNULL too (not to be confused with ISNULL). It behaves the same in this scenario, but COALESCE is more portable; it allows multiple arguments and returns the first not-null one. Also, other databases support it too, so that makes it slightly easier to migrate to another database if you would like to in the future.
SELECT COALESCE(SALARY, 0) + COALESCE(BONUS, 0)
FROM EMPLOYEE_PAY_TBL;
or
SELECT IFNULL(SALARY, 0) + IFNULL(BONUS, 0)
FROM EMPLOYEE_PAY_TBL;
Both of them are just a very convenient way to write:
SELECT
CASE WHEN SALARY IS NULL THEN 0 ELSE SALARY END +
CASE WHEN BONUS IS NULL THEN 0 ELSE BONUS END
FROM EMPLOYEE_PAY_TBL;
SELECT IFNULL(SALARY, 0), IFNULL(BONUS, 0) FROM EMPLOYEE_PAY_TBL;
You should use IFNULL function for that.
CORRECTED

MySQL query executes fine, but returns (false) empty result set when using != NULL?

I have the following result set, that I'm trying to drill down
+----+---------+---------------+---------------------+----------------------+---------------+-----------+------------------+------------------+
| id | auth_id | trusts_number | buy_sell_actions_id | corporate_actions_id | fx_actions_id | submitted | created_at | updated_at |
+----+---------+---------------+---------------------+----------------------+---------------+-----------+------------------+------------------+
| 2 | 6 | N100723 | 2 | NULL | NULL | 0 | 08/05/2015 11:30 | 08/05/2015 15:32 |
| 5 | 6 | N100723 | NULL | NULL | 1 | 0 | 08/05/2015 15:10 | 08/05/2015 15:10 |
| 6 | 6 | N100723 | NULL | NULL | 2 | 1 | 08/05/2015 15:12 | 08/05/2015 15:41 |
+----+---------+---------------+---------------------+----------------------+---------------+-----------+------------------+------------------+
This result set is generated with the query
SELECT * FROM actions WHERE auth_id = 6 AND trusts_number = 'N100723'
I also want to get rid of any field with fx_actions is NULL, so I change the query to
SELECT * FROM actions WHERE auth_id = 6 AND trusts_number = 'N100723' AND fx_actions_id != NULL
However this returns an empty result set. I've never used "negative" query parameters in MySQL before, so I'm not sure if they should take on a different syntax or what?
Any help would be much appreciated.
Normal comparison operators don't work well with NULL. Both Something = NULL and Something != NULL will return 'unknown', which causes the row to be omitted in the result. Use the special operators IS NULL and IS NOT NULL instead:
SELECT * FROM actions
WHERE auth_id = 6
AND trusts_number = 'N100723'
AND fx_actions_id IS NOT NULL
Wikipedia on NULL and its background
Because null isn't a value, you should use IS NOT NULL

How are the correct mysql script?

i have a mysql's script as follow as:
SET #waktu = NULL;
SELECT username,
IF (#username = username,
IFNULL(TIMEDIFF(waktu,#waktu),0), #waktu:=waktu +
NULL + LEAST(0,#username := username)) selisih_waktu, #waktu:=`waktu` as time
FROM mdl_temporer_log2 WHERE rolename = 'Student'
ORDER BY waktu
After, i run that script, it will show as follow as:
username | selisih_waktu | time
-----------|---------------|--------------
aq | null | 05:55:07
aq | null | 05:55:09
megi | null | 05:55:37
megi | null | 05:55:40
megi | null | 05:55:45
but, when I run it a second time, that script will show as follow as:
username | selisih_waktu | time
-----------|---------------|--------------
aq | 0 | 05:55:07
aq | 00:00:02 | 05:55:09
megi | null | 05:55:37
megi | 00:00:03 | 05:55:40
megi | 00:00:03 | 05:55:45
I want to get output from that script as follow as:
username | selisih_waktu | time
-----------|---------------|--------------
aq | null | 05:55:07
aq | 00:00:02 | 05:55:09
megi | null | 05:55:37
megi | 00:00:03 | 05:55:40
megi | 00:00:03 | 05:55:45
So, i'm confused, are that script wrong or right? and if that script are wrong, how are the correct scripts?
The problem is here:
...#waktu:=waktu + NULL + LEAST...
Why are you adding NULL here? NULL is not zero or anything, it is unknown.
Anything + unknown = unknown
Just remove the + NULL.
EDIT:
Now that I read a bit more about it, this here
LEAST(0,#username := username))
doesn't make sense to me either. LEAST() returns the smallest argument, but you're comparing apples and oranges. The second parameter is just assigning a row value to a variable.
You should explain, what your query should do (with words, not just with tables), otherwise this question doesn't make much sense and is unanswerable.