How to compare two comma-separated string lists using MySQL - mysql

I used a Java method called 'containsAll()' to check if ArrayLists have common content.
Let's say I have a list A (one row), and several other lists in a MySQL table (in column 'name', row by row).
All lists consist of comma-separated Strings (at least one String in a list) - names or whatever.
Now, I want to check if all Strings in list A can be found in any of the rows in column 'name'.
The result set should show all the rows in 'name' that match, that includes rows/lists must have all Strings in list A, and can have additional Strings.
Example I
A: 'Mr.T'
____name_________________________________________
'Hannibal'
'Hannibal','Face','Murdock','Mr.T','Donald Duck'
'Face','Donald Duck'
'Superman','Chuck Norris','Mr.T'
_________________________________________________
Result set: 'Hannibal','Face','Murdock','Mr.T','Donald Duck' -AND-
'Superman',Chuck Norris','Mr.T'
Example II
A: 'Rocky', 'Mr.T', 'Apollo'
______name__________________________________________________
'Hannibal','Face','Murdock','Donald Duck','Superman','Mr.T'
'Rocky','Apollo','Ivan'
'Apollo', 'Superman','Hannibal','Rocky','Mr.T','Chuck Norris'
'Rocky','Mr.T','Apollo','Chuck Norris'
_____________________________________________________________
Result set: 'Apollo', 'Superman','Hannibal','Rocky','Mr.T','Chuck Norris' -AND-
'Rocky','Mr.T','Apollo','Cuck Norris'
I wonder if one can carry out those results using a MySQL query.
Thank you in advance.

It appears you want to do an array intersection, except your array is a single column. It can be done, but it will be slow, difficult to debug and will not leverage the power of relational databases. A better way would be to change your table schema to something like this:
Table groups
group_id int unsigned not null auto_increment primary key,
character_list text
Table members_in_group
group_id int unsigned not null,
group_member varchar(45) not null
Then you can query like this:
SELECT group_id, character_list
FROM groups g
JOIN members_in_groups m USING (group_id)
WHERE m.group_member IN ('Mr. T', ...);
The groups table is probably very like your current table. The members_in_groups table is the same data chopped up into easily searchable parts.
ETA given your comment, this should work if you can guarantee that each character_list contains only one instance of each character:
SELECT group_id,
SUM(CASE m.group_member IN ('Mr. T', 'Apollo', 'Rocky') THEN 1 ELSE 0 END) AS tally,
character_list
FROM groups g
JOIN members_in_groups m ON (g.group_id=m.group_id)
GROUP BY group_id
HAVING SUM(CASE m.group_member IN ('Mr. T', 'Apollo', 'Rocky') THEN 1 ELSE 0 END) = 3;
In this case the HAVING clause must equal 3 because there are 3 members in IN ('Mr. T', 'Apollo', 'Rocky').

I solved this issue by using the REGEXP function in MySQL:
SELECT * FROM `table` WHERE `column` REGEXP("(value1|value2|value3)");

SELECT * FROM tbl_name WHERE field_name LIKE '%\'Mr.T\'%'

Related

What is the proper MySQL way to take data from 4 rows, 1 column, and separate into 9 columns?

