Getting data from multiple tables into single row while concatenating some values - mysql

I'm trying to retrieve data from tables and combine multiple rows into a single column, without repeating any information.
I have the following tables: profile, qualification, projects.
Profile
pro_id surname firstname
------ ------- ----------
1 John James
2 King Fred
3 Luxury-Yachts Raymond
Qualification
pro_id Degree School Year
------ ------ ------ -----
1 MBA Wharton university 2002
1 LLB Yale University 2001
2 BSc Covington University 1998
2 BEd Kellog University 1995
Projects
pro_id Title Year
------ ------ ------
1 Social Networking 2003
1 Excavation of aquatic debris 2007
2 Design of solar radios 1992
2 Development of expert systems 2011
I want to retrieve the all of the information for each person, with each person appearing only once in the result. The info on qualifications and projects should each be in their own column (one column for qualifications, another for projects), separated by commas. For example, the results for the above sample data should be:
1 John James MBA Wharton university 2002, LLB Yale University 2001 Social Networking 2003, Excavation of aquatic debris 2007, Design of Solar panels 2008
2 King Fred BSc Covington University 1998, BEd Kellog University 1995, Msc MIT 2011 Design of solar radios 1992, Development of expert systems 2011
3 Raymond Luxury-Yachts
Currently, I have the query:
SELECT pro_id,
surname,
firstname,
group_concat(degree,school,year) AS qual,
concat(Title,year) AS work
FROM profile,
LEFT JOIN qualification
ON qualification.pro_id = profile.pro_id
JOIN projects
ON projects.pro_id = profile.pro_id
GROUP BY pro_id
For the sample data, this query results in:
1 John James MBA Wharton university 2002, Social Networking 2003
1 John James LLB Yale University 2001, Excavation of aquatic debris 2007
1 John James MBA Wharton university 2002, Social Networking 2003, Excavation of aquatic debris 2007
etc
Note: Raymond Luxury-Yachts isn't present in the current result.
I don't want duplicate result records. Also if the surname does not have any entry in the qualification and projects table, I want the query to return the name and display an empty field in the qualification and projects table instead of omitting them altogether.

Replace LEFT JOIN with JOIN
Select pro_id, surname, firstname, group_concat(degree,school,year) as qual,concat(Title,year) as work
from profile
join qualification on qualification.pro_id = profile.pro_id
join projects on projects.pro_id = profile.pro_id group by pro_id
What is the difference between "INNER JOIN" and "OUTER JOIN"?

Using Join will fix the issue with displaying values even if there are no records in the projects table.
For the first question, you can try making a stored function and calling it from the select statement. This function will take pro_id as parameter, create the concatenated string and return it. That's the only solution for MySQL that I can think of at the moment.

I think you are close on your thoughts of group_concat. However, with possible No values (thus leaving nulls), can cause problems. I would have each secondary table pre-concatinated by person's ID and join to THAT result. Eliminates the problem of nulls
SELECT
p.pro_id,
p.surname,
p.firstname,
PreQConcat.UserQual,
PrePJConcat.UserWork
FROM
profile p
LEFT JOIN
( select q.pro_id,
group_concat( q.degree, q.school, q.year) AS UserQual
from
qualification q
group by
q.pro_id ) PreQConcat
ON p.Pro_ID = PreQConcat.pro_id
LEFT JOIN
( select pj.pro_id,
concat(pj.Title, pj.year) AS UserWork
from
projects pj
group by
pj.pro_id ) PrePJConcat
ON p.Pro_ID = PrePJConcat.pro_id
You are going through all people anyhow, and want all their respective elements (when they exist) grouped, so why group on a possibility it doesn't exist. Let the JOINED queries run once each, complete with a single result grouped by only those people it had data for, then join back to the original profile person.

Related

SQL: how to select where one column does not match another column for ALL records within a given group

