SELECT zlec_status.nazwa AS Status,
piorytet.nazwa AS Priorytet,
Concat(koord.imie, ' ', koord.nazwisko) AS `Koordynator`,
Concat(zlec_adresy.town, ' - ', zlec_adresy.street, ' ',
zlec_adresy.other)
AS `adres`,
zlec_z_dnia,zlec_id,
zlec_nr,
zlec_do,
zlec_ogran,
awizacje,
awizacja_na_dzien,
termin_zamkniecia,
tresc,
uwagi
FROM zlec
INNER JOIN koord
ON zlec.koord = koord.id
INNER JOIN zlec_adresy
ON zlec.zlec_addres = zlec_adresy.id
INNER JOIN piorytet
ON zlec.priorytet = piorytet.id
INNER JOIN zlec_status
ON zlec.status_zlecenia = zlec_status.id
WHERE `zlec_adresy`.`town` LIKE '%Sz%' LIMIT 0, 10
The table zlec_adresy is the following:
=============================
id | street | town | other
=============================
As you see there is a concat for the whole address part
Concat(zlec_adresy.town, ' - ', zlec_adresy.street, ' ',zlec_adresy.other)AS `adres`
Starting off - the user has a input field with the address lookup - so he can write either the town name or a street name or the other condition. In my code there will be only filtered through the WHERE clause of the .'town' LIKE userinput. But how can I make it working => Where ||Concat(zlec_adresy.town, ' - ', zlec_adresy.street, ' ',zlec_adresy.other)ASadres|| LIKE userinput
So basically it would filter the As adres for the where statement. Is that possible to do? Or there is another way to accomplish it.
Another, possibly more clean approach uses OR:
WHERE
town like concat('%',#userinput,'%')
OR
street like concat('%',#userinput,'%')
OR
other like concat('%',#userinput,'%')
WHERE
Concat(zlec_adresy.town, ' - ', zlec_adresy.street, ' ',
zlec_adresy.other) like concat('%',#variable,'%')
You can use HAVING to solve your issue. Something like :
SELECT
name, CONCAT(street, ' ', town) AS address
FROM
zlec
WHERE
zlec.town like '%abc%'
HAVING address like '%xyz%'
The query anatomy looks like this:
it handles the from, because all the other clauses are dependent on it
it handles the where, to filter out unneeded rows
it handles the select, so you will get the columns you want
Note, that I did not mention group by, having or order by, since they are not part of your query, so this anatomy is quite a simplified one. The purpose of this simplification was to achieve clarity.
Now, since the select runs after the where, you cannot use the renames from the select in the where, since when the where is executed, the selection was not yet executed.
So you have to do a work-around:
- you can define a temporary new relation by (select ...) mynewrelation and use the column renamings of the newrelation, but it is not advisable for this very situation. However, it is good to know about this option, it might be useful in the future
- you can use something like the one suggested by Madhivanan, but that solution is not performant, since you are doing very slow string operations to concatenate those fields. As a result, the code will be clear but slow
- Hanno Binder's solution is much better, since it uses the benefit of the or of not evaluating the second operand if the first was true. His code is fast, but you do not want to see it
My suggestion: You should define a stored function to make this calculation. That stored function should be equivalent to the solution suggested by Hanno Binder and you would just call that function in your query. So, your source-code will be clear, easy-to-read, correct and performant.
Related
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,%';
I have a field called 'areasCovered' in a MySQL database, which contains a string list of postcodes.
There are 2 rows that have similar data e.g:
Row 1: 'B*,PO*,WA*'
Row 2: 'BB*, SO*, DE*'
Note - The strings are not in any particular order and could change depending on the user
Now, if I was to use a query like:
SELECT * FROM technicians WHERE areasCovered LIKE '%B*%'
I'd like it to return JUST Row 1. However, it's returning Row 2 aswell, because of the BB* in the string.
How could I prevent it from doing this?
The key to using like in this case is to include delimiters, so you can look for delimited values:
SELECT *
FROM technicians
WHERE concat(', ', areasCovered, ', ') LIKE '%, B*, %'
In MySQL, you can also use find_in_set(), but the space can cause you problems so you need to get rid of it:
SELECT *
FROM technicians
WHERE find_in_set('B', replace(areasCovered, ', ', ',') > 0
Finally, though, you should not be storing these types of lists as strings. You should be storing them in a separate table, a junction table, with one row per technician and per area covered. That makes these types of queries easier to express and they have better performance.
You are searching wild cards at the start as well as end.
You need only at end.
SELECT * FROM technicians WHERE areasCovered LIKE 'B*%'
Reference:
Normally I hate REGEXP. But ho hum:
SELECT * FROM technicians
WHERE concat(",",replace(areasCovered,", ",",")) regexp ',B{1}\\*';
To explain a bit:
Get rid of the pesky space:
select replace("B*,PO*,WA*",", ",",");
Bolt a comma on the front
select concat(",",replace("B*,PO*,WA*",", ",","));
Use a REGEX to match "comma B once followed by an asterix":
select concat(",",replace("B*,PO*,WA*",", ",",")) regexp ',B{1}\\*';
I could not check it on my machine, but it's should work:
SELECT * FROM technicians WHERE areasCovered <> replace(areaCovered,',B*','whatever')
In case the 'B*' does not exist, the areasCovered will be equal to replace(areaCovered,',B*','whatever'), and it will reject that row.
In case the 'B*' exists, the areCovered will NOT be eqaul to replace(areaCovered,',B*','whatever'), and it will accept that row.
You can Do it the way Programming Student suggested
SELECT * FROM technicians WHERE areasCovered LIKE 'B*%'
Or you can also use limit on query
SELECT * FROM technicians WHERE areasCovered LIKE '%B*%' LIMIT 1
%B*% contains % on each side which makes it to return all the rows where value contains B* at any position of the text however your requirement is to find all the rows which contains values starting with B* so following query should do the work.
SELECT * FROM technicians WHERE areasCovered LIKE 'B*%'
I have a table which features 37 columns, of various types, INTs and VARCHARS and 2 LONGTEXT columns. The client wants a search to search the table and find the rows that match.
However, I'm having trouble with this. Here is what I've done so far:
1) My initial view was to do a massive set of OR queries - however I was put off this by the fact I would need to supply the search data ~30 times, which is massive repetition and I'm sure there;s a better way than this.
Code:
SELECT
MemberId,
MemNameTitle,
MemSName,
MemFName,
MemPostcode,
MemEmail
FROM
MemberData
WHERE
MemFName LIKE CONCAT('%',?,'%') OR
MemSName LIKE CONCAT('%',?,'%') OR
MemAddr LIKE CONCAT('%',?,'%') OR
MemPostcode LIKE CONCAT('%',?,'%') OR
MemEmail LIKE CONCAT('%',?,'%')
...etc...
Etc. Etc. That's a massive set of OR's and really unwieldy.
2) I thought I'd try and rework it to place all the columns in brackets and then only ask the query once, I saw a similar piece of code on SO but not sure that was correctly working, but it was an insprition, at least:
SELECT
MemberId,
MemNameTitle,
MemSName,
MemFName,
MemPostcode,
MemEmail
FROM
MemberData
WHERE
(MemNameTitle OR
MemFName OR
MemSName OR
MemAddr OR
MemPostcode OR
MemEmail OR
MemSkype OR
MemLinkedIn OR
MemFacebook OR
MemEmailTwo ...etc...) LIKE CONCAT('%',?,'%')
GROUP BY
MemberId
This code executes without apparent error but fails as it always returns no result, as in 0 fields returned. I can't see why, from an initial view,
3) So, with some research on OS I found a rearrangement using the IN keyword, but from previous questions on here Is it possible to use LIKE and IN for a WHERE statment? it appeared not to work.
What I wanted to get was something like:
SELECT
MemberId,
MemNameTitle,
MemSName,
MemFName,
MemPostcode,
MemEmail
FROM
MemberData
WHERE
MemNameTitle,
MemFName,
MemSName,
MemAddr,
MemPostcode,
MemEmail,
MemSkype,
MemLinkedIn,
...etc ...
MemFax,
MemberStatus,
CommitteeNotes,
SecondAddr,
SecondAddrPostcode IN (LIKE CONCAT('%',?,'%') )
This is crudy syntax but I hope you get the idea I want to get, I want to search many fields for the same value using a LIKE % % clause. Fields are variously TEXT/VARCHAR types.
4) I then looked into MySQL full text searches but this quickly became useless as this is only applied to TEXT type rather than VARCHAR type searching. I considered before each search changing each VARCHAR column to a TEXT column but figured that was also be relatively processor intensive and seemed illogical for a search that many people must want to do?
So, I'm out of ideas..... Can you help me search this way? Or suggest why my code in attempt 2 always returns Zero rows?
Cheers
Additional Work:
5) I have been looking at rearranging the IN clause statement and came up with this:
SELECT *(lazy typing!) WHERE
CONCAT('%',?,'%') IN
(MemNameTitle,
MemFName,
MemSName,
MemAddr,
MemPostcode,
MemEmail,
MemSkype,
...etc...
CommitteeNotes,
SecondAddr,
SecondAddrPostcode)
GROUP BY MemberId
However this returns a result, but the result is always the last row of the table. This doesn't work.
Solution 1:
From Ravinder, using CONCAT_WS for all the fields - this works in my case, although something in my mind does find CONCATs somewhat ugly, but oh well.
SELECT * FROM MemberData WHERE
CONCAT_WS('<*!*>',
MemNameTitle, MemFName, MemSName,
MemAddr, MemPostcode, MemEmail,
MemSkype, MemLinkedIn,
...etc...
MemberStatus, CommitteeNotes, SecondAddr,
SecondAddrPostcode)
LIKE CONCAT('%',?,'%')
GROUP BY MemberId ";
The table will eventually have a few thousand rows, and I am a little worried that as this query will concat 24 columns for each row on the table for each search, that this could easily become quite expensive and inefficient (ie slow), so if anyone has any ways of either
i) searching without CONCAT columns or
ii) making this solution faster/ more efficient
please share!!
There is a workaround solution. But I feel this is too crude and performance may not be that good.
where
concat_ws( "<*!*>", col1, col2, col3, ... ) like concat( '%', ?, '%' )
Here, I used '<*!*>' just as an example separator.
You have to use a pattern string as separator which, you are sure that,
is not part of the place holder value or
is not part of the generated string when 2 or more columns are
concatenated
Refer to Documentation:
MySQL: CONCAT_WS(separator,str1,str2,...)
It won't skip empty column values but NULLs.
One rather ugly way to do it would be
SELECT
MemberId,
MemNameTitle,
MemSName,
MemFName,
MemPostcode,
MemEmail
FROM
MemberData
WHERE
CONCAT(
MemNameTitle,
MemFName,
MemSName,
MemAddr,
MemPostcode,
MemEmail,
MemSkype,
MemLinkedIn,
...etc ...
MemFax,
MemberStatus,
CommitteeNotes,
SecondAddr,
SecondAddrPostcode) LIKE CONCAT('%',?,'%')
so you first concatenate all the columns you want to search and then look in the resulting big string for your text.
But i guess you can see that this is far from performant and optimal. But since you are using the % sign in the beginning and end of your searches, you couldn't use any indexes anyway.
Warning:
Be aware that this CONCAT may fail in case one of your columns contains a null value, because then the whole CONCAT will return null!
Consider I am having tow fields in my table
field_profile_first_name_value field_profile_last_name_value
Victor Brecher
Abhi Jass
select field_profile_first_name_value, field_profile_last_name_value
from content_type_profile where field_profile_first_name_value LIKE '%Victor Bre%' OR
field_profile_last_name_value LIKE '%Victor Bre%'
I am having a auto complete text box.
When i enter the keyword as victor it will fetch the result. But if i give the first name and last name in the same time it will not produce the result.
That is if i give the keyword as Victor Brecher it will not produce the result.
How can i make to get the result if i enter first name and last name ?
How can i make it ?
Try :
select * from contracts
where lower(concat_ws(' ', field_profile_first_name_value, field_profile_last_name_value))
like lower('%Victor Bre%')
Well, even you don't need to use lower too. just use it simply.
select * from contracts where concat_ws(' ', field_profile_first_name_value,field_profile_last_name_value)
like '%Victor Bre%'
I would make use of soundex.
select * from contracts where SOUNDEX(concat_ws(' ', field_profile_first_name_value,field_profile_last_name_value)) = SOUNDEX('Victor Bre');
I suggest to use this code, so the search works also if the search input is the lastname before the name
SELECT
*
FROM
contracts
WHERE
CONCAT(firstname, ' ', lastname)) LIKE 'Victor Bre%'
OR CONCAT(lastname, ' ', firstname)) LIKE 'Victor Bre%';
Moreover, I tested this sql code with and without LOWER(), in mysql 5.7+ the search already do that.
I need to search on two fields using LIKE function and should match also in reverse order. My table uses InnoDB which dont have Full text search.
Consider the following case:
I have users table with first_name and last_name column. On it, there is a row with the following value:
{
first_name: 'Ludwig',
last_name: 'van Beethoven',
}
Cases:
Can search "Ludwig van Beethoven"
Can search "Beethoven Ludwig"
Can search "Ludwig"
Can search "Beethoven"
I tried this SQL statement but no luck.
SELECT CONCAT(first_name, ' ', last_name) as fullname
FROM users
WHERE fullname LIKE '%Ludwig van Beethoven%';
You need to re-state the concat expression in your where clause.
SELECT CONCAT(first_name, ' ', last_name) as fullname
FROM users
WHERE CONCAT(first_name, ' ', last_name) LIKE '%doe%';
Unfortunately "as" just create a column alias, not a variable that you can use elsewhere.
The Main Thing
Make sure you have a compound index on first_name and last_name. Otherwise, it's really easy to end up doing a full table scan regardless of how you approach this. So if you don't already have one, create one:
CREATE INDEX users_firstandlast ON users(first_name, last_name);
Syntax Options
Once that index is in place, you have some options:
Option 1: As Willis Blackburn said, repeat the CONCAT in your WHERE clause (because AS doesn't create a name you can use in the WHERE clause):
SELECT CONCAT(first_name, ' ', last_name) as fullname
FROM users
WHERE CONCAT(first_name, ' ', last_name) LIKE '%doe%';
Use EXPLAIN to check in your specific situation, but in my tests it says it uses the compound index, even though you're using a function in the WHERE clause.
Option 2: In this particular case, you can always just use two LIKE s in your WHERE clause:
SELECT CONCAT(first_name, ' ', last_name) as fullname
FROM users
WHERE first_name LIKE '%doe%' or last_name LIKE '%doe%';
Again this can make use of the compound index (whereas it won't make use of individual indexes on the first_name and last_name columns -- it would if you weren't leading with a wildcard, but according to EXPLAIN [and your mileage may vary, always check], in that case it goes with the table scan).
Option 3 In his answer, Andy says you can use HAVING for this. My read of the MySQL manual suggests it will first build the result set, and only then apply HAVING at the very end before sending it to the client, and so I'd be leery of this. But, in my quick and dirty tests, EXPLAIN tells me that if you have the compound index I mentioned above, the HAVING version does an index search, not a table scan. If your tests with real data bear that out, that may be a good option for you. This use of HAVING in this way is a MySQL extension (not standard), but then again, so is CONCAT so we're already into MySQL-specific stuff. :-) But again, double-check in your real life environment.
Conclusion
Create the index if you don't already have it, then I'd go with Option 2 if it's remotely a possibility; otherwise, option 1 unless you can find (or Andy can provide) a reference for the HAVING thing not building a massive interim result set (it would be really cool, if non-standard, if it didn't). Regardless, check with EXPLAIN, and test, in your specific environment.
SELECT * FROM users WHERE CONCAT(first_name, ' ', last_name) LIKE '%Ludwig%' OR CONCAT(last_name, ' ', first_name) LIKE '%Ludwig%';
All search cases where returned including 'Beethoven Ludwig'.
When you CONCAT() two columns the LIKE become case sensitive. So this should find you results but isn't optimal for performance:
SELECT CONCAT(first_name, ' ', last_name) AS fullname FROM users
WHERE LOWER(CONCAT(first_name, ' ', last_name)) LIKE LOWER('%doe%');
That's getting MySQL to do work on each row though.