Connecting Tech Pros Worldwide Help | Site Map

Select Multiple Rows and Combine / Insert into One Row

 
LinkBack Thread Tools Search this Thread
  #1  
Old January 8th, 2009, 04:58 AM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default Select Multiple Rows and Combine / Insert into One Row

Hi All,

I am attempting to merge something like this in my MySQL database:

Name | Dept
=========
John Account
Wayne Account
James Management
Bond Management

into something like this:

Name | Dept
=========
John,Wayne Account
James,Bond Management

So far, I have found that by running the MySQL Script:
Expand|Select|Wrap|Line Numbers
  1. SELECT dept,
  2. GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ',')
  3. FROM Table
  4. GROUP BY dept
will do the stuff I need in getting the data out, but how do I then run a insert / update to insert it into the column Name?

Any help is greatly appreciated. Thanks in advance.

Last edited by Atli; January 8th, 2009 at 05:32 AM. Reason: Added [code] tags
Reply
  #2  
Old January 8th, 2009, 05:44 AM
Atli's Avatar
Moderator
 
Join Date: Nov 2006
Location: Iceland
Age: 22
Posts: 3,523
Default

Hi.

You really, really shouldn't try to do that.

You should never put more than one piece of data into a single field. It is a very bad idea, because in that format, your database has no control over it.
MySQL will consider it all to be a single string, which puts your data at a much greater risk of getting corrupted, and makes it that much more complex for the front-end software to actually use the data.

I suggest you check out this article. It explains the 3 basic rules you should always keep in mind when designing a database.
Database Normalization and Table Structures.
Reply
  #3  
Old January 8th, 2009, 05:53 AM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default

Hi Atli,

Thanks for the reply. I do understand the basic principle behind database design, however, in this case, the software I am using required that the data be combined into a single row in a single column for it to function, thus the question on how to do it... :)

I've so far been able to work out how to use GROUP_CONCAT, but unfortunately, how to tie in updating the table's column and row with the new data is still beyond me.

If i was to use a temp table, will it help? eg:

Table1
NAME | DEPT
==========
JOHN ACCOUNT
DOE ACCOUNT
JANE MANAGEMENT
DOE MANAGEMENT

with the new data combined into a new table like:

Table2
NAME2 | DEPT2
===========
JOHN,DOE ACCOUNT
JANE,DOE MANAGEMENT
Reply
  #4  
Old January 8th, 2009, 06:27 AM
Administrator
 
Join Date: Sep 2006
Posts: 11,940
Default

Introducing the other table will give rise to the need to synchronize it's contents with the original tables. If your application requires the fields to be concatenated for view purposes only then there is no need to change the tables. For the update part, your front end language should catch the data and convert it into the format required by the back end.
Basically database design should be done independently of how the data needs to be presented. In this case I think that you are trying to change the back end to satisfy a presentation format which should never be done.
Reply
  #5  
Old January 8th, 2009, 06:38 AM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default

Hi,

Perhaps a more detail description of the issue i'm facing will help everyone understand why I'm trying to do it. :)

I have a Postfix server which is dependent on the MySQL database backend. However, to create a mass mailing list that is auto updated, I am trying to write a sql script to grip the data from the the mailbox table and update the alias table sorted by domain.

As i have tried to do so from the applications and it has never worked (postfixadmin) and found out that the previous sysadmin also use the method of copy and paste all the address into the rows to update it.

So in brief, I'm trying to grip from :

Mailbox
username | domain
==============
abc@abc.com abc.com
def@abc.com abc.com

and combine the data into a single row in the alias table:

Alias
goto | domain
==========
abc@abc.com,def@abc.com abc.com

So I truely hope you guys can help me out on this, as I'm at a lost on how to go about it. :(
Reply
  #6  
Old January 8th, 2009, 06:39 AM
Atli's Avatar
Moderator
 
Join Date: Nov 2006
Location: Iceland
Age: 22
Posts: 3,523
Default

You could try using a view. That way you wouldn't have to mutilate the actual data but you could still convert it into whatever format your software needs.

So, using the query you posted in the first post:
Expand|Select|Wrap|Line Numbers
  1. CREATE VIEW `nameListView` AS
  2. SELECT dept,
  3. GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ',')
  4. FROM Table
  5. GROUP BY dept
  6.  
