Mysql join table result as one row - mysql

I have two tables
prj
id | ptitle
1 | prj111
2 | prj222
prjflow
id | pid | paction | pactiontxt
1 | 1 | 1 | man1
2 | 1 | 1 | man2
3 | 1 | 2 | woman1
4 | 1 | 1 | man3
i want this output:
output
ptitle | men | women
prj111 | man1,men3 | woman1
i write this query:
SELECT prj.ptitle
, GROUP_CONCAT(pflow1.pactiontxt) men
, GROUP_CONCAT(pflow2.pactiontxt) women
FROM prj
JOIN prjflow pflow1
ON prj.id = pflow1.pid
AND pflow1.paction = 1
JOIN prjflow pflow2
ON prj.id = pflow2.pid
AND pflow2.paction = 2;
but output is:
ptitle | men | women
prj111 | man1,men3 | woman1,woman1
My Query when the number of rows of men and women have been equal, working properly
but
i want that works at any case.
thanks a lot
and excuse me for poor english writing

Just use conditional aggregation:
SELECT prj.ptitle,
GROUP_CONCAT(CASE WHEN prjflow.paction = 1 THEN prjflow.pactiontext END ORDER BY prjflow.id) as men,
GROUP_CONCAT(CASE WHEN prjflow.paction = 2 THEN prjflow.pactiontext END ORDER BY prjflow.id) as women
FROM prj JOIN
prjflow
ON prj.id = prjflow.pid
GROUP BY prj.ptitle;
This will also fix two potential problems with your query. The first is performance. If some of the titles have large numbers of men and women, then the query has to process a cartesian product. The second is semantic. If some titles have men but not women or women without men, then the two joins will filter them out.
Here is a SQL Fiddle demonstrating it.
Do note that the suggested output seems inconsistent with the input data. This produces the output:
ptitle | men | women
prj111 | man1,man2,men3 | woman1
I see no reasonable way to exclude man2 from the list, so I assume that is a typo.

The 'quick fix' would be to use distinct in the group_concat aggregation:
SELECT
prj.ptitle,
group_concat(distinct pflow1.pactiontxt) AS men,
group_concat(distinct pflow2.pactiontxt) AS women
FROM prj
JOIN prjflow AS pflow1 ON (prj.id = pflow1.pid AND pflow1.paction = 1)
JOIN prjflow AS pflow2 ON (prj.id = pflow2.pid AND pflow2.paction = 2)
GROUP BY
prj.ptitle -- Officially you'll need this as well, although MySQL is quite forgiving.
But the query still uses two joins, and will even fail if there a no men or no women for a particular project.
So to solve that, you can write the query with subqueries. This way, you won't need joins distinct or group by, and the query will work too when an item has no men or no women at all.
SELECT
prj.ptitle,
( SELECT group_concat(pflow1.pactiontxt) FROM prjflo pflow1
WHERE prj.id = pflow1.pid AND pflow1.paction = 1) AS men,
( SELECT group_concat(pflow2.pactiontxt) FROM prjflo pflow2
WHERE prj.id = pflow2.pid AND pflow2.paction = 2) AS women
FROM prj

Related

Mysql SELECT in SELECT

I have a table like this :
+--------+---------------------------------------+
| CandId | Speak |
+--------+---------------------------------------+
| 1 | English |
| 1 | Spanish |
| 2 | English |
| 2 | Spanish |
| 3 | Dutch |
| 3 | English |
| 4 | Dutch |
| 4 | Spanish |
| 4 | German |
+--------+---------------------------------------+
I'm trying to make a query that would for instance get the CandId of people that speaks English and Spanish (not or Spanish).
So in the specific case the query would show 1 and 2.
It's surely very easy but I even can't imagine how to do this.
Many thanks by advance for your help.
SELECT CandId
FROM Candidate
WHERE Speak in ('English','Spanish')
GROUP BY CandId
HAVING COUNT(CandId) = 2
It returns all CandId for which there are 2 records which match 'English' or 'Spanish'.
If you search for more languages also change in the HAVING clause the value of 2 in the number of languages.
UPDATED (thanks to spencer7593 for the valid comment):
if the combination CandId+Speak is not unique, one must use:
SELECT CandId
FROM Candidate
WHERE Speak in ('English','Spanish')
GROUP BY CandId
HAVING COUNT(DISTINCT Speak) = 2
Aggregation with a having clause is a very general way to answer these "set-within-a-set" questions. For both English and Spanish:
select candid
from t
group by candid
having sum(language = 'English') > 0 and
sum(language = 'Spanish') > 0;
If you don't want Spanish:
having sum(language = 'English') > 0 and
sum(language = 'Spanish') = 0;
Nuahatl and Amdo but not Mandarin:
having sum(language = 'Nuahatl') > 0 and
sum(language = 'Amdo') > 0 and
sum(language = 'Mandarin') = 0;
The column name CandId suggests that you have a Cand table containing the people. You can then select from that table and look up the languages with IN or EXISTS:
select *
from cand
where candid in (select candid from cand_languages where speak = 'English')
and candid in (select candid from cand_languages where speak = 'Spanish')
order by candid;
With a large table, you would provide an index to get the lookup fast:
create index idx on cand_languages(speak, candid);
You can join the table on itself using the CandId:
SELECT
DISTINCT t1.CandId
FROM
`table` t1
JOIN `table` t2 ON t2.CandId = t1.CandId
WHERE
t1.Speak = 'English'
AND t2.Speak = 'Spanish';
See http://sqlfiddle.com/#!9/97635c/3 for a working example
You can use CASE+SUM to count language for every candidate and then select only having count=2:
select CandId
from
(
select CandId,
sum( case speak
when 'English' then 1
when 'Spanish' then 1
else 0
end) countlang
from Table
group by CandId
)x
where countlang=2