I've studied and tried days worth of SQL queries to find "something" that will work. I have a table, apj32_facileforms_subrecords, that uses 7 columns. All the data I want to display is in 1 column - "value". The "record" displays the number of the entry. The "title" is what I would like to appear in the header row, but that's not as important as "value" to display in 1 row based upon "record" number.
I've tried a lot of CONCAT and various Pivot queries, but nothing seems to do more than "get close" to what I'd like as the end result.
Here's a screen shot of the table:
The output "should" be linear, so that 1 row contains 9 columns:
Project; Zipcode; First Name; Last Name; Address; City; Phone; E-mail; Trade (in that order). And the values in the 9 columns come from "value" as they relate to the "record" number.
I know there are LOT of examples that are similar, but nothing I've found covers taking all the values from "value" and CONCAT to 1 row.
This works to get all the data I want - SELECT record,value FROM apj32_facileforms_subrecords WHERE (record IN (record,value)) ORDER BY record
But the values are still in multiple rows. I can play with that query to get just the values, but I'm still at a loss to get them into 1 row. I'll keep playing with that query to see if I can figure it out before one of the experts here shows me how simple it is to do that.
Any help would be appreciated.
Using SQL to flatten an EAV model representation into a relational representation can be somewhat convoluted, and not very efficient.
Two commonly used approaches are conditional aggregation and correlated subqueries in the SELECT list. Both approaches call out for careful indexing for suitable performance with large sets.
correlated subqueries example
Here's an example of the correlated subquery approach, to get one value of the "zipcode" attribute for some records
SELECT r.id
, ( SELECT v1.value
FROM `apj32_facileforms_subrecords` v1
WHERE v1.record = r.id
AND v1.name = 'zipcode'
ORDER BY v1.value LIMIT 0,1
) AS `Zipcode`
FROM ( SELECT 1 AS id ) r
Extending that, we repeat the correlated subquery, changing the attribute identifier ('firstname' in place of 'zipcode'. looks like we we could also reference it by element, e.g. v2.element = 2
SELECT r.id
, ( SELECT v1.value
FROM `apj32_facileforms_subrecords` v1
WHERE v1.record = r.id
AND v1.name = 'zipcode'
ORDER BY v1.value LIMIT 0,1
) AS `Zipcode`
, ( SELECT v2.value
FROM `apj32_facileforms_subrecords` v2
WHERE v2.record = r.id
AND v2.name = 'firstname'
ORDER BY v2.value LIMIT 0,1
) AS `First Name`
, ( SELECT v3.value
FROM `apj32_facileforms_subrecords` v3
WHERE v3.record = r.id
AND v3.name = 'lastname'
ORDER BY v3.value LIMIT 0,1
) AS `Last Name`
FROM ( SELECT 1 AS id UNION ALL SELECT 2 ) r
returns something like
id Zipcode First Name Last Name
-- ------- ---------- ---------
1 98228 David Bacon
2 98228 David Bacon
conditional aggregation approach example
We can use GROUP BY to collapse multiple rows into one row per entity, and use conditional tests in expressions to "pick out" attribute values with aggregate functions.
SELECT r.id
, MIN(IF(v.name = 'zipcode' ,v.value,NULL)) AS `Zip Code`
, MIN(IF(v.name = 'firstname' ,v.value,NULL)) AS `First Name`
, MIN(IF(v.name = 'lastname' ,v.value,NULL)) AS `Last Name`
FROM ( SELECT 1 AS id UNION ALL SELECT 2 ) r
LEFT
JOIN `apj32_facileforms_subrecords` v
ON v.record = r.id
GROUP
BY r.id
For more portable syntax, we can replace MySQL IF() function with more ANSI standard CASE expression, e.g.
, MIN(CASE v.name WHEN 'zipcode' THEN v.value END) AS `Zip Code`
Note that MySQL does not support SQL Server PIVOT syntax, or Oracle MODEL syntax, or Postgres CROSSTAB or FILTER syntax.
To extend either of these approaches to be dynamic, to return a resultset with a variable number of columns, and variety of column names ... that is not possible in the context of a single SQL statement. We could separately execute SQL statements to retrieve information, that would allow us to dynamically construct a SQL statement of a form show above, with an explicit set of columns to be returned.
The approaches outline above return a more traditional relational model, (individual columns each with a value).
non-relational munge of attributes and values into a single string
If we have some special delimiters, we could munge together a representation of the data using GROUP_CONCAT function
As a rudimentary example:
SELECT r.id
, GROUP_CONCAT(v.title,'=',v.value ORDER BY v.name) AS vals
FROM ( SELECT 1 AS id ) r
LEFT
JOIN `apj32_facileforms_subrecords` v
ON v.record = r.id
AND v.name in ('zipcode','firstname','lastname')
GROUP
BY r.id
To return two columns, something like
id vals
-- ---------------------------------------------------
1 First Name=David,Last Name=Bacon,Zip Code=98228
We need to be aware that the return from GROUP_CONCAT is limited to group_concat_max_len bytes. And here we have just squeezed the balloon, moving the problem to some later processing, to parse the resulting string. If we have any equal signs or commas that appear in the values, it's going to make a mess of parsing the result string. So we will have to properly escape any delimiters that appear in the data, so that GROUP_CONCAT expression is going to get more involved.

MySQL lookup based on round(1 + rand() * x) produces NULL and multiple results

I'm trying to select first names from a lookup table at random in MySQL to build a test dataset. I have a table with 200 first names, genders and a row id going from 1 to 200. Something like this:
id firstname gender
1 Aaron m
2 Adam m
3 Alan m
etc...
I'm selecting from this table using a random generator with the following query:
SELECT id, firstname FROM firstname WHERE id = round(1 + (rand() * 199));
I am expecting the random number to tally up with exactly one id from the lookup table, thus producing a single results like
id firstname
43 Jason
Running the code again and again instead gives me a selection of
single rows (as above)
or multiple rows like
id firstname
29 Ethan
147 Jean
or no results (just NULL in both fields).
If I run the random generator on its own, it will always generate a number between 1 and 200. As you can see below, the id field is INT, and the query behaves the same way if I cast the result as SIGNED. I have also tried to use FLOOR instead of ROUND, just to see if that worked any differently - alas, no.
Can anyone tell my why the anomaly? What am I missing?
Here is some code to create the first 20 rows of the original table for testing purposes:
-- First Name --
drop table if exists firstname;
CREATE TABLE firstname (
id INT NOT NULL,
firstname VARCHAR(20) NOT NULL,
gender VARCHAR(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE (firstname)
);
INSERT INTO firstname
(id,firstname,gender)
VALUES
(1,"Aaron","m"),
(2,"Adam","m"),
(3,"Alan","m"),
(4,"Albert","m"),
(5,"Alexander","m"),
(6,"Andrew","m"),
(7,"Anthony","m"),
(8,"Arthur","m"),
(9,"Austin","m"),
(10,"Benjamin","m"),
(11,"Billy","m"),
(12,"Bobby","m"),
(13,"Brandon","m"),
(14,"Brian","m"),
(15,"Bruce","m"),
(16,"Bryan","m"),
(17,"Carl","m"),
(18,"Charles","m"),
(19,"Christian","m"),
(20,"Christopher","m");
Since RAND() is not deterministic, the WHERE condition is evaluated/executed once per each row. Thus each row has a chance of 1/199 to be selected. You can use a subquery in the FROM clause (derived table) instead to generate exactly one random number:
SELECT f.id, f.firstname
FROM firstname f
JOIN (SELECT floor(rand()*200)+1 as rnd) r ON r.rnd = f.id

FULLTEX Search on two column of Different Table in Mysql

Can anyone help me to find the query for fulltext search?
I have two columns Product and Generic.
Table-Product:
1. ProductID (Integer)
2. GenericID (Integer)-FK
3. Product_Name (Varchar)
And in Table-Generic:
1. GenericID (Integer)
2. Generic_Name (Varchar)
What I need is to search the input string with the combined name of both Product_Name and Generic_Name.
my sample query is given below
SELECT
prod.ProductID AS ID,
generic.Generic_Name AS genericName,
prod.Product_Name AS packageName
FROM
Product prod
INNER JOIN
Generic generic ON prod.GenericID = generic.GenericID
WHERE
MATCH (prod.Product_Name ,generic.Generic_Name) AGAINST('+acb* +ace* +serr* +para*' IN BOOLEAN MODE)
ORDER BY prod.Product_Name ASC
It doesn't work because the columns are in different tables.
FULLTEXT search operations each use a FULLTEXT index. That index can only be on one table.
So, you could try using two fulltext search operations...
WHERE (
match(prod.Product_Name) against('+acb* +ace* +serr* +para*' in boolean mode)
OR
match(generic.Generic_Name) against('+acb* +ace* +serr* +para*' in boolean mode)
)
Or, for best performance and result-set ranking, you could build a new name table like this
GenericId NOT a primary key
IsGeneric 1 or 0
Name either Product_Name or Generic_Name
You would construct this table from the union of the names in your other two tables. For example, it might contain
4321 0 Advil
4321 0 Motrin
4321 1 Ibuprofen
4322 0 Coumadin
4322 1 Warfarin
Then, a query like this would do the trick
select prod.ProductID AS ID,
generic.Generic_Name AS genericName,
prod.Product_Name AS packageName
FROM Product prod
INNER JOIN Generic generic ON prod.GenericID = generic.GenericID
INNER JOIN Name ON Name.GenericID = prod.GenericID
WHERE MATCH(Name.Name) AGAINST('+acb* +ace* +serr* +para*' in boolean mode)
ORDER BY prod.Product_Name ASC
The second alternative is more work to program. But, because it puts both tradenames and generic names into a single fulltext index, it will be faster and it is likely to give better results.

Searching for data in SQL

Please take a look at the following table:
I am building a search engine which returns card_id values, based on search of category_id and value_id values.
To better explain the search mechanism, imagine that we are trying to find a car (card_id) by supplying information what part (value_id) the car should has in every category (category_id).
In example, we may want to find a car (card_id), where category "Fuel Type" (category_id) has a value "Diesel" (value_id), and category "Gearbox" (category_id) has a value "Manual" (value_id).
My problem is that my knowledge is not sufficient to build a query, which will returns card_ids which contains more than one pair of category_id and value_id.
For example, if I want to search a car with diesel engine, I could build a query like this:
SELECT card_id FROM cars WHERE category_id=1 AND value_id=2
where category_id = 1 is a category "Fuel Type" and value_id = 2 is "Diesel".
My question is, how can I build a query, which will look for more category-value pairs? For example, I want to look for diesel cars with manual gearbox.
Any help will be very appreciated. Thank you in advance.
You can do this using aggregation and a having clause:
SELECT card_id
FROM cars
GROUP BY card_id
HAVING SUM(category_id = 1 AND value_id = 2) > 0 AND
SUM(category_id = 3 and value_id = 43) > 0;
Each condition in the having clause counts the number of rows that match a given condition. You can add as many conditions as you like. The first, for instance, says that there is at least one row where the category is 1 and the value is 2.
SQL Fiddle
Another approach is to create a user defined function that takes a table of attribute/value pairs and returns a table of matching cars. This has the advantage of allowing an arbitrary number of attribute/value pairs without resorting to dynamic SQL.
--Declare a "sample" table for proof of concept, replace this with your real data table
DECLARE #T TABLE(PID int, Attr Int, Val int)
--Populate the data table
INSERT INTO #T(PID , Attr , Val) VALUES (1,1,1), (1,3,5),(1,7,9),(2,1,2),(2,3,5),(2,7,9),(3,1,1),(3,3,5), (3,7,9)
--Declare this as a User Defined Table Type, the function would take this as an input
DECLARE #C TABLE(Attr Int, Val int)
--This would be populated by the code that calls the function
INSERT INTO #C (Attr , Val) VALUES (1,1),(7,9)
--The function (or stored procedure) body begins here
--Get a list of IDs for which there is not a requested attribute that doesn't have a matching value for that ID
SELECT DISTINCT PID
FROM #T as T
WHERE NOT EXISTS (SELECT C.ATTR FROM #C as C
WHERE NOT EXISTS (SELECT * FROM #T as I
WHERE I.Attr = C.Attr and I.Val = C.Val and I.PID = T.PID ))

Inner join A on B if B not empty, else A

Two tables:
prefix ( id, value )
---------------------
1 'hello'
2 'good afternoon'
3 'good night'
suffix ( id, value )
---------------------
1 'world'
3 'world'
I'd like to get
all from table prefix which can be joined on table suffix via id
result should look like:
prefix.id prefix.value
--------------------------
1 'hello'
3 'good night'
well - quite easy so far...
but if table suffix is empty I'd like everything from table prefix
without subselects/ctes or if.... and in one query fulfilling both conditions!
Is there any trick to get this done by some magic having-clause or tricky something else?
Just for testcases: SQL-fiddle
Well, there is a way, but I agree with others that your requirements make no (practical) sense.
Anyway, here you go:
Join the suffix table twice (each time with a left join). One join is on the id column, the other on an always true condition.
Group the results on the prefix columns you want in the output and at least one non-nullable column of the first instance of suffix.
In the HAVING clause, put a condition that the first suffix column is not null or the number of values of a non-nullable column in the second suffix instance is 0. (Obviously, every group will have the same number of rows, i.e. the count will be the same for every prefix row.)
This is the query:
SELECT prefix.id, prefix.value
FROM prefix
LEFT JOIN suffix ON prefix.id = suffix.id
LEFT JOIN suffix AS test ON 1=1
GROUP BY prefix.id, prefix.value, suffix.id
HAVING suffix.id IS NOT NULL OR COUNT(test.id) = 0;
And there's also a demo at SQL Fiddle.
You need an OR and NOT EXISTS:
SELECT
prefix.id, prefix.value
FROM
prefix
WHERE
EXISTS(SELECT 1 from suffix WHERE prefix.id=suffix.id)
OR NOT EXISTS(SELECT 1 FROM suffix)
Demo
I guess the answer is: no, you can't!
Or if you can: No, you shouldn't.