You should be able to treat `nameListView` as an actual table, which would return the data precisely the way your wanted it.
Reply
  #7  
Old January 8th, 2009, 07:49 AM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default

So in this case, if i use the example for my DB:

Expand|Select|Wrap|Line Numbers
  1. CREATE VIEW `nameListView` AS 
  2. SELECT domain, 
  3. GROUP_CONCAT(username ORDER BY username ASC SEPARATOR ',') 
  4. FROM mailbox 
  5. GROUP BY domain
followed by:
Expand|Select|Wrap|Line Numbers
  1. UPDATE alias
  2. SET goto = nameListView
Will that update the goto column in the alias table with the data?

Last edited by Atli; January 8th, 2009 at 06:38 PM. Reason: Added [code] tags
Reply
  #8  
Old January 8th, 2009, 06:47 PM
Atli's Avatar
Moderator
 
Join Date: Nov 2006
Location: Iceland
Age: 22
Posts: 3,523
Default

No. The `nameListView` VIEW acts as a regular table.
Meaning you don't actually have to pull the data into a temporary table. Just querying the VIEW will work.

So you can do:
Expand|Select|Wrap|Line Numbers
  1. SELECT * FROM nameListView
And get the results of the query you created the VIEW with, just like if you had create a table using the data from the query.

So if you need a table named `alias` that should be structured like the return data of a query, create a VIEW named `alias` instead using the query.
Then simply have your software query the `alias` VIEW.

You can even set the "field" names of the *fake* table your VIEW represents by using the AS clause in the query that creates it.
For example, if you have this view:
Expand|Select|Wrap|Line Numbers
  1. CREATE VIEW `TimeAndDate` AS
  2. SELECT
  3.   DATE(NOW()) AS 'CurrentDate',
  4.   TIME(NOW()) AS 'CurrentTime';
  5.  
And you do:
Expand|Select|Wrap|Line Numbers
  1. SELECT * FROM `TimeAndDate`
You would get:
Expand|Select|Wrap|Line Numbers
  1. +-------------+-------------+
  2. | CurrentDate | CurrentTime |
  3. +-------------+-------------+
  4. | 2009-01-08  | 19:51:42    |
  5. +-------------+-------------+
Reply
  #9  
Old January 8th, 2009, 10:20 PM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default

Ah ok. Understand.

If possible, I belief I will need the data to be written to an actual table call 'alias' into an actual row 'goto', as the program (postfix) are relatively hard coded and I don't have the ability to change the codes in it.

I do know that the general practice is to use view for select and display, but in this case, as the table is also used by other parts of the program which require the same format of data (ie. concated longtext), I belief it would be easier if it was done this way, as the alternative will be to rewrite the whole postfix mail program.

So basically, I need to grip the 'username' from the table 'mailbox' grouped by 'domain' and format it into a long string separated by ',' and insert the whole string into a field call 'goto' in the 'alias' table which has the same domain as mailbox's domain.

Hope you guys can help me with this... :)
Reply
  #10  
Old January 8th, 2009, 10:41 PM
Atli's Avatar
Moderator
 
Join Date: Nov 2006
Location: Iceland
Age: 22
Posts: 3,523
Default

OK. Personally, I would use a View to do this. Your software shouldn't be able to tell the difference between an actual table or a VIEW.

But, if you want a table, that is possible to.
You can either create the table using a SELECT statement:
Expand|Select|Wrap|Line Numbers
  1. CREATE TABLE `alias` AS
  2. SELECT
  3.     `domain`,
  4.     GROUP_CONCAT(`username` ORDER BY `username` DESC SEPARATOR ',') AS 'goto'
  5. FROM `mailbox`
  6. GROUP BY `domain`;
Note, the fields in the new table would be named after the rows the SELECT returns, which is why I added the AS keyword to the GROUP_CONCAT

Or create the table first, and insert the data into it:
Expand|Select|Wrap|Line Numbers
  1. INSERT INTO `alias`(`domain`, `goto`)
  2. SELECT
  3.     `domain`,
  4.     GROUP_CONCAT(`username` ORDER BY `username` DESC SEPARATOR ',') AS 'goto'
  5. FROM `mailbox`
  6. GROUP BY `domain`;
  7.  
Note that the names of the rows returned by the SELECT must be the same as the names of the fields, or you will get an incorrect column count error