Subquery driving NULL values into outer query

I keep getting NULL rows in a nested query
Provided we have the following tables:
'article' containing article information
'bom' containing an m-n relation between article IDs, forming a bill of materials
Not every article is appearing in the 'bom' table - some will neither appear as parent nodes, nor as child nodes in any bill of materials.
I'm trying to get a list of articles and include a concatenated string containing the bill of materials, in case my article is a parent node.
Hence I'm trying this query structure in MySQL:
SELECT
a.id,
a.name,
(SELECT group_concat(
bom.quantity, ' times ',
a_child.partnumber, ' ',
a_child.name)
FROM
bom
left outer join article a_child on a_child.id = bom.child_article
WHERE
bom.parent_article = a.id
) myBOM
from article a
The query should also return plain articles which are not a parent of any bom or a part of any bom.
In case a parent article does have an associated bom, it should be outlined on the myBOM column. Otherwise the column should just be empty.
I am now getting some lines returning NULL in the outer query, while others work just fine - and I can't figure out what is driving some to work, others to fail.
How do I go about this ?
Update: some sample data:
Table article:
id | name | partnumber
-----+---------+------------
1 | desk | P001
2 | leg | P002
3 | board | P003
Table bom:
id | parent_article | child_article | quantity
-----+----------------+---------------+---------
1 | 1 | 2 | 4
2 | 1 | 3 | 1
Expected output:
id | name | myBOM
-----+------------+-------------------------------------
1 | table | 4 times P002 leg, 1 times P003 board
2 | leg |
3 | board |
I guess tables should be swaped and group_concut wrap with IFNULL instruction... The following query returns required result
SELECT
a.id,
a.name,
(SELECT IFNULL(group_concat(
bom.quantity, ' times ',
a_child.partnumber, ' ',
a_child.name), '')
FROM
article a_child
left outer join bom on a_child.id = bom.child_article
WHERE
bom.parent_article = a.id
) myBOM
from article a

SQL Select parent as column name and child as value

I am creating a database to store music.
There are different categories that each have sub categories. Example:
id name parentID
1 instrumentation null
2 solo_instrument null
3 Concert Band 1
4 Brass Band 1
5 Fanfare Band 1
6 Clarinet 2
7 Saxophone 2
8 Trumpet 2
On the other hand I have a table that stores the musicID that is linked to a categoryID
id categoryID musicID
1 4 1
2 8 1
3 3 2
4 6 2
I need the following result from a query:
musicID instrumentation solo_instrument
1 Brass Band Trumpet
2 Concert Band Clarinet
I have been told to use a tree structure as in the future it is likely that other categories are added and this should be able to support that. However, I am not able to figure out how to write a query to get the result above.
I kind of get the result I want when selecting first the instrumentation, second the solo_instrument, but this is all hardcoded and does not allow for music tracks to only have one parentID as I select them individually.
Is this even possible or should I overhaul my database structure? I'd like to see your recommendations.
You should be able to tackle this using conditional aggregation.
Query :
SELECT
mu.musicID,
MAX(CASE WHEN cp.name = 'instrumentation' THEN ca.name END) instrumentation,
MAX(CASE WHEN cp.name = 'solo_instrument' THEN ca.name END) solo_instrument
FROM
musics mu
INNER JOIN categories ca ON ca.id = mu.categoryID
INNER JOIN categories cp ON cp.id = ca.parentID
GROUP by mu.musicID
The INNER JOINs pull up the corresponding category, and then goes up one level to find the parent category. If new root categories are created, you would just need to add more MAX() columns to the query.
In this DB Fiddle demo with your sample data, the query returns :
| musicID | instrumentation | solo_instrument |
| ------- | --------------- | --------------- |
| 1 | Brass Band | Trumpet |
| 2 | Concert Band | Clarinet |
First you group by musicid in table_music and the join twice to table_categories:
select t.musicid, c1.name instrumentation, c2.name solo_instrument
from (
select musicid, min(categoryid) instrumentationid, max(categoryid) solo_instrumentid
from table_music
group by musicid
) t inner join table_categories c1
on c1.id = t.instrumentationid
inner join table_categories c2
on c2.id = t.solo_instrumentid
order by t.musicid

