Concatenate values for concatenated IDs - mysql

Database: MySQL
I have two tables, one for user's assigned roles and one that contains the role information. My problem is that the assigned roles are stored in a single field, separated by commas. I need to build a report that lists the roles by name, not the id, but still be in a single field separated by columns.
I'm thinking GROUP_CONCAT might be the solution but I've seen it used to create a concatenated list, not use one that already exists.
Table 1:USERS
ID | FNAME | LNAME | ROLE_IDS
------------------------------------------
1 | Bob | Jones | 445,44,45,449,459
2 | Mark | Doe | 426,459,445
3 | Jeff | Apple | 444,45
Table 2: ROLES
ID | ROLE_NAME
------------------------------------
4 | Basic
13 | Reporting
16 | Advanced
44 | Admin
45 | Super User
426 | Accounting
444 | User
445 | Receivables
449 | Processing
459 | Research
Expected Query Results:
ID | FNAME | LNAME | ROLES
-------------------------------------------
1 | Bob | Jones | Receivables, Admin, Super User, Processing, Research
2 | Mark | Doe | Accounting, Research, Receivables
3 | Jeff | Apple | User, Super User

For getting referencing role names, you can use GROUP_CONCAT like this :
SELECT us.ID,us.FNAME,us.LNAME,
GROUP_CONCAT(ro.ROLE_NAME) ROLES_NAME
FROM USERS us
INNER JOIN ROLES ro
ON FIND_IN_SET(ro.ID, us.ROLE_IDS) > 0
GROUP BY us.ID
I've tested it in SQLFIDDLE and working fine.

Related

Return Query Results to Textbox on Userform

let me start by giving you some table structures. I have a 3 relevant tables:
OutreachEventsLog: stores information like Date/Time/Location for community events
| EventID | Date | Location |...
| 1 |2/19/2019 | Earth |
| 2 |2/18/2019 | Earth |
Staff: A list of staff employed in program
|StaffID | First Name | Last Name | Full Name |
| 1 | John | Smith | John Smith |
| 2 | Mary | Sue | Mary Sue |
| 3 | Betty | Jane | Betty Jane |
OutreachEventtoStaff: a junction table that relates staff members to the outreach events they attended.
| Event Id | Staff ID |
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
I've created a couple of forms to capture event information. The two relevant ones are called OutreachSummary and EventtoStaff. The EventtoStaff form is launched by clicking a button entitled "Add Staff" embedded in the OutreachSummary form. This process is working properly and updating all associated tables correctly.
Here's what I want to do: I want to embed a text box on the OutreachSummary form that lists all the staff members currently assigned to that event, with entries separated by a comma. Example: "John Smith, Mary Sue, Betty Jane..." where Event ID = 2
I've managed to write some code that returns a comma delimited list like the one above to a query. I then tried grab a specific row from this query based on EventID and assign it to the text box with no luck. I keep getting a ?Name error. Is there an easy way to do this? Am I even on the right track?
Thanks for the input!

How to properly join two tables to use alternative ORDER BY