I have a table named sales in a MySQL database that looks like this:
company manufactured shipped
Mercedes Germany United States
Mercedes Germany Germany
Mercedes Germany United States
Toyota Japan Canada
Toyota Japan England
Audi Germany United States
Audi Germany France
Audi Germany Canada
Tesla United States Mexico
Tesla United States Canada
Tesla United States United States
Here is a Fiddle: http://www.sqlfiddle.com/#!17/145ff/3
I would like to return the list of companies that ship ALL of their products internationally (that is, where the value in the manufactured column differs from the value in the shipped column for ALL records of a particular company).
Using the example above, the desired result set would be:
company
Toyota
Audi
Here is my (hackish) attempt:
WITH temp_table AS (
SELECT
s.company AS company
, SUM(CASE
WHEN s.manufactured != s.shipped THEN 1
ELSE 0
END
) AS count_international
, COUNT(s.company) AS total_within_company
FROM
sales s
GROUP BY
s.company
)
SELECT
company
FROM
temp_table
WHERE count_international = total_within_company
Essentially, I count the instances where the columns do not match. Then I check whether the sum of those mismatched instances matches the number of records within a given group.
This approach works, but it's far from an elegant solution!
Can anyone offer advice as to a more idiomatic way to implement this query?
Thanks!
We can GROUP BY company and use a HAVING clause to say all countries in shipped must differ to the country in manufactured:
SELECT company
FROM sales
GROUP BY company
HAVING COUNT(CASE WHEN manufactured = shipped THEN 1 END) = 0;
Try out here: db<>fiddle
The fiddle linked in the question is a Postgres DB, but MySQL is taged as DBMS.
In a MySQL DB, the above query can be simplified to:
SELECT company
FROM sales
GROUP BY company
HAVING SUM(manufactured = shipped) = 0;
In a Postgres DB, this is not possible.
You have to think in sets... you want to display all without a match -- find the matches display the rest
SELECT DISTINCT company
FROM sales
WHERE company NOT IN (
SELECT company
FROM sales
WHERE manufactured = shipped
)

MS Access - create and populate column containing concatenated text separated by commas with no repeating values

I have two tables in MS Access, one with foods and associated companies:
FoodID
Food
Company
1
Apple
Vino Farms
2
Orange
Citrus Co.
3
Banana
Vino Farms
and one with information about whether someone ate the food
ClientID
ClientName
Apple
Orange
Banana
1
Bob
Yes
No
Yes
2
Tyler
Yes
Yes
Yes
3
Joe
No
No
No
I'd like to write a query that creates a column populated by the company that makes the foods someone reported eating, separated by commas. If someone reports eating a more than one food made by the same company, I only want the company's name listed once:
ClientID
ClientName
AssociatedCompanies
1
Bob
Vino Farms
2
Tyler
Vino Farms, Citrus Co.
3
Joe
Any help would be greatly appreciated!
As indicated by Gustav, start by normalizing your "ate food" Table. It should look like:
1 Bob Apple
1 Bob Banana
2 Tyler Apple
2 Tyler Orange
2 Tyler Banana
You then get what you want with the following Query:
-- Group companies into one field, sepparated with commas
SELECT ClientID, ClientName, RemoveCommas(Company1 & ", " & Company2 & ", " ... & CompanyN)
FROM
( -- Convert rows (records) into columns (fields)
SELECT ClientID, ClientName
, Max(Iif(Company="Vino Farms", "Vino Farms", Null)) AS Company1
, Max(Iif(Company="Citrus Co.", "Citrus Co.", Null)) AS Company2
, ...
, Max(Iif(Company="Last_company", "Last_company", Null)) AS CompanyN
FROM
(-- Replace food by its manufacturing company
SELECT DISTINCT ClientID, ClientName, Company
FROM
Table_ate_food_normalized AS Ate
INNER JOIN
Table_food AS Comp
ON Ate.Food = Comp.Food
)
GROUP BY ClientID, ClientName
)
Notice the following relevant points:
This will only work, as you notice, for a fixed number of companies with names hardcoded in the Query. This is a very severe limitation. If these restrictions are not satisfied, I suggest that you use a TRANSFORM Query, but, you would not get the companies as a single text field with values sepparated by commas, and you would rather get them as a variable number of fields, each field having a Yes/No value (similar to your current "ate food" table).
I am assuming that the values of "food" and "Company" are each a candidate key in its Table. Otherwise, use the corresponding ID fields, and get the actual values using an inner join.
If you want to understand better the part of converting rows into columns, you may check the Query "K_rows_into_columns_1" from the database of examples dowloadable from LightningGuide.net.
You have to code the user defined VBA function "RemoveCommas()" to remove unncessary commas from the string containing the listing of companies. If unncessesary commas do not bother you, then you can do the Query without coding this functions.
If you want to code the TRANSFORM alternative that I suggested above, you may check the Query "K_rows_into_columns_2" from the database of examples dowloadable from LightningGuide.net.

Sql query to clean up junk record

Somehow a table is having junk data, need to clean it up and generate a new table.
I think it should use case or some row_number over, tried a few, failed.
Database is mysql.
original table:
Student Registration Course
John CS
John 2018
John 2017
Peter 2019 MATH
Mary 2016 MATH
Mary 2016 CS
The rule is, if we have duplicate records for a student, merge them together, for Registration, take max of year. If no any columns is missing, like Mary. order by Course asc, take first record. so the result will be :
Student Registration Course
John 2018 CS
Peter 2019 MATH
Mary 2016 CS
It looks like you want aggregation:
select student
, max(registration) as registration
, min(course) as course
from original
group
by student;
SELECT Student, MAX(Registration), MAX(Course)
-- or MIN(Course) if you want the first alphabetical
FROM YourTable
GROUP BY Student

how to design this database