How to get multiple counts with multiple joins using one SQL query?

There is an error in my query and I would like some help. I have three tables
Rooms{id,number,name,type(ECO/LUX),active(0/1)}
Men{passport,roomid,status(YOUTH/ADULT)}
Women{passport,roomid,status(YOUTH/ADULT)}
**In each room there can be more than one woman or man.
I want to count how many women and men have the same room with roomid in (1,2,3), status='ADULT', type='LUX' and active=1. Therefore I need a result like this:
+----+--------+-----------+----------+------------+
| id | number | name | CountMen | CountWomen |
+----+--------+-----------+----------+------------+
| 1 | 23 | 1st suite | 2 | 4 |
| 3 | 4 | 2nd suite | 1 | 2 |
+----+--------+-----------+----------+------------+
SELECT id,number,name,
sum(case when Men.status='ADULT' then 1 else 0 end) as CountMen,
sum(case when Women.status='ADULT' then 1 else 0 end) as CountWomen
FROM Rooms left join Men
on Rooms.id=Men.roomid
left join Women on Room.id=Women.roomid where
(type='LUX') and (active=true) and (id in (1,2,3))
group by id;
The problem is that I get sometimes wrong results in the counters.
In a left join, conditions on the second table need to be in the on clause. It would help if you qualified all column names in the query.
However your problem is because you are getting a Cartesian product between the gender tables. This is definitely a case where gender segregation is not a good thing. You should have just one table for people (and this doesn't even bring up other issues with defining binary genders).
SELECT r.id, r.number, r.name,
(SELECT COUNT(*)
FROM men m
WHERE m.status = 'ADULT' AND r.id = m.roomid
) as CountMen,
(SELECT COUNT(*)
FROM women w
WHERE w.status = 'ADULT' AND r.id = w.roomid
) as CountWomen
FROM Rooms r
WHERE r.type = 'LUX' AND r.active = true AND r.id IN (1, 2, 3);
However, you should fix your data model so you have people rather than segregated gender tables.

SQL - match records from one table to another table based on several columns

I have two tables:
tblhobby
+-------+-------+-------+-------+
| name |hobby1 |hobby2 |hobby3 |
+-------+-------+-------+-------+
| kris | ball | swim | dance |
| james | eat | sing | sleep |
| amy | swim | eat | watch |
+-------+-------+-------+-------+
tblavailable_hobby
+----------------+
| available_hobby|
+----------------+
| ball |
| dance |
| swim |
| eat |
| watch |
+----------------+
the sql query should take all the columns in tblhobby and match it with tblavailable_hobby. If all the hobbies match to the available_hobby, then the person is selected
the query should produce
+--------+
| name |
+--------+
| kris |
| amy |
+--------+
Please help
Thanks for the answers. I have inherited this database and not able to normalize it at the moment. however, I would like to add another twist to the question. Suppose:
+-------+-------+-------+-------+
| name |hobby1 |hobby2 |hobby3 |
+-------+-------+-------+-------+
| kris | ball | swim | dance |
| james | eat | sing | sleep |
| amy | swim | eat | watch |
| brad | ball | | dance |
+-------+-------+-------+-------+
I would like to get
+--------+
| name |
+--------+
| kris |
| amy |
| brad |
+--------+
how would i go about with it?
Poor DB design, but, assuming you have to live with it:
SELECT h.name
FROM tblhobby h
INNER JOIN tblavailable_hobby ah1
ON h.hobby1 = ah1.available_hobby
INNER JOIN tblavailable_hobby ah2
ON h.hobby2 = ah2.available_hobby
INNER JOIN tblavailable_hobby ah3
ON h.hobby3 = ah3.available_hobby
EDIT: Answering the twist proposed in the comments below.
SELECT h.name
FROM tblhobby h
LEFT JOIN tblavailable_hobby ah1
ON h.hobby1 = ah1.available_hobby
LEFT JOIN tblavailable_hobby ah2
ON h.hobby2 = ah2.available_hobby
LEFT JOIN tblavailable_hobby ah3
ON h.hobby3 = ah3.available_hobby
WHERE (h.hobby1 IS NULL OR ah1.available_hobby IS NOT NULL)
AND (h.hobby2 IS NULL OR ah2.available_hobby IS NOT NULL)
AND (h.hobby3 IS NULL OR ah3.available_hobby IS NOT NULL)
I know this doesn't answer your question directly, and others have pointed out that your table design is problematic. What it should look like is this:
Table: Person
Id Name
-------------
1 Kris
2 James
3 Amy
table: PersonHobby (Join table)
PersonId HobbyId
----------------
1 1 -- Kris likes to ball
1 2 -- " dance
1 3 -- " swim
2 4 -- James likes to eat
Table: Hobby
Id Name
--------------
1 Ball
2 Dance
3 Swim
4 Eat
etc.
This design uses the concept of a Join or Junction table that allows you make many-to-many relationships between data. In this case people and hobbies.
You then query the data like this:
SELECT *
FROM Person p
JOIN PersonHobby AS ph on p.Id = ph.PersonId
JOIN Hobby AS h on h.Id = ph.HobbyId
WHERE ... -- filter as you need to
The PersonHobbies table in my example takes a table of Persons and a table of Hobbies and enables relationships between Persons and Hobbies. I know this will probably look like more work to you... extra tables, extra columns. But trust us, this design will make your life much simpler in the near future. In fact, you're already feeling the pain of your design by trying to figure out a query which should be much simpler than it is against your current db.
I would like to produce a WHERE filter to match your requirements but I don't quite understand what you're after. Could you explain in some more detail?
You can use a query to transform your existing table into a "virtual table", which I think should be easier to work with. Save this SQL statement as qryHobbiesUnion.
SELECT [name] AS person, hobby1 AS hobby
FROM tblhobby
WHERE (((hobby1) Is Not Null))
UNION
SELECT [name], hobby2
FROM tblhobby
WHERE (((hobby2) Is Not Null))
UNION
SELECT [name], hobby3
FROM tblhobby
WHERE (((hobby3) Is Not Null));
I enclosed "name" in square brackets because it's a reserved word. And I aliased [name] as person to avoid problems with square brackets when using qryHobbiesUnion in a subquery later.
I assumed any "empty" values for hobby will be Null. If blanks could also be empty strings (""), change the WHERE clauses to a pattern like this:
WHERE Len(hobby1 & "") > 0
After you determine which version of the WHERE clause returns the correct rows, save the query and use it in another query.
SELECT sub.person
FROM
[SELECT qh.person, qh.hobby, ah.available_hobby
FROM
qryHobbiesUnion AS qh
LEFT JOIN tblavailable_hobby AS ah
ON qh.hobby = ah.available_hobby
]. AS sub
GROUP BY sub.person
HAVING (((Count(sub.hobby))=Count([sub].[available_hobby])));
Using your second set of sample data, that query returns the 3 person names you wanted: amy; brad; and kris.
If tblhobby contained a row for a person with all the hobby fields empty, this query would not include that person's name. That makes sense to me because it seems your intention is to identify the people whose hobby choices are all matched in tblavailable_hobby. So a person with no hobby selections has no matches. If you want different behavior, this will probably get uglier. :-)
Really you must learn more about relational databases. Your design isn't good. You should have table with people and a table with hobbies. Then you should have a table the relates the two tables by an ID.
Your tables should look likes this
TABLE: People
COLUMNS: PID (INT, Primary Key), NAME
TABLE: Hobbies
COLUMNS: HID (INT, Primary Key), Hobby
TABLE: PeoplesHobbies
COLUMNS: ID, PID, HID
THEN your query would look something like this
select * from people `p` inner join PeoplesHobbies `ph` on p.PID = ph.PID inner join on Hobbies `h` on ph.HID = h.HID where p.NAME = 'JOHN'
SELECT name
FROM tblhobby AS h
WHERE EXISTS
( SELECT *
FROM tblavailable_hobby AS ah1
WHERE h.hobby1 = ah1.available_hobby
)
AND EXISTS
( SELECT *
FROM tblavailable_hobby AS ah2
WHERE h.hobby2 = ah2.available_hobby
)
AND EXISTS
( SELECT *
FROM tblavailable_hobby AS ah3
WHERE h.hobby3 = ah3.available_hobby
)
Borrowing from Joe's answer:
SELECT h.name
FROM tblhobby h
LEFT JOIN tblavailable_hobby ah1
ON h.hobby1 = ah1.available_hobby
LEFT JOIN tblavailable_hobby ah2
ON h.hobby2 = ah2.available_hobby
LEFT JOIN tblavailable_hobby ah3
ON h.hobby3 = ah3.available_hobby
WHERE (h.hobby1 IS NULL OR ah1.available_hobby IS NOT NULL)
AND (h.hobby2 IS NULL OR ah2.available_hobby IS NOT NULL)
AND (h.hobby3 IS NULL OR ah3.available_hobby IS NOT NULL)
ypercube's answer can be similarly extended.