1

I have two tables in one DB,

  tbl_language:                           tbl_users
  lang_id    lang_name               user_id          user_name        user_lang
  1          English                                 
  2          Spanish                                 
  3          German
  4          Portuguese

I have PHP code to select languages from tbl_lang, Now I have a multiple select in a form where users can choose more than one languages. While inserting the values in database, I want to store multiple language values in one column/attribute. Where user_lang attribute would only store lang_ids of the tbl_language separated by comma(,).

Like

  tbl_users:
  user_id          user_name        user_lang
  1                 ABC              1,2
  2                 XYZ              1,4

How to accomplish this using PHP and MySQL ?

I have sample code here:

<?php

$user_name = $_POST['user_name'];
$user_lang = $_POST['user_lang']; // user_lang as array from multiple select
$qry = ""; // I need help with this query
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
  • The continuation of question here, I guess Stackoverflow has limited characters while asking question::: anyways below is my sample code... I have sample code here: –  Aug 06 '12 at 03:45
  • consider the use of `implode()` – Yang Aug 06 '12 at 03:45
  • 6
    Don't store multiple values in a single field. It defeats the purpose of having a relational database in the first place. Normalize your design now, before this setup bites you in the rump down the road. – Marc B Aug 06 '12 at 03:46
  • I wish I could upvote Marc's comment, again and again. – OMG Ponies Aug 06 '12 at 03:48
  • Thanks @MarcB for insight. But I don't understand normalization here. I already have two tables containing few columns, but when I have to store the user who speaks several languages, how to normalize it. It'd be great help if you could shed some lights about it. –  Aug 06 '12 at 03:55
  • I'll do that for you @OMG Ponies – the_red_baron Aug 06 '12 at 03:56
  • @JhilkeDai: if a field calls for multiple values, you split it out into its own dedicated table, where each of those values gets its own record, and a foreign key pointing back at the original table. – Marc B Aug 06 '12 at 04:30

4 Answers4

2

From your setup, considering that user_id is an auto increment PK :