I have a table for users. Each user has certain skills they teach. So for example:
Bob can teach karate
Louise can teach piano and knitting
Roger can teach judo, sailing and fencing
This is how I've done it in the database:
Table users
pk: uid, name
1 Bob,
2 Louise,
3 Roger
Table skills
pk: sk_id, skill
1 karate,
2 piano,
3 knitting,
4 judo,
5 sailing,
6 fencing
Table user_skill (relationship between user and skills)
pk:usk_id, fk:uid, sk_id
1 1 1,
2 2 2,
3 2 3,
4 3 4,
5 3 5,
6 3 6,
I want to then display "Roger has these skills: judo, basketweaving"
select name, skill
from users, skills, user_skill
where users.uid = user_skill.uid
and users.uid = 3
Is this the right way to go about it - both in terms of designing the tables and querying (mysql)?
Then say I want to update their profile with the areas they teach in:
Bob can teach karate in London
Louise can teach piano in Bolton and knitting in Manchester
Roger can teach judo in London and Manchester, sailing in Liverpool and fencing in Bradford
So I add the following tables:
Table cities
pk: city_id, city
1 London,
2 Manchester,
3 Liverpool,
4 Bolton,
5 Bradford,
But I'm confused as to how to do the relationships. I keep writing it out and realizing it doesnt work and starting again so I've obviously gone wrong somewhere.
I would say your general DB structure is fine as far as the relations go. To incorporate the cities aspect you could use your proposed cities table, but also add a column to your user_skill table to include a reference to the city table.
Also make sure you use proper join statements in the select queries as this is best practice and helps the queries run as efficiently as possible.
Can users teach skills in more than one location, e.g. "bob teaches judo in london and bolton"? Or is it strictly one skill-one city?
Depending on how you want your tables, you'd either just add a 'city' field to the user_skills table, and have multiple "bob/judo/cityX" "bob/judo/cityY" type records. Or you'll add yet another table "user_city_skills" where it'd be "user_skill_ID, cityID".
Your Structure looks fine except your usr_skill table. To incorporate the last part add a fk city_id in user_skill table. If the player can teach the same skill in multiple cities, you will need an additional table to avoid multi-valued columns.
Yes, carry on with it. You should also add one more column in table user_skill which will hold city_id.

Many to Many Relationship using Joins

I have a database two tables and a linking table that I need a JOIN query for:
Here are my Tables:
family (userid (int), loginName, etc)
member (memberid (int), loginName(this links member to a family),name, etc)
Linking Table:
user2member (userid,memberid)...would both be foreign keys?
I want to do two things:
1) Be able to have a family.loginName(12,Johnson) subscribe to another family.loginName (43,Smith) and record that into the linking table.
That would look like this:
12,43
2) When I do a query for all the member.name that are in the Johnson Family, I'll get all the Johnsons & all the Smiths.
If Johnson = Ted, Sue & Patty
IF Smith =Joe, Sue & Bob
my query results would be Johnson now = Ted,Sue,Patty,Joe,Sue,Bob
I asked this question a few days ago without good table names and I ended up confusing myself and the nice guy Ollie Jones who posted an answer similar to this for the query:
SELECT member.name
FROM family
JOIN user2member on family.userid = member.memberid
JOIN member on user2member.name = member.name
 WHERE family.userid = '30'
ORDER BY member.name
I had to change Ollie's answer to match my tables but I'm getting a limit error 0,30 on line 5.
This is my first time doing JOINS and I have no idea if this is correct.
Thanks,
Here's the link to my first question: mySQL table linking , group linked to other members lists, the displaying all members
I am not sure, if the tables you suggested would solve your problem. If I understand your question correct, there are two relationships:
a relationship for all family members (Johnson with Ted, Sue, Patty, Smith with Joe, Sue, Bob)
a relationship for subscriptions (a family can subscribe to another family)
I would suggest following tables:
family (f_id, f_loginName, etc.)
member (m_id, m_f_id, m_name) (foreign key to family, many-to-one relationship)
subscription (s_f_id,s_to_f_id) (linking is based on both family keys)
This would result in following contents:
family:
f_id f_loginName
12 Johnson
43 Smith
member:
m_id m_f_id m_name
1 12 Ted
2 12 Sue
3 12 Patty
4 43 Joe
5 43 Sue
6 43 Bob
subscription
s_f_id s_to_f_id
12 43
Now, to get all possible members for a specific family and it's subscriptions, I would use following SQL query. It has a simple join for family and it's family members. In the WHERE clause, the family Johnson is fetched (f_id = 12) and to get all family members from the subscriptions, it's easier to use a subquery.
SELECT f_loginName, m_name
FROM family
INNER JOIN member ON m_f_id = f_id
WHERE f_id = 12
OR f_id IN (SELECT s_to_f_id FROM subscription WHERE s_f_id = 12)
ORDER BY f_loginName, m_name;