How do you merge rows from 2 SQL tables without duplicating rows? - sql

I guess this query is a little basic and I should know more about SQL but haven't done much with joins yet which I guess is the solution here.
What I have is a table of people and a table of job roles they hold. A person can have multiple jobs and I wish to have one set of results with a row per person containing their details and their job roles.
Two example tables (people and job_roles) are below so you can understand the question easier.
People
id | name | email_address | phone_number
1 | paul | paul#example.com | 123456
2 | bob | bob#example.com | 567891
3 | bart | bart#example.com | 987561
job_roles
id | person_id | job_title | department
1 | 1 | secretary | hr
2 | 1 | assistant | media
3 | 2 | manager | IT
4 | 3 | finance clerk | finance
4 | 3 | manager | IT
so that I can output each person and their roles like such
Name: paul
Email Address: paul#example.com
Phone: 123456
Job Roles:
Secretary for HR department
Assistant for media department
_______
Name: bob
Email address: bob#example.com
Phone: 567891
Job roles:
Manager for IT department
So how would I get each persons information (from the people table) along with their job details (from the job_roles table) to output like the example above. I guess it would be some kind of way of merging their jobs and their relevant departments into a jobs column that can be split up for output, but maybe there is a better way and what would the sql look like?
Thanks
Paul
PS it would be a mySQL database if that makes any difference

