MySQL IN query with list of pairs unintended behavior - mysql

I just stumbled into a problem and I don't understand why this happens. I have a SELECT query with a WHERE part looking like this using varchar columns: (.,.) IN ((.,.),(.,.),(.,.)). The collation of my columns is latin1_german1_ci and somehow this is relevant when I use german letters like 'ä'. The problem is then that the query returns an empty result but it should select some columns.
I created a minimal SQL Fiddler example: http://sqlfiddle.com/#!9/0c8a0/3/0
CREATE TABLE PairProblem
(`a` varchar(10), `b` varchar(10))
COLLATE = latin1_german1_ci
;
INSERT INTO PairProblem
(a,b) VALUES ('ä','c');
SELECT * FROM PairProblem
WHERE (a,b) IN (('ä','c'),('d','e'));
SELECT * FROM PairProblem
WHERE (a,b) IN (('a','c'),('d','e'));
SELECT * FROM PairProblem
WHERE (a,b) IN (('ä','c'));
SELECT * FROM PairProblem
WHERE a IN ('ä','d');
The first SELECT query there should in my opinion return the same as the second query, but it doesn't.
Since I don't understand why this happens, I cannot really ensure that my query which I need for a much more complex situation selects what I think it should select. So please help me understand this situation or at least how I can prevent such unintended behavior.

Related

In SQL, can you nest a SELECT statement in the VALUES clause of the INSERT statement?

In SQL is it possible to nest a SELECT statement in the VALUES clause of the INSERT statement? I'm using MySQL and would like to query the table for the max value of a field plus one when creating a new record, as shown below.
INSERT into attornies (
LawOfficeId, LawOfficeName
) VALUES (
(select max(LawOfficeID)+1 from attornies),
'Wee, Sue Em and Howe'
);
I'm not sure if my syntax is bad, what I'm trying to do is not possible, etc. Of course, If I try this as two separate statements it works but I would like to make it one statement. I know one suggestion is to use auto increment but I don't want to.
If this question has already been answered please point me in that direction. If not .... Help.
Yes you can do it
INSERT into attornies (
LawOfficeId, LawOfficeName
)
select max(LawOfficeID) + 1,'Wee, Sue Em and Howe' from attornies;
However this is not recommended over auto increment since you are likely going to run into duplicate primary key situation if multiple threads are doing the insert
Your query should work. Scalar subqueries are allowed in the values clause of the insert.
In general, it would more commonly be written as:
insert into attornies (LawOfficeId, LawOfficeName)
select max(LawOfficeID)+1, 'Wee, Sue Em and Howe'
from attornies;
However, this is not the right way to do what you seem to want. Instead, create attornies -- which I am renaming to lawOffices because that seems to be the intention -- with an auto-increment column:
create table lawOffices (
lawOfficeId int auto_increment primary key,
lawOfficeName varchar(255)
);
And then do:
insert into lawOffices (lawOfficeName)
values ( 'Wee, Sue Em and Howe' );
The database does the work of incrementing the id.
You can't use the syntax you show.
mysql> INSERT into attornies (
-> LawOfficeId, LawOfficeName
-> ) VALUES (
-> (select max(LawOfficeID)+1 from attornies),
-> 'Wee, Sue Em and Howe'
-> );
ERROR 1093 (HY000): You can't specify target table 'attornies' for update in FROM clause
https://dev.mysql.com/doc/refman/8.0/en/subquery-errors.html says:
You can use a subquery for assignment within an UPDATE statement because subqueries are legal in UPDATE and DELETE statements as well as in SELECT statements. However, you cannot use the same table (in this case, table t1) for both the subquery FROM clause and the update target.
The doc is talking about using a subquery in an UPDATE statement, but the same rule applies in an INSERT or DELETE statement.
However, this works:
mysql> INSERT into attornies (
-> LawOfficeId, LawOfficeName
-> )
-> select max(LawOfficeID) + 1,'Wee, Sue Em and Howe' from attornies;
Query OK, 1 row affected (0.02 sec)
Records: 1 Duplicates: 0 Warnings: 0
https://dev.mysql.com/doc/refman/8.0/en/insert-select.html says:
The target table of the INSERT statement may appear in the FROM clause of the SELECT part of the query. However, you cannot insert into a table and select from the same table in a subquery.
When selecting from and inserting into the same table, MySQL creates an internal temporary table to hold the rows from the SELECT and then inserts those rows into the target table.
I agree with the other answers and comments who warn you against using the MAX()+1 method of getting the next id value. This method is susceptible to race conditions. Use an auto-increment column instead.
Okay, you've all convinced me. In addition, I did a bit of testing of import/export with an auto-increment field in MySQL. My concerns are no longer justified. I'll discard my max()+1 idea as unnecessary and go with auto-increment.
I would also like to thank Steve, Bill Karwin and Gordon Linoff for correcting my SQL and pointing me in the proper direction if I had continued with my original idea.

