MySQL: how to improve my query? - mysql

Currently I'm making my website application to validate the suburb values and ensure it exsits in the database data.
Here is my MySQL table
CREATE TABLE IF NOT EXISTS `postcodeTable` (
`id` int(11) NOT NULL,
`postcode` varchar(50) NOT NULL,
`suburb` text NOT NULL,
`state` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The user input 2 variables which are State and Suburb variable.
Here is my SQL statement to check if the row exists:
"select * from postcodeTable where state='".$state."' and suburb LIKE '%".$suburb."%'";
Here is my sample table data
Row1 id:1 postcode:3794 suburb: BANGHAM,BORDERTOWN,CANNAWIGARA,LOWAN, state: SA
Row2 id:2 postcode:6627 suburb: CANNA, state: WA
When a user input suburb CANNA and state SA it matches with the MySQL statement which is incorrect.
It matches because the LIKE statement because 'CANNA' word in CANNAWIGARA suburb name.
Which is incorrect because CANNA suburb only exist in WA state.
Is there a way to make the LIKE statement smarter which can go through the suburb string and ensure it matches the whole suburb name only?
Thanks so much in advance!

The problem here is your database schema : you should not store several suburbs in a single field of the postcodeTable table.
Instead, you should have one suburbs table, that would store one suburb per line, with a foreign key that points to the corresponding postcodeTable row.
Your postcodeTable table would be :
id
postcode
state
And your suburbs table would be :
id_suburb
id_postcode : foreign key to postcodeTable
name
Then, as suburbs.name would contain the exact name of one suburb, you wouldn't have to use like %...% anymore : you'd just have to use suburbs.name = '...'
And here's an idea of what your SQL query would look like :
select postcodeTable.*
from postcodeTable
inner join suburbs on suburbs.id_postcode = postcodeTable.id
where
suburbs.name = 'CANNA'
and postcodeTable.state = 'SA'

You could try regular expression matching. But I think it would be better to normalize your database. That is, split the suburbs off in a separate table, with a key pointing to the postcode table. Then you could do:
SELECT * FROM postcodeTable p
LEFT JOIN suburbTable s ON s.postcode_id = p.id
WHERE p.state = 'SA' AND s.name = 'CANNA';
Which should return zero rows.

If you need to search for exact comparisons, rather use
"select * from postcodeTable where state='".$state."' and suburb = $suburb
or drop the "%" at the back.
Have a look at the SQL Wildcards available: http://www.w3schools.com/sql/sql_wildcards.asp

If you don't want to modify your tables as people are suggesting, try:
"select * from postcodeTable where state='".$state."' and suburb LIKE '%".$suburb."%,'";
It will solve some problems, but not all of them.

Try:
$query = "select * from postcodeTable
where state='".$state."' and suburb REGEXP '(^|,)".$suburb."(,|$)'";
But as many others did, I strongly suggest you to normalize your schema.

Thanks for the responses!
Silly me I just realised if I just add a comma to the suburb field, it would solves the problem.
Since "comma" symbol in my suburb string means end of surburb word.
Cheers!

Thanks for the quick responses!
Silly me I just realised if I just add a comma to the suburb input field, it would solve the problem.
Since "comma" symbol in my suburb string means end of surburb word.
Thanks for your advices! I would normalise my Postcode table when I get the chance.
Cheers!

Related

how to pass multiple variables in WHERE ... IN in stored procedure? [duplicate]

I have a column in one of my table where I store multiple ids seperated by comma's.
Is there a way in which I can use this column's value in the "IN" clause of a query.
The column(city) has values like 6,7,8,16,21,2
I need to use as
select * from table where e_ID in (Select city from locations where e_Id=?)
I am satisfied with Crozin's answer, but I am open to suggestions, views and options.
Feel free to share your views.
Building on the FIND_IN_SET() example from #Jeremy Smith, you can do it with a join so you don't have to run a subquery.
SELECT * FROM table t
JOIN locations l ON FIND_IN_SET(t.e_ID, l.city) > 0
WHERE l.e_ID = ?
This is known to perform very poorly, since it has to do table-scans, evaluating the FIND_IN_SET() function for every combination of rows in table and locations. It cannot make use of an index, and there's no way to improve it.
I know you said you are trying to make the best of a bad database design, but you must understand just how drastically bad this is.
Explanation: Suppose I were to ask you to look up everyone in a telephone book whose first, middle, or last initial is "J." There's no way the sorted order of the book helps in this case, since you have to scan every single page anyway.
The LIKE solution given by #fthiella has a similar problem with regards to performance. It cannot be indexed.
Also see my answer to Is storing a delimited list in a database column really that bad? for other pitfalls of this way of storing denormalized data.
If you can create a supplementary table to store an index, you can map the locations to each entry in the city list:
CREATE TABLE location2city (
location INT,
city INT,
PRIMARY KEY (location, city)
);
Assuming you have a lookup table for all possible cities (not just those mentioned in the table) you can bear the inefficiency one time to produce the mapping:
INSERT INTO location2city (location, city)
SELECT l.e_ID, c.e_ID FROM cities c JOIN locations l
ON FIND_IN_SET(c.e_ID, l.city) > 0;
Now you can run a much more efficient query to find entries in your table:
SELECT * FROM location2city l
JOIN table t ON t.e_ID = l.city
WHERE l.e_ID = ?;
This can make use of an index. Now you just need to take care that any INSERT/UPDATE/DELETE of rows in locations also inserts the corresponding mapping rows in location2city.
From MySQL's point of view you're not storing multiple ids separated by comma - you're storing a text value, which has the exact same meaing as "Hello World" or "I like cakes!" - i.e. it doesn't have any meaing.
What you have to do is to create a separated table that will link two objects from the database together. Read more about many-to-many or one-to-many (depending on your requirements) relationships in SQL-based databases.
Rather than use IN on your query, use FIND_IN_SET (docs):
SELECT * FROM table
WHERE 0 < FIND_IN_SET(e_ID, (
SELECT city FROM locations WHERE e_ID=?))
The usual caveats about first form normalization apply (the database shouldn't store multiple values in a single column), but if you're stuck with it, then the above statement should help.
This does not use IN clause, but it should do what you need:
Select *
from table
where
CONCAT(',', (Select city from locations where e_Id=?), ',')
LIKE
CONCAT('%,', e_ID, ',%')
but you have to make sure that e_ID does not contain any commas or any jolly character.
e.g.
CONCAT(',', '6,7,8,16,21,2', ',') returns ',6,7,8,16,21,2,'
e_ID=1 --> ',6,7,8,16,21,2,' LIKE '%,1,%' ? FALSE
e_ID=6 --> ',6,7,8,16,21,2,' LIKE '%,6,%' ? TRUE
e_ID=21 --> ',6,7,8,16,21,2,' LIKE '%,21,%' ? TRUE
e_ID=2 --> ',6,7,8,16,21,2,' LIKE '%,2,%' ? TRUE
e_ID=3 --> ',6,7,8,16,21,2,' LIKE '%,3,%' ? FALSE
etc.
Don't know if this is what you want to accomplish. With MySQL there is feature to concatenate values from a group GROUP_CONCAT
You can try something like this:
select * from table where e_ID in (Select GROUP_CONCAT(city SEPARATOR ',') from locations where e_Id=?)
this one in for oracle ..here string concatenation is done by wm_concat
select * from table where e_ID in (Select wm_concat(city) from locations where e_Id=?)
yes i agree with raheel shan .. in order put this "in" clause we need to make that column into row below code one do that job.
select * from table where to_char(e_ID)
in (
select substr(city,instr(city,',',1,rownum)+1,instr(city,',',1,rownum+1)-instr(city,',',1,rownum)-1) from
(
select ','||WM_CONCAT(city)||',' city,length(WM_CONCAT(city))-length(replace(WM_CONCAT(city),','))+1 CNT from locations where e_Id=? ) TST
,ALL_OBJECTS OBJ where TST.CNT>=rownum
) ;
you should use
FIND_IN_SET Returns position of value in string of comma-separated values
mysql> SELECT FIND_IN_SET('b','a,b,c,d');
-> 2
You need to "SPLIT" the city column values. It will be like:
SELECT *
FROM table
WHERE e_ID IN (SELECT TO_NUMBER(
SPLIT_STR(city /*string*/
, ',' /*delimiter*/
, 1 /*start_position*/
)
)
FROM locations);
You can read more about the MySQL split_str function here: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/
Also, I have used the TO_NUMBER function of Oracle here. Please replace it with a proper MySQL function.
IN takes rows so taking comma seperated column for search will not do what you want but if you provide data like this ('1','2','3') this will work but you can not save data like this in your field whatever you insert in the column it will take the whole thing as a string.
You can create a prepared statement dynamically like this
set #sql = concat('select * from city where city_id in (',
(select cities from location where location_id = 3),
')');
prepare in_stmt from #sql;
execute in_stmt;
deallocate prepare in_stmt;
Ref: Use a comma-separated string in an IN () in MySQL
Recently I faced the same problem and this is how I resolved it.
It worked for me, hope this is what you were looking for.
select * from table_name t where (select (CONCAT(',',(Select city from locations l where l.e_Id=?),',')) as city_string) LIKE CONCAT('%,',t.e_ID,',%');
Example: It will look like this
select * from table_name t where ',6,7,8,16,21,2,' LIKE '%,2,%';

Why doesn't this LIKE query return any results?

I made table copied from W3schools 2015 ... Here's some of the data:
here the columns and data types i used as follows.
CustomerID = int
CustomerName = varchar
ContactName = varchar
Address = varchar
City = varchar
PostalCode = varchar
Country = text
When i used the following query i got the actual result.
SELECT * FROM Customers WHERE Country LIKE 'U%'; SELECT * FROM Customers WHERE Country LIKE 'M%'; SELECT * FROM Customers WHERE Country LIKE 'G%';
Now the problem was that When i used the following query, i weren't getting actual result.
SELECT * FROM Customers WHERE Country LIKE 's%';
i didn't see any row !!
But We should have seen the following output,
why ?? can anybody explain pls...
Moreover I am totally in Novice phase..
Thanks in Advance
May be,
This is Because of Your Country Column Starting with 'S'(which we think) are not really Starting with 'S', May be it's first charter is a Space.(Just A Guess)
so, For this Case you can try Once,
SELECT * FROM Customers WHERE rtrim(ltrim(Country)) LIKE 's%';
or
SELECT * FROM Customers WHERE replace(Country,' ','') LIKE 's%';
See How can I search (case-insensitive) in a column using LIKE wildcard?

Query for first result in a column - substring_index function - MySQL

I can't seem to get the substring_index() to work:
I have created a simple table as follows:
CREATE TABLE ContactList(
cont_id int(11) NOT NULL AUTO_INCREMENT,
last_name varchar(30),
first_name varchar(20),
interests varchar(100),
PRIMARY KEY(cont_id));
I then populated the ContactList table as follows:
INSERT INTO ContactList (last_name, first_name, interests)
VALUES
('Murphy', 'Dave', 'Golf, Pets, Basketball'),
('Murphy', 'Ben', 'Pets, Gym, Basketball'),
('Finn', 'Belinda', 'Pets, Tennis, Knitting'),
('Murphy', 'Steve', 'Pets, Archery, Fishing');
I ran a quick SELECT to ensure the data was entered correctly:
SELECT * FROM ContactList;
Then I ran the following query:
SELECT * FROM ContactList
WHERE last_name = 'Murphy'
AND SUBSTRING_INDEX(interests, ',' ,1) = 'Pets';
I was expecting to get two records back (which I did for Ben & Steve), however, for the 'Interests' column I was assuming I should only get one interest back if it equaled 'pets' (due to the substring_index) however, I got all interests back. How can I use the SUBSTRING_INDEX() to run the query and only get the first interest listed back for each record if it says 'Pets'?
BTW I am using MySQL Version 5.5.24 and I know the Interests would be best suited in their own table - I just want to see why substring_index is not picking the first item from the list if it equals 'pets'.
Thanks for any input,
Andy R ;-)
You're using SUBSTRING_INDEX in the WHERE clause, which determines which rows to include. That's good, but you also need to use it in the SELECT clause, which determines which columns to include.
Try this:
SELECT
last_name,
first_name,
SUBSTRING_INDEX(interests, ',' ,1) AS FirstInterestInList
FROM ContactList
WHERE last_name = 'Murphy'
AND SUBSTRING_INDEX(interests, ',' ,1) = 'Pets';
Although substring_index() will work for the first element, you really want find_in_list():
SELECT last_name, first_name, SUBSTRING_INDEX(interests, ',' ,1) AS FirstInterestInList
FROM ContactList
WHERE last_name = 'Murphy' and
find_in_set('pets', interests) = 1
The advantage of find_inset() is that it will work for arbitrary positions.
Just as a note, though, your delimiter is ', '. For find_in_set() to work best, you should have no space after the column.
Also, if you are doing queries like this, you should fix your data structure. It really wants a table called something like ContactInterests which contains one row for each contact and each interest.