It looks like a straight-forward join:
SELECT p.*, j.*
FROM People AS p INNER JOIN Roles AS r ON p.id = r.person_id
ORDER BY p.name;
The remainder of the work is formatting; that's best done by a report package.
Thanks for the quick response, that seems a good start but you get multiple rows per person like (you have to imagine this is a table as you don't seem to be able to format in comments):
id | Name | email_address | phone_number | job_role | department
1 | paul | paul#example.com | 123456 | secretary | HR
1 | paul | paul#example.com | 123456 | assistant | media
2 | bob | bob#example.com | 567891 | manager | IT
I would like one row per person ideally with all their job roles in it if that's possible?
It depends on your DBMS, but most available ones do not support RVAs - relation-valued attributes. What you'd like is to have the job role and department part of the result like a table associated with the user:
+----+------+------------------+--------------+------------------------+
| id | Name | email_address | phone_number | dept_role |
+----+------+------------------+--------------+------------------------+
| | | | | +--------------------+ |
| | | | | | job_role | dept | |
| 1 | paul | paul#example.com | 123456 | | secretary | HR | |
| | | | | | assistant | media | |
| | | | | +--------------------+ |
+----+------+------------------+--------------+------------------------+
| | | | | +--------------------+ |
| | | | | | job_role | dept | |
| 2 | bob | bob#example.com | 567891 | | manager | IT | |
| | | | | +--------------------+ |
+----+------+------------------+--------------+------------------------+
This accurately represents the information you want, but is not usually an option.
So, what happens next depends on your report generation tool. Using the one I'm most familiar with, (Informix ACE, part of Informix SQL, available from IBM for use with the Informix DBMSs), you would simply ensure that the data is sorted and then print the name, email address and phone number in the 'BEFORE GROUP OF id' section of the report, and in the 'ON EVERY ROW' section you would process (print) just the role and department information.
It is often a good idea to separate the report formatting from the data retrieval operations; this is an example of where it is necessary unless your DBMS has unusual features to help with the formatting of selected data.
Oh dear that sounds very complicated and not something I could run easily on a mySQL database in a PHP page?
The RVA stuff - you're right, that is not for MySQL and PHP.
On the other hand, there are millions of reports (meaning results from queries that are formatted for presentation to a user) that do roughly this. The technical term for them is 'Control-Break Report', but the basic idea is not hard.
You keep a record of the 'id' number you last processed - you can initialize that to -1 or 0.
When the current record has a different id number from the previous number, then you have a new user and you need to start a new set of output lines for the new user and print the name, email address and phone number (and change the last processed id number). When the current record has the same id number, then all you do is process the job role and department information (not the name, email address and phone number). The 'break' occurs when the id number changes. With a single level of control-break, it is not hard; if you have 4 or 5 levels, you have to do more work, and that's why there are reporting packages to handle it.
So, it is not hard - it just requires a little care.

RE:
I was hoping SQL could do something
clever and join the rows together
nicely so I had essentially a jobs
column with that persons jobs in it.
You can get fairly close with
SELECT p.id, p.name, p.email_address, p.phone_number,
group_concat(concat(job_title, ' for ', department, ' department') SEPARATOR '\n') AS JobRoles
FROM People AS p
INNER JOIN job_roles AS r ON p.id = r.person_id
GROUP BY p.id, p.name, p.email_address, p.phone_number
ORDER BY p.name;

Doing it the way you're wanting would mean the result set arrays could have infinite columns, which would be very messy. for example, you could left join the jobs table 10 times and get job1, job2, .. job10.
I would do a single join, then use PHP to check if the name ID is the same from 1 row to the next.

One way might be to left outer join the tables and then load them up into an array using
$people_array =array();
while($row1=mysql_fetch_assoc($extract1)){
$people_array[] = $row1;
}
and then loop through using
for ($x=0;$x<=sizeof($people_array;)
{
echo $people_array[$x][id];
echo $people_array[$x][name];
for($y=0;$y<=$number_of_roles;$y++)
{
echo $people_array[$x][email_address];
echo $people_array[$x][phone_number];
$x++;
}
}
You might have to play with the query a bit and the loops but it should do generally what you want.For it to work as above every person would have to have the same number of roles, but you may be able to fill in the blanks in your table

Related

Trying to get results from 3 different tables using sql or linq query

I'm very new to SQL/LINQ and could really use your help.
I have 3 tables:
Invitation table
| InviteId | Name | Email | SalesAgentId | |
|----------|----------|--------------|--------------|---|
| 9 | John Doe | john#doe.com | 1 | |
SalesAgent table
| SalesAgentId | UserId | Active |
|--------------|--------|--------------|
| 1 | 2 | true |
User table
| UserId | Name | Email |
|--------|--------------------------------------|-----------------------------------|
| 2 | Sales Guy | sales#agent.com |
Trying to generate a list of a given SalesAgent that should contain that SalesAgent's info along with info of the user that has been invited by the sales agent (from the Invitation table)
Goal:
Trying to Get a list of users (both from the Invitation table and User table that are tied to the SalesAgent Id (either the user is the sales agent himself, or the user was invited by the sales agent).
So in this scenario, the goal is to get the result table (result) as:
ResultId: If there is a record in User table, then use the SalesAgentId, but if the record is coming from Invitation table, then use the InviteId for this column.
Name/Email: Same logic; if record is in User table, use the Name/Email from User table, if not, then use the info from Invitation table.
InvitedUser: If record is in User table, then this should be false, but if its coming from Invitation table, then this should be true.
| ResultId | Name | Email | InvitedUser |
|----------|-----------|-----------------|-------------|
| 1 | Sales Guy | sales#agent.com | false |
| 9 | John Doe | john#doe.com | true |
I'm not sure what kind of join would help me in this scenario, more or less I need to combine results from 2 queries (I think). Ultimate goal is to get a linq query version of this select, but I think LINQ/EfCore doesn't support Concat when using different data stores.
I'd really appreciate any type of help, or a pointer in the right direction; many thanks in advance.
The output you show looks like result of a UNION query so consider:
SELECT Invitation.InviteID AS ResultID, Invitation.Name, Invitation.Email, True AS InvitedUser
FROM Invitation
UNION
SELECT SalesAgent.SalesAgentID, User.Name, User.Email, False AS InvitedUser
FROM [User] INNER JOIN SalesAgent ON User.UserID = SalesAgent.UserID
WHERE (((User.Name) Not In (SELECT [Name] FROM [Invitation])));

“Is there an Access SQL code/query for concatenating first letter plus a unique ID number and insert into a new column? [duplicate]

This question already has answers here:
Customized Auto-Number IDs for tables?
(2 answers)
Closed 3 years ago.
First of all, I am quite new to SQL and Microsoft Access.
I am setting a database in Access. My database collects information from four different departments. I store my data through forms. My main table (Business) stores information (department) using a Combo Box saving a number instead of text.
I want to have a column (similar to CODE ID already available in the table above) which shows the initial letter from a field (name Department) + a number.
Ie. In table "Business", I want to display a Code ID which contains the initials of column Department plus a number code (department order number ascending). I want to have this every time i add information.
+===============+=================+=========+==+
| DEPARTMENT | PARTNER | CODE ID | |
+===============+=================+=========+==+
| Data_Analysis | John Doe | D001 | |
+---------------+-----------------+---------+--+
| Marketing | Jane Doe | M001 | |
+---------------+-----------------+---------+--+
| Finance | Alex Mustermann | F001 | |
+---------------+-----------------+---------+--+
| Operations | Juan Perez | O001 | |
+---------------+-----------------+---------+--+
| Finance | Barack Trump | F002 | |
+---------------+-----------------+---------+--+
| Finance | Mark Merkel | F003 | |
+---------------+-----------------+---------+--+
| Marketing | Peggy Hilton | M002 | |
+---------------+-----------------+---------+--+
| Operations | Max Mustermann | O002 | |
+---------------+-----------------+---------+--+
| Operations | | OXXX | |
+---------------+-----------------+---------+--+
The values in column CODE ID are those I would like to have display every time I add a new row (new department order). I need this type of code for tracking my number of orders in each department and use it as a unique code for any inquires with partners. I dont want to have it as the primary key id.
Thanks in advance!
If you rethink the schema slightly it becomes trivial; instead of having the column with the ID and code combined, just keep a running count when inserting:
INSERT INTO business(department, name, code) SELECT Forms!Department, Forms!Name, COUNT(*)+1 FROM business WHERE name=Forms!Name
Then when you pull the information out:
SELECT department, name, LEFT(1, department) & code

Sql data from row to column with reference to another column

Parent table
+====+===========+
| id | firstname |
+====+===========+
| 1 | abc |
+----+-----------+
| 2 | bcd |
+----+-----------+
| 3 | cde |
+----+-----------+
StudentRelationship table
+==========+==========+===========+
| relation | parentid | studentid |
+==========+==========+===========+
| father | 1 | s0001 |
+----------+----------+-----------+
| mother | 2 | s0001 |
+----------+----------+-----------+
| father | 3 | s0002 |
+----------+----------+-----------+
STUDENT table
+=======+===========+==========+=========+======+
| id | firstname | lastname | address | sex |
+=======+===========+==========+=========+======+
| s0001 | shdj | khb | jxx | male |
+-------+-----------+----------+---------+------+
It would be great if you could help me create a query which will return studentid ,name,father name,mother name,sex,address.
Based on what you've posted, then updated in your comments, I think this should work for you. I am sure someone with more advanced SQL skills can post a more elegant way to do this. But this is what I came up with:
SELECT DISTINCT cte.studentid
,studentFirstName
,studentLastName
,father.fatherFirstName
,mother.motherFirstName
,sex
,address
FROM cte
LEFT JOIN father ON cte.studentid = father.studentid
LEFT JOIN mother ON cte.studentid = mother.studentid
The following is an example where a student (Jeff Jones) has two fathers (let's say one of them is the step-father):
A few recommendations here:
Take a course on SQL syntax fundamentals (any type MySQL, T-SQL, etc..)
Read about FROM and JOIN
When posting your question here, the table examples should have better test data. "asdfkj", "shdsf", "Asdjkfdjkf" are horribly hard to
use to test code against because there is no context of what you are
looking at. I realize you are just posting an example, and the context
of the rows is partly insignificant, but it just makes for easier
question answering, and doesn't scare off people who would want to
answer your question.
Here is an DEMO you can play with, that has reasonable data in the fields you've mentioned.

Using a table to lookup multiple IDs on one row

I have two tables I am using at work to help me gain experience in writing SQL queries. One table contains a list of Applications and has three columns -
Application_Name, Application_Contact_ID and Business_Contact_ID. I then have a separate table called Contacts with two columns - Contact_ID and Contact_Name. I am trying to write a query that will list the Application_Name and Contact_Name for both the Applications_Contact_ID and Business_Contact_ID columns instead of the ID number itself.
I understand I need to JOIN the two tables but I haven't quite figured out how to formulate the correct statement. Help Please!
APPLICATIONS TABLE:
+------------------+------------------------+---------------------+
| Application_Name | Application_Contact_ID | Business_Contact_ID |
+------------------+------------------------+---------------------+
| Adobe | 23 | 23 |
| Word | 52 | 14 |
| NotePad++ | 44 | 989 |
+------------------+------------------------+---------------------+
CONTACTS TABLE:
+------------+--------------+
| Contact_ID | Contact_Name |
+------------+--------------+
| 23 | Tim |
| 52 | John |
| 14 | Jen |
| 44 | Carl |
| 989 | Sam |
+------------+--------------+
What I am trying to get is:
+------------------+--------------------------+-----------------------+
| Application_Name | Application_Contact_Name | Business_Contact_Name |
+------------------+--------------------------+-----------------------+
| Adobe | Tim | Tim |
| Word | John | Jen |
| NotePad++ | Carl | Sam |
+------------------+--------------------------+-----------------------+
I've tried the below but it is only returning the name for one of the columns:
SELECT Application_Name, Application_Contact_ID, Business_Contact_ID, Contact_Name
FROM Applications
JOIN Contact ON Contact_ID = Application_Contact_ID
This is a pretty critical and 101 part of SQL. Consider reading this other answer on a different question, which explains the joins in more depth. The trick to your query, is that you have to join the CONTACTS table twice, which is a bit hard to visualize, because you have to go there for both the application_contact_id and business_contact_id.
There are many flavors of joins (INNER, LEFT, RIGHT, etc.), which you'll want to familiarize yourself with for the future reference. Consider reading this article at the very least: https://www.techonthenet.com/sql_server/joins.php.
SELECT t1.application_name Application_Name,
t2.contact_name Application_Contact_name,
t3.contact_name Business_Contact_name
FROM applications t1
INNER JOIN contacts ON t2 t1.Application_Contact_ID = t2.contact_id -- join contacts for appName
INNER JOIN contacts ON t3 t1.business_Contact_ID = t3.contact_id; -- join contacts for busName

Retrieve comma delimited data from a field

I've created a form in PHP that collects basic information. I have a list box that allows multiple items selected (i.e. Housing, rent, food, water). If multiple items are selected they are stored in a field called Needs separated by a comma.
I have created a report ordered by the persons needs. The people who only have one need are sorted correctly, but the people who have multiple are sorted exactly as the string passed to the database (i.e. housing, rent, food, water) --> which is not what I want.
Is there a way to separate the multiple values in this field using SQL to count each need instance/occurrence as 1 so that there are no comma delimitations shown in the results?
Your database is not in the first normal form. A non-normalized database will be very problematic to use and to query, as you are actually experiencing.
In general, you should be using at least the following structure. It can still be normalized further, but I hope this gets you going in the right direction:
CREATE TABLE users (
user_id int,
name varchar(100)
);
CREATE TABLE users_needs (
need varchar(100),
user_id int
);
Then you should store the data as follows:
-- TABLE: users
+---------+-------+
| user_id | name |
+---------+-------+
| 1 | joe |
| 2 | peter |
| 3 | steve |
| 4 | clint |
+---------+-------+
-- TABLE: users_needs
+---------+----------+
| need | user_id |
+---------+----------+
| housing | 1 |
| water | 1 |
| food | 1 |
| housing | 2 |
| rent | 2 |
| water | 2 |
| housing | 3 |
+---------+----------+
Note how the users_needs table is defining the relationship between one user and one or many needs (or none at all, as for user number 4.)
To normalise your database further, you should also use another table called needs, and as follows:
-- TABLE: needs
+---------+---------+
| need_id | name |
+---------+---------+
| 1 | housing |
| 2 | water |
| 3 | food |
| 4 | rent |
+---------+---------+
Then the users_needs table should just refer to a candidate key of the needs table instead of repeating the text.
-- TABLE: users_needs (instead of the previous one)
+---------+----------+
| need_id | user_id |
+---------+----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 1 | 2 |
| 4 | 2 |
| 2 | 2 |
| 1 | 3 |
+---------+----------+
You may also be interested in checking out the following Wikipedia article for further reading about repeating values inside columns:
Wikipedia: First normal form - Repeating groups within columns
UPDATE:
To fully answer your question, if you follow the above guidelines, sorting, counting and aggregating the data should then become straight-forward.
To sort the result-set by needs, you would be able to do the following:
SELECT users.name, needs.name
FROM users
INNER JOIN needs ON (needs.user_id = users.user_id)
ORDER BY needs.name;
You would also be able to count how many needs each user has selected, for example:
SELECT users.name, COUNT(needs.need) as number_of_needs
FROM users
LEFT JOIN needs ON (needs.user_id = users.user_id)
GROUP BY users.user_id, users.name
ORDER BY number_of_needs;
I'm a little confused by the goal. Is this a UI problem or are you just having trouble determining who has multiple needs?
The number of needs is the difference:
Len([Needs]) - Len(Replace([Needs],',','')) + 1
Can you provide more information about the Sort you're trying to accomplish?
UPDATE:
I think these Oracle-based posts may have what you're looking for: post and post. The only difference is that you would probably be better off using the method I list above to find the number of comma-delimited pieces rather than doing the translate(...) that the author suggests. Hope this helps - it's Oracle-based, but I don't see .