Fulltext search in mysql doesn't retrieve all rows

i've a problem with a query in mysql.
This is what i done:
CREATE TABLE `dar`.`MyTable` (
`MyCol` VARCHAR(100) NOT NULL,
FULLTEXT INDEX `Index_1`(`MyCol`)
)
ENGINE = MyISAM;
INSERT INTO MyTable (MyCol)
VALUES ('6002.C3'),
('6002'),
('6002R1'),
('6003.C4'),
('AA6002.X'),
('BB 6002.X');
This is not necessary, but i've done anyway:
REPAIR TABLE MyTable QUICK;
Now, i execute the next query:
SELECT MyCol FROM MyTable
WHERE MATCH(MyCol) AGAINST ('6002*');
And, it doesn't return any row!!
The parameter ft_min_word_len i've changed to 2, but nothing is changed.
When deleting the row with 'BB 6002.X' the query returns 2 rows!!
6002
6002.C3
That is creepy.
Any idea what is happening here?
I need the query return:
6002.C3
6002
6002R1
Plus if include:
AA6002.X
BB 6002.X
Thanks in advance!!
You are past the 50% threshold in your dataset. Try
SELECT MyCol FROM MyTable
WHERE MATCH(MyCol) AGAINST ('6003');
And see what the result is.
The 50% threshold has a significant implication when you first try full-text searching to see how it works: If you create a table and insert only one or two rows of text into it, every word in the text occurs in at least 50% of the rows. As a result, no search returns any results. Be sure to insert at least three rows, and preferably many more. Users who need to bypass the 50% limitation can use the boolean search mode; see Section 12.9.2, “Boolean Full-Text Searches”.
http://dev.mysql.com/doc/refman/5.0/en/fulltext-natural-language.html

Insert in MYSQL multiple rows with one common value

I'm trying to do a select from one table and insert the return values into another table with a common value representing the search parameter using the insert/select function. For simplicities sake instead of...
INSERT INTO tbl_name (a,b) VALUES (1,1,1), (4,5,6);
I want to do something like this....
INSERT INTO tbl_name (a,b) VALUES (1), (4,5,6);
Except where the second column b has several hundred values and 'a' is the common value. I've tried using SET for 'a' but either this can't be done or I can't get the syntax correct. The reason I'm doing this is to avoid building up an insert function in PHP. Here is the best I have....
INSERT INTO tbl_name
(a,b)
SET a = '1'
SELECT c
FROM tbl_name2
WHERE `d` LIKE '%word%'
I might be missing the point, but doing:
INSERT INTO tbl_name (a,b) (SELECT 1,c FROM tbl_name2 WHERE d like '%word%';);
will do the job.
OK I FIGURED IT OUT, already typed all of this, might as well post the answer for everyone.
INSERT INTO tbl_name
(a,b)
SELECT "2", c
FROM tbl_name2
WHERE d LIKE '%word%'

Is there an easy SELECT-Statement that creates an empty set?