MySQL QUERY condition

I try make search for my site. Example: have field name = city, if city is empty my query looks: SELECT * FROM TABLE WHERE CITY = ALL;
I don't know how to write: CITY = ALL correct, expression CITY is not null will be complicated because I should be remove =
Try this:
SELECT *
FROM Table
WHERE #SearchParam IS NULL OR City = #SearchParam
If his parameter #SearchParam is passed to the query with a NULL value then it will return all the data in the table, otherwise it will search for the cities with this parameter.
remove the where completely
SELECT * FROM TABLE
or
SELECT * FROM TABLE WHERE CITY = #searchstring or #searchstring is null;
If you don't care about very optimized queries, you can use a like operator compare with
City like '%#exp'
If exp is empty the query returns all Cities.
I personally do not recommend this :)
All the best
If you are getting them all, why have the WHERE clause in at all?
SELECT * FROM TABLE
Note, you should avoid using SELECT * whenever possible. As it is a waste of resources, always be explicit with your columns returned.
Maybe something like this?
SELECT * FROM TABLE WHERE (CITY = SOMETHING) OR (SOMETHING is null);

Not finding field after join

I'm working with the InnoDB version of MySQL's world database (available here) and am trying to fetch a list of the countries of South America and their capitals. In my mind, the query should look like this:
SELECT `Country.Name` as `CountryName`, `City.Name` as `CityName`
FROM `Country`, `City`
WHERE `Continent` = 'South America' AND `ID` = `Capital`;
But that one gives error #1054 - Unknown column 'Country.Name' in 'field list', even though the table Country does have the field Name.
Why isn't MySQL finding the fields I want? How do I change the query to make it find them?
Let me know if I need to provide more information for you to be able to help me.
If you quote identifiers, do not surround the inner dot with backticks.
SELECT
`Country`.`Name` AS CountryName,
`City`.`Name` AS CityName
If you quote around the inner dot, it will be assumed to be inside the column name, rather than a separator between the table name and column name -- you have a column named Name, but not a column called Country.Name. In this case, however, it is unnecessary to quote any of the identifiers since none of them are MySQL reserved keywords.
Try modifying your back ticks.
SELECT
`Country`.`Name` as `CountryName`,
`City`.`Name` as `CityName`
FROM `Country`, `City`
WHERE `Continent` = 'South America' AND `ID` = 'Capital';
Do not put them around the entire table.column, but around them individually with the period between them.
Also capitol should be single quotes and not back ticks.
This appears to be a syntax issue:
Try using this instead:
`Country`.`Name`
country.name (in ticks) would be the name of the field when what your after is
`country`.`name`