But...
This VIEW should be able to replace the tables created by both of those methods:
Expand|Select|Wrap|Line Numbers
  1. CREATE VIEW `alias` AS 
  2. SELECT
  3.     `domain`,
  4.     GROUP_CONCAT(`username` ORDER BY `username` DESC SEPARATOR ',') AS 'goto'
  5. FROM `mailbox`
  6. GROUP BY `domain`;
I recommend you try that first, just to see if it will work :)
Reply
  #11  
Old January 9th, 2009, 12:21 AM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default

Thanks!

Ok. I have tried the SQL command for insert and it sort of work for me. But some fields already have values in it and probably a UPDATE will work better for me in those cases I think.

So if i were to use the UPDATE command, would it look something like this?
Expand|Select|Wrap|Line Numbers
  1. UPDATE alias
  2. SET goto = (
  3. SELECT 
  4.     `domain`, 
  5.     GROUP_CONCAT(`username` ORDER BY `username` DESC SEPARATOR ',') AS 'goto' 
  6. FROM `mailbox` 
  7. GROUP BY `domain`
  8. )
  9. WHERE alias.domain = mailbox.domain;

Last edited by Atli; January 9th, 2009 at 12:31 AM. Reason: Added [code] tags
Reply
  #12  
Old January 9th, 2009, 12:48 AM
Atli's Avatar
Moderator
 
Join Date: Nov 2006
Location: Iceland
Age: 22
Posts: 3,523
Default

The subquery would have to be changed.
A sub-query like that can only return 1 column from 1 row, so you wold have to limit it to that.

A correlated subquery with the GROUP_CONCAT function should do the trick.

For example:
Expand|Select|Wrap|Line Numbers
  1. UPDATE newTable AS nt
  2. SET value = (
  3.   SELECT GROUP_CONCAT(value SEPARATOR ',')
  4.   FROM oldTable AS ot
  5.   WHERE nt.group = ot.group
  6.   GROUP BY ot.group
  7. );
Note, however, that this would only update groups that already exists in the "newTable". Those that do not would NOT be inserted.

P.S.
Please use [code] tags when posting your code examples.
Thanks.
Reply
  #13  
Old January 13th, 2009, 06:39 AM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default

Hi,

I've tested the script, need a bit of twigging but it is working as intended... but... I hit another snag... when updating the alias table, the group_concat cut off after a certain number of char... which to my dismay, not what i wanted...

So on a related question, is there anyway to increase the group_concat cache or another way to grip and update the field? (I think i got around 10k emails there)

:)
Reply
  #14  
Old January 13th, 2009, 06:52 AM
Atli's Avatar
Moderator
 
Join Date: Nov 2006
Location: Iceland
Age: 22
Posts: 3,523
Default

What is the type of the target field?
Could it be that the field is to small to contain all the data?
A VarChar(255) would cut of anything beyond the 255 chars.
And a Text field would only hold up to 65.536 bytes of data.
Reply
  #15  
Old January 14th, 2009, 12:18 AM
Newbie
 
Join Date: Jan 2009
Posts: 8
Default

Hi,

Currently using LongText. I think that should hold about 4GB of data right?
Reply
  #16  
Old January 14th, 2009, 02:55 AM
Atli's Avatar
Moderator
 
Join Date: Nov 2006
Location: Iceland
Age: 22
Posts: 3,523
Default

Ok. Seems the result of the GROUP_CONCAT is limited by a couple of variables.

From the manual
Quote:
Originally Posted by dev.mysql.com
The result is truncated to the maximum length that is given by the group_concat_max_len system variable, which has a default value of 1024. The value can be set higher, although the effective maximum length of the return value is constrained by the value of max_allowed_packet.
So, to increase this you would have to add/edit these to your my.cnf/my.ini file:
Expand|Select|Wrap|Line Numbers
  1. # Would set the max length to 2MB
  2. group_concat_max_len = 2097152
  3. max_allowed_packet = 2097152
Reply
Reply

Bookmarks

Tags
combine, insert, multiple, row, select

Thread Tools Search this Thread
Search this Thread:

Advanced Search


Popular Articles

What is Bytes?

We are a network of experts and professionals in IT and software development that help one another with answers to tough questions and share insights. Get the best answers to your questions from over 220,662 network members.