Is there an easy and simple way to create a result table that has specified columns but zero rows? In set theory this is called an empty set, but since relational databases use multidimensional sets the term doesn't fit perfectly. I have tried these two queries, but both deliver exactly one row and not zero rows:
SELECT '' AS ID;
SELECT null AS ID;
But what I want is the same result as this query:
SELECT ID FROM sometable WHERE false;
I'm searching for a more elegant way because I don't want to have a table involved, so the query is independent from any database scheme. Also a generic query might be a bit faster (not that it would matter for such a query).
SELECT "ID" LIMIT 0;
Without any real tables.
Do note that most (My)SQL clients simply will display "Empty set". However, it actually does what you want:
create table test.test_table
select "ID" limit 0;
show create table test.test_table\G
Table: test_table
Create Table: CREATE TABLE `test_table` (
`ID` varchar(2) character set latin1 NOT NULL default ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
SELECT * FROM (SELECT NULL AS ID) AS x WHERE 1 = 0
You can use the DUAL pseudo-table.
SELECT whatever FROM DUAL WHERE 1 = 0
Check the documentation (look for the DUAL section).

MySQL INSERT Using Subquery with COUNT() on the Same Table

I'm having trouble getting an INSERT query to execute properly, and I can't seem to find anything on Google or Stack Overflow that solves this particular issue.
I'm trying to create a simple table for featured entries, where the entry_id is saved to the table along with it's current order.
My desired output is this:
If the featured table currently has these three entries:
featured_id entry_id featured_order
1 27 0
2 54 1
4 23 2
I want the next entry to save with featured_order=3.
I'm trying to get the following query to work with no luck:
INSERT INTO `featured`
(
`entry_id`, `featured_order`
)
VALUES
(
200,
(SELECT COUNT(*) AS `the_count` FROM `featured`)
)
The error I'm getting is: You can't specify target table 'featured' for update in FROM clause.
Can anyone help with a solution that gets the count without causing an error?
Thanks in advance!
Here is a cool thing: MySQL's INSERT . . . SELECT:
INSERT INTO `featured`
(
`entry_id`, `featured_order`
)
SELECT 200, COUNT(*) + 1
FROM `featured`
No subquery required.
#Bohemian has a good point:
Better to use max(featured_order) + 1 if you use this approach
So a better query would probably be:
INSERT INTO `featured`
(
`entry_id`, `featured_order`
)
SELECT 200, MAX(`featured_order`) + 1
FROM `featured`
His trigger method describe in his answer is also a good way to accomplish what you want.
The potential problem with query 1 is if you ever delete a row the rank will be thrown off, and you'll have a duplicate in featured_order. With the second query this is not a problem, but you will have gaps, just as if you were using an auto-increment column.
If you absolutely must have an order with no gaps the best solution I know of is to run this series of queries:
SET #pos:=0;
DROP TABLE IF EXISTS temp1;
CREATE TEMPORARY TABLE temp1 LIKE featured;
ALTER TABLE featured ORDER BY featured_order ASC;
INSERT INTO temp1 (featured_id, entry_id, featured_order)
SELECT featured_id, entry_id, #pos:=#pos+1 FROM words;
UPDATE featured
JOIN temp1 ON featured.featured_id = temp1.featured_id
SET featured.rank = temp1.rank;
DROP TABLE temp1;
Whenever you delete a row
Use a trigger:
drop trigger if exists featured_insert_trigger;
delimiter //
create trigger featured_insert_trigger before insert on featured
for each row
begin
set new.featured_order = ifnull((select max(featured_order) from featured), -1) + 1;
end; //
delimiter ;
Now your inserts look like this:
insert into featured (entry_id) values (200);
featured_order will be set to the highest featured_order value plus one. This caters for rows being deleted/updated and always guarantee uniqueness.
The ifnull is there in case there are no rows in the table, in which case the first value will be zero.
This code has been tested as works correctly.
INSERT INTO `featured`
(
`entry_id`, `featured_order`
)
VALUES
(
200,
(SELECT COUNT(*) AS `the_count` FROM `featured` F1)
)
Correction is just adding "F1" table alias.
This standard sql solution works fine on various dbms (not only mysql)
I also suggest an improvement over:
SELECT COUNT(*) +1 (Problem: if some row gets deleted you may collide with existing index)
SELECT MAX(featured_order)+1 (Problem: the first insert with empty table gets error)
SELECT (COALESCE(MAX(featured_order), 0)+1) (no Problem)
You have to simpley use alias that will solve the problem :
INSERT INTO `featured`
(
`entry_id`, `featured_order`
)
VALUES
(
200,
(SELECT COUNT(*) AS `the_count` FROM `featured` as f1)
)
From the MySQL manual regarding subqueries:
Another restriction is that currently you cannot modify a table and select from the same table in a subquery.
Perhaps an alias or a join (otherwise useless) in the subquery would help here.
EDIT: It turns out that there's a work-around. The work-around is described http://www.xaprb.com/blog/2006/06/23/how-to-select-from-an-update-target-in-mysql/.