$user_name = mysql_real_escape_string($_POST['user_name']);
$user_lang = mysql_real_escape_string(implode(',', $_POST['user_lang']);
$sql = "INSERT INTO tbl_users (`user_name`, `user_lang`) VALUES ('{$user_name}', '{$user_lang}');

Howerver, as others have mentioned, you should probably look for a 3rd level table :

tbl_users_lang
user_id    lang_id

And insert like (for example, no error handling, just a suggestion)

$user_name = mysql_real_escape_string($_POST['user_name']);
$sql = "INSERT INTO tbl_users (`user_name`) VALUES ('{$user_name}');"
mysql_query($sql);

$user_id = mysql_insert_id();
foreach ($user_lang as $lang) {
   $lang = mysql_real_escape_string($lang);
   $sql = "INSERT INTO tbl_users_lang (`user_id`, `lang_id`) VALUES ({$user_id}, '{$lang}');"
   mysql_query($sql);       
}

** Edit **

Just as a bonus, with this query, you can fetch a comma separated row for the language list of any given user. Thought you might be interested :

SELECT DISTINCT u.user_id, u.user_name, GROUP_CONCAT(l.lang_name) 
  FROM `tbl_users` as u
  LEFT JOIN `tbl_users_lang` as ul ON u.user_id = ul.user_id 
  LEFT JOIN `tbl_language` as l on ul.lang_id = l.lang_id
  -- add WHERE clause here to filter (ex : WHERE u.user_name LIKE 'John%')
Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214
  • Aye, now I see the normalized db... my brain didn't work that way.... thank you very much Yanick !!! –  Aug 06 '12 at 04:07
1

This is what you want:

File view.html

<form method="POST" action="test.php">


    <label>Select langs: </label>
            <select name="languages[]" multiple="yes" size="5">

                <!--OR PUT lang id's instead of "en, fr, de" -->
                <option value="en">English</option>         <option value="fr">French</option>      <option value="de">Deutsch</option>


    </select>

    <button type="submit">Sub the form</button>

</form>

File : test.php

<?php

 // check if form submitted if so do 
 if ( !empty($_POST) ){

    //$_POST['languages'] is an array itself
    $langs = implode(', ', $_POST['languages']);
    //$langs you want to insert into the table:
    print $langs;

 }
Yang
  • 8,580
  • 8
  • 33
  • 58
  • thank you friend for your effort... this was what I was looking for until I knew more normalized form. –  Aug 06 '12 at 04:09
  • @JhilkeDai I could suggest: normalization, MVC pattern, OOP design, XSS/SQL filter... But I've tried to be very very specific to the question first – Yang Aug 06 '12 at 04:13
  • I am just stepping in the PHP/MySQL, I would be asking lots of questions and will need lots of help from you guys. Glad to have you guys with various knowledge who are willing to share their knowledge around. –  Aug 06 '12 at 04:24
  • @JhilkeDai www.tuxradar.com/practicalphp/ – Yang Aug 06 '12 at 04:25
0

Like Marc B suggested above, I wouldn't store multiple values in the same column - this is bad practice. You can save one line per language in the second table and still achieve the required result using group_concat as follows:

select t2.user_id, t2.user_name, 
group_concat(t1.lang_name separator ', ') as language
from tbl_users, tbl_language
where t1.lang_id = t2.user_lang

you can see an example here

Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
  • That query implies that there should be duplicated user records for each language. There should be a third, user_langs (or whatever it's named) table to handle the relation. – Corbin Aug 06 '12 at 04:07
  • @Corbin I totally agree with you that having a third table that connects between users and languages is a better practice, and of course that the same result can be achieved with three tables as well (using `group_concat`). – Nir Alfasi Aug 06 '12 at 04:10
  • To be a clearer, what I was getting at is that you've denormalized the data. The users table should store unique user records. The same user shouldn't have two rows because there's two languages for that user. In my opinion, the third table isn't a "better practice"; it's the *correct* practice. – Corbin Aug 06 '12 at 04:27
  • @Corbin you've been perfectly clear on your first comment and I already said I agree with you. I'm not sure what you were trying to achieve with your last comment. TMO there is no such thing as "correct practice", only "bad" and "good" and both of them are subjective. Sometimes you'll want to denormalize data to achieve something (for example, for reporting purposes) - and it's perfectly fine (again TMO). – Nir Alfasi Aug 06 '12 at 04:45
  • In this situation, it's not subjective. There are distinct advantages and disadvantages of both approaches. In particular, your approach has a disadvantage that a third table does not have. Your approach must duplicate all user related data. Every time a password is changed, every time an email address is changed, or a name, etc... It doesn't make sense to repeat a distinct entity for no reason. Denormalization has it's place, but that place is not here. This denormalization achieves nothing. – Corbin Aug 06 '12 at 04:58
  • @Corbin you were right if `tbl_users` would have contained all the columns you have just mentioned (password, email etc). But, this is not the case in the example above. You insist on arguing when I already said I agree with (in general - not in the specific example). I also agree that denormalization has its place. Ta-ta. – Nir Alfasi Aug 06 '12 at 05:21
  • The user_name column is sufficient in its own right to warrant normalization. But anyway... Whatever. – Corbin Aug 06 '12 at 06:19
0

Firstly, yes, not placing multiple values into one field, spreadsheet cell, or array element is the official answer. However, when you need to:

  1. The field type to use is integer or large integer
  2. Use code values that are prime numbers
    • 3 == English
    • 5 == French
    • 7 == Spanish
    • 11 == Italian
  3. Store the product of all that apply into the field.
    • 21 == English and Spanish
    • 385 == French, Spanish and Italian
  4. Use modulo functions to determine which values are in the field

    if ( field % 3 == 0 ) {  english() ;}
    if ! (field % 5) { french() ;}
    
  5. The same value can appear multiple times
    • 9 == English, English

I first used this technique to store dimensions.

  • 3 == time
  • 5 == length
  • 7 == mass
  • 11 == charge
  • 13 == temperature
  • 17 == moles

For example, a first moment lever arm would have a dimension value of 35 == mass * length. To store fractional dimensions in an integer, I multiplied fractional dimensions by the product of all of them and dealt with it in processing.

  • 255255 == 3*5*7*11*13*17
  • force == mass * length / (second^2)
  • force == ( 7 * 5 / ( 3 * 3 ) ) * 255255 * 255255
  • force == 253381002875

Please do not ask for the code to extract the fractional dimensions. All this was 40 years ago in APL.

user15972
  • 124
  • 4