Two tables...
people (personid, name, mainordering)
entries (userid, personid, altordering)
"personid" is the common field. My app displays a draggable list users can move around. When done, they click to "lock" in their order.
Table : people
+----------+---------+--------------+
| personid | name | mainordering |
+----------+---------+--------------+
| 1 | Bob | 2 |
| 2 | Charlie | 4 |
| 3 | Jim | 1 |
| 4 | Doug | 3 |
+----------+---------+--------------+
So using mainordering, it would display:
Jim
Bob
Doug
Charlie
entries table might have (for user 16):
+--------+----------+-------------+
| userid | personid | altordering |
+--------+----------+-------------+
| 16 | 1 | 3 |
| 16 | 2 | 1 |
| 16 | 3 | 2 |
| 16 | 4 | 4 |
+--------+----------+-------------+
So if user 16 has already submitted his entry BUT NOT LOCKED IT IN, I want to display his list using altordering. i.e.
Charlie
Jim
Bob
Doug
I'm struggling with the proper join to use. Here is what I tried and isn't working (it's simply ordering by mainordering still)...
$sql = "SELECT * from entries
WHERE userid=".$_SESSION['userid']."
LEFT JOIN people ON entries.personid = people.personid
ORDER BY altordering";
Any thoughts would be much appreciated. Thank you...
Are you sure you don't get an error when using WHERE before JOIN?
It should work like this:
SELECT people.*
FROM people
JOIN entries ON entries.personid = people.personid
WHERE entries.userid={$_SESSION['userid']}
ORDER BY entries.altordering
I assume entries.personid will always have a matching person in people, so you should use an INNER JOIN. You would use FROM entries LEFT JOIN people if you wanted to retrieve altordering even for non-existing people.

How to condense a column like this?

I've tried finding something like this, but to no avail...
This is about a system of tables for a customer management system. In particular, I need to create a note history for each customer.
So, I have a table 'customers' with the columns customers.customer_ID, customers.lastname, customers.firstname, customers.postal_code, customers.city and customers.street;
and another table 'notes' with the columns notes.note_ID, notes.customer_ID, notes.subject, notes.description and notes.entered_on
Now I need to create a third table search which condenses much of the information above. It has the tables search.contact_ID, search.name, search.address and search.history. This is supposed to look like this:
contacts:
contact_ID | lastname | firstname | ...
------------+-----------+-----------+-----
1 | Doe | John | ...
2 | Dane | Jane | ...
note:
note_ID | contact_ID | subject | description | entered_on
--------+---------------+-----------------------+-----------------------+----------------
1 | 1 | call received | John Doe called us to | 2014-05-03
| | | ask for an offer |
2 | 1 | offer made | We called John Doe to | 2014-06-03
| | | submit our offer |
3 | 2 | advertisement call | We called Jane Dane to| 2014-06-03
| | | inform her of our |
| | | latest offer |
4 | 1 | offer accepted | John Doe called to | 2014-08-03
| | | accept our offer |
search:
contact_ID | name | address | history
------------+---------------+---------------------------------+-------------------
1 | Doe, John | 55 Main Street, 12345 Oldtown | 'On 2014-08-03 offer accepted: John Doe accepted our offer.
| | | On 2014-06-03 offer made: We called John Doe to submit our offer.
| | | On 2014-05-03 call received: John Doe called us to ask for an offer.'
2 | Dane, Jane | 111 Wall Street, 67890 Newtown | 'On 2014-06-03 advertisement call: We called Jane Dane to submit our offer.'
While I can deal with much of the rest, I have no idea how to generate the history information. My idea was as follows
WHILE
customers.customer_ID = note.customer_ID
AND
note.entered_on = GREATEST(note.entered_on)
DO
SET customers.note_history = CONCAT_WS(' | ', CONCAT_WS(': ',note.subject,note.description), customers.note_history);
But that one isn't necessarily chronological. Also how do I transform that into a statement compatible with the SELECT INTO used for the creation of the rest of the table?
Sounds like a case for a Group-By, along with GROUP_CONCAT
CREATE TABLE search (PRIMARY KEY(contact_ID))
SELECT contact_ID, CONCAT(lastname,', ',firstname) AS name, address,
GROUP_CONCAT(CONCAT('On ',entered_on,' ',subject,': ',description)
ORDER BY note_ID SEPARATOR "\n") AS history
FROM contacts LEFT JOIN note USING (contact_ID)
GROUP BY contact_ID
If dont want to use CREATE TABLE .. SELECT ... , can first just create (or truncate!) the table, and then use INSERT INTO ... SELECT ... instead.

How to store multiple values in single column where use less memory?

I have a table of users where 1 column stores user's "roles".
We can assign multiple roles to particular user.
Then I want to store role IDs in the "roles" column.
But how can I store multiple values into a single column to save memory in a way that is easy to use? For example, storing using a comma-delimited field is not easy and uses memory.
Any ideas?
If a user can have multiple roles, it is probably better to have a user_role table that stores this information. It is normalised, and will be much easier to query.
A table like:
user_id | role
--------+-----------------
1 | Admin
2 | User
2 | Admin
3 | User
3 | Author
Will allow you to query for all users with a particular role, such as SELECT user_id, user.name FROM user_role JOIN user WHERE role='Admin' rather than having to use string parsing to get details out of a column.
Amongst other things this will be faster, as you can index the columns properly and will take marginally more space than any solution that puts multiple values into a single column - which is antithetical to what relational databases are designed for.
The reason this shouldn't be stored is that it is inefficient, for the reason DCoder states on the comment to this answer. To check if a user has a role, every row of the user table will need to be scanned, and then the "roles" column will have to be scanned using string matching - regardless of how this action is exposed, the RMDBS will need to perform string operations to parse the content. These are very expensive operations, and not at all good database design.
If you need to have a single column, I would strongly suggest that you no longer have a technical problem, but a people management one. Adding additional tables to an existing database that is under development, should not be difficult. If this isn't something you are authorised to do, explain to why the extra table is needed to the right person - because munging multiple values into a single column is a bad, bad idea.
You can also use bitwise logic with MySQL. role_id must be in BASE 2 (0, 1, 2, 4, 8, 16, 32...)
role_id | label
--------+-----------------
1 | Admin
2 | User
4 | Author
user_id | name | role
--------+-----------------
1 | John | 1
2 | Steve | 3
3 | Jack | 6
Bitwise logic allows you to select all user roles
SELECT * FROM users WHERE role & 1
-- returns all Admin users
SELECT * FROM users WHERE role & 5
-- returns all users who are admin or Author because 5 = 1 + 4
SELECT * FROM users WHERE role & 6
-- returns all users who are User or Author because 6 = 2 + 4
From your question what I got,
Suppose, you have to table. one is "meal" table and another one is "combo_meal" table. Now I think you want to store multiple meal_id inside one combo_meal_id without separating coma[,]. And you said that it'll make your DB to more standard.
If I not getting wrong from your question then please read carefully my suggestion bellow. It may be help you.
First think is your concept is right. Definitely it'll give you more standard DB.
For this you have to create one more table [ example table: combo_meal_relation ] for referencing those two table data. May be one visible example will clear it.
meal table
+------+--------+-----------+---------+
| id | name | serving | price |
+------+--------+-----------+---------+
| 1 | soup1 | 2 person | 12.50 |
+------+--------+-----------+---------+
| 2 | soup2 | 2 person | 15.50 |
+------+--------+-----------+---------+
| 3 | soup3 | 2 person | 23.00 |
+------+--------+-----------+---------+
| 4 | drink1 | 2 person | 4.50 |
+------+--------+-----------+---------+
| 5 | drink2 | 2 person | 3.50 |
+------+--------+-----------+---------+
| 6 | drink3 | 2 person | 5.50 |
+------+--------+-----------+---------+
| 7 | frui1 | 2 person | 3.00 |
+------+--------+-----------+---------+
| 8 | fruit2 | 2 person | 3.50 |
+------+--------+-----------+---------+
| 9 | fruit3 | 2 person | 4.50 |
+------+--------+-----------+---------+
combo_meal table
+------+--------------+-----------+
| id | combo_name | serving |
+------+--------------+-----------+
| 1 | combo1 | 2 person |
+------+--------------+-----------+
| 2 | combo2 | 2 person |
+------+--------------+-----------+
| 4 | combo3 | 2 person |
+------+--------------+-----------+
combo_meal_relation
+------+--------------+-----------+
| id | combo_meal_id| meal_id |
+------+--------------+-----------+
| 1 | 1 | 1 |
+------+--------------+-----------+
| 2 | 1 | 2 |
+------+--------------+-----------+
| 3 | 1 | 3 |
+------+--------------+-----------+
| 4 | 2 | 4 |
+------+--------------+-----------+
| 5 | 2 | 2 |
+------+--------------+-----------+
| 6 | 2 | 7 |
+------+--------------+-----------+
When you search inside table then it'll generate faster result.
search query:
SELECT m.*
FROM combo_meal cm
JOIN meal m
ON m.id = cm.meal_id
WHERE cm.combo_id = 1
Hopefully you understand :)
You could do something like this
INSERT INTO table (id, roles) VALUES ('', '2,3,4');
Then to find it use FIND_IN_SET
As you might already know, storing multiple values in a cell goes against 1NF form. If youre fine with that, using a json column type is a great way and has good methods to query properly.
SELECT * FROM table_name
WHERE JSON_CONTAINS(column_name, '"value 2"', '$')
Will return any entry with json data like
[
"value",
"value 2",
"value 3"
]
Youre using json, so remember, youre query performance will go down the drain.

Data Entry Tracking (Database Design)

I have developed a website (PHP) that allow staffs to add records on to our system.
Staffs will be adding thousands of records into our database.
I need a way to keep track of what record have been done and the process/status of record.
Here a number of Teams I could think of:
Data Entry Team
Proof Reading Team
Admin Team
When staff (Data Entry Team) completed a record - he/she will then click on the Complete button. Then somehow it should asssign to 'Proof Reading Team' automatically.
A record need to be checked twice from a Proof Reading Team. If StaffB finish proof reading then another member from Proof Reading Team need to check it again.
When Proof reading is done, Admin Team will then assign "Record Completed"
In a few months later record might need to be updated (spelling mistake, price change, etc) - Admin might to assign record to Data entry team.
Is this good data entry management solution? How do I put this into Database Design perspective?
Here what I tried:
mysql> select * from records;
+----+------------+----------------------+
| id | name | address |
+----+------------+----------------------+
| 1 | Bill Gates | Text 1 Text Text 1 |
| 2 | Jobs Steve | Text 2 Text 2 Text 2 |
+----+------------+----------------------+
mysql> select * from staffs;
+----+-----------+-----------+---------------+
| id | username | password | group |
+----+-----------+-----------+---------------+
| 1 | admin1 | admin1 | admin |
| 2 | DEntryA | DEntryA | data_entry |
| 3 | DEntryB | DEntryB | data_entry |
| 4 | PReadingA | PReadingA | proof_reading |
| 5 | PReadingB | PReadingB | proof_reading |
+----+-----------+-----------+---------------+
mysql> select * from data_entry;
+----+------------+-----------+------------------------+
| id | records_id | staffs_id | record_status |
+----+------------+-----------+------------------------+
| 1 | 2 | 3 | data_entry_processiing |
| 2 | 2 | 3 | data_entry_completed |
| 3 | 2 | 4 | proof_read_processing |
| 4 | 2 | 4 | proof_read_completed |
| 5 | 2 | 5 | proof_read_processing |
| 6 | 2 | 5 | proof_read_completed |
+----+------------+-----------+------------------------+
Is there alternative better solution of database design?
i think design it's well done. but may be you want to separate group into groups table, and record_status into status table. If you're storing a lot of records you would store a lot of useless information, at least create an enum type for record_status field and group field
table: groups
id - name 1 - admin 2 - data_entry 3 - proof_reading
...
table: status
id - name 1 - data_entry_processing ...
and if you want the users to be in different groups at a time, you could create users_group table
table: user_groups
group_id - user_id 1 - 1 2 - 1 1 - 4 3 -
4 4 - 4 ....
Hope this helps