0

I have a MySQL table that looks like the following:

    uid       title 
    ____     ________
    aaac      A, 73, B5 
    aaab      A, B6

"title" holds a string of values separated by commas. There is always at least two values in title. I need to query this Table and return the uid. I will be passing in a string via POST for title to check for. However, order of this string is not important. The string could look like:

    73, B5, A

and this would need to return aaaa

Any ideas how to setup the query?

Sente
  • 277
  • 4
  • 11
  • not sure your db structure is good for this one. If you can have the "string" in order (so A is always first ...), you may try something like `where title LIKE "%A,%73,%B5,%"` – insider Aug 27 '17 at 20:38
  • 3
    [normalise](https://en.wikipedia.org/wiki/First_normal_form) the db first –  Aug 27 '17 at 20:39
  • 3
    That db-structure is pretty bad. You shouldn't save values comma separated like that. Read this answer for more details why: https://stackoverflow.com/questions/3653462/is-storing-a-delimited-list-in-a-database-column-really-that-bad/3653574#3653574 – M. Eriksson Aug 27 '17 at 20:39
  • @insider That example will fail if the data is in another order, like the OP says it can be. `%` is simply a wildcard for any character(s), not for any order. Plus, the last comma will make it fail, even if it is in the correct order. – M. Eriksson Aug 27 '17 at 20:40
  • @MagnusEriksson thtas why the sentence starts with "if you can have the "string" in order" (maybe not the best wording - I meant if it can be altered in way the keys are always sorted) – insider Aug 27 '17 at 20:43
  • I know it is wonky. It is like this because there could be any number of values passed in – Sente Aug 27 '17 at 20:43
  • 2
    NB: a normalised database is perfect for "any number of values". – trincot Aug 27 '17 at 20:45

1 Answers1

0

First of all, storing multiple values in one field goes against the first rule in normalisation of a database. Things become a lot easier if you apply normalisation.

But given this situation, you could aim for a query with a where clause like this:

WHERE FIND_IN_SET(?, replace(title, ' ', ''))
AND   FIND_IN_SET(?, replace(title, ' ', ''))
AND   FIND_IN_SET(?, replace(title, ' ', ''))

You would create each line dynamically in PHP, for instance like this:

$items = explode(",", $_POST["search"]);
$conditions = implode(" AND ", 
     array_fill(0, count($items), "FIND_IN_SET(?, replace(title, ' ', ''))")
);
$sql = "SELECT ......... WHERE $conditions";

Then use a prepared statement and bind_params to bind the $items to the placeholders (?).

Normalised approach

It is better to create a new table with one record per individual piece: (uid, piece).

So instead of the following records in the main table (table1):

uid       title       other
____     ________     _______
aaac      A, 73, B5   x
aaab      A, B6       y

You would remove the title column:

uid       title       other
____     ________     _______
aaac      A, 73, B5   x
aaab      A, B6       y

And add these records in a new table table2

uid       piece      
____     ________    
aaac      A
aaac      73
aaac      B5
aaab      A
aaab      B6    

Then the SQL would look like:

FROM       table1 
INNER JOIN table2 ON table1.uid = table2.uid
WHERE      table2.piece in (?, ?, ?)
GROUP BY   table1.uid
HAVING     count(distinct table2.piece) = 3
trincot
  • 317,000
  • 35
  • 244
  • 286
  • the string is coming in from an iOS app like that. I just thought it would be simple to store it like that too. So, the better Table would split up the string into pieces and store "uid" along with a corresponding title piece for each string? – Sente Aug 27 '17 at 21:11
  • Yes, that would be the normalised schema: if you have 3 pieces related to a `uid`, then in the new, dependent table you would insert three rows, one for each piece. – trincot Aug 27 '17 at 21:15
  • ok. And then for the query in question. I could break apart the string. Query the DB and return every uid for each piece. The uid that all three have in common (if the string had 3 pieces) is the one I want. – Sente Aug 27 '17 at 21:27
  • Yes, that you can do with `where piece in (?, ?, ?) group by uid having count(distinct piece) = 3`. The `?` are the place holders for the user-provided values. – trincot Aug 27 '17 at 21:29
  • "having count" is important because the values are not necessarily unique right? (A, B) could return 2 uids if (A, B, C) exists – Sente Aug 27 '17 at 21:34
  • The count is important to make sure you have that number of matches. The `distinct` in the count is to make it work even if you would have duplicate records `(uid, piece)`, as such duplicates would increase the bare count. The `3` must of course be adapted to the actual number of provided items. NB: you should also eliminate duplicate pieces from the user's input before building the SQL. – trincot Aug 27 '17 at 21:38