Subquery driving NULL values into outer query - mysql

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

Related

How do I write a JOIN query in SQL linking to other tables? (Specific q in description)

I'm trying to solve this exercise:
The output for this query should show parent/carer title, first name and surname in a single column, and the total number of activities taken by all children registered to that parent/carer.
This is what I have so far:
SELECT CONCAT(carer_title, ' ', carer_fname, ' ', carer_sname) AS 'carer name'
FROM Carer;
Giving me the first column:
I appreciate this is tricky without looking at the full schema but here's the relevant info, let me know if you think I'm missing any information.
Schema:
Relevant tables and columns:
Carer | carer_id |carer_title | carer_fname | carer_sname | carer_phone | carer_address1 | carer_address2 | carer_town | carer_pcode
Child | child_id | child_fname | child_sname | child_gender | child_dob | child_carer | child_school
Childactivity (Table linking children and activities they partake in made up of foreign keys) | child_id | activity_id
Activity | activity_id | activity_name | activity_day | activity_fee
How would you go about adding a column which finds the total number of activities taken by all children registered to each carer?
You could join the other involved tables, and count per group:
select concat(carer_title, ' ', carer_fname, ' ', carer_sname) as `carer name`,
count(*) `number of activities`
from carer
left join child on child.child_carer = carer.carer_id
left join childactivity on childactivity.child_id = child.child_id
group by carer.id;
If only distinct activities should be counted, so not counting twice when two children participate in the same activity, then replace count(*) by count(distinct childactivity.activity_id)

Return value for MySQL NULL result

I am working in MySQL. I have the following query:
SELECT tar.ID, COUNT(tsr.StudentID) AS Students
FROM Teacher_ApplicationRecord tar
LEFT JOIN Teacher_StudentRecord tsr ON sar.ID = tsr.TeacherID AND tsr.Session = 1 AND tsr.Year = 2017
WHERE tar.ApplicationYear = 2017
AND tar.Session1 = 1
This query returns no results. However if I take the COUNT(tsr.StudentID) out of the SELECT statement, it returns all the teacher ID's with NULL for the tsr table.
What I want is a query that returns all the teacher ID's and a count of the students assigned to that teacher, with 0 if the result is NULL.
I have tried COALESCE(COUNT(tsr.StudentID), 0) AND IFNULL(COUNT(tsr.StudentID), 0) with no success so far. Any other thoughts?
UPDATE:
The tsr table has 4 columns: TeacherID, StudentID, Year, Session. It has no records yet. It will be populated next year when students are assigned to teachers.
The tar table has a list of TeacherID's in it with some other data, such as year and faculty.
I want my results to look like below:
+-----------+-----------------+
| TeacherID | COUNT(StudentID)|
+-----------+-----------------+
| 1 | 0 |
+-----------+-----------------+
| 2 | 0 |
+-----------+-----------------+
etc.
As students are assigned to teachers, the COUNT(StudentID) numbers will go up. Hope this helps.
UPDATE 2
The tar table looks like this:
+---------+---------------+
|TeacherID|ApplicationYear|
+---------+---------------+
| 1 | 2017 |
+---------+---------------+
| 2 | 2017 |
+---------+---------------+
| 3 | 2017 |
+---------+---------------+
It has other columns but they are not relevant to the question.
The tsr table looks like this:
+---------+---------+----+-------+
|TeacherID|StudentID|Year|Session|
+---------+---------+----+-------+
| 1 | 10 |2017| 1 |
+---------+---------+----+-------+
| 1 | 11 |2017| 1 |
+---------+---------+----+-------+
| 2 | 12 |2017| 1 |
You can try joining the teacher table to the student table (in that order), and then using GROUP BY to count the number of students per teacher:
SELECT tsr.ID,
COUNT(sar.ID) AS numOfStudents -- count no matching students as zero
FROM Teacher_StudentRecord tsr
LEFT JOIN Student_ApplicationRecord sar
ON tsr.TeacherID = sar.ID AND
tsr.Session = 1 AND
tsr.Year = 2017
GROUP BY tsr.ID
The usefulness of a LEFT JOIN here is the edge case where a teacher has no matching students. In this case, the result set, before aggregation happens, would have a single record for that teacher, and the student ID value would be NULL, which be ignored by COUNT, resulting in a correct zero count.
Note that I removed the WHERE clause, whose intended logic is already contained in the ON clause. This WHERE clause was throwing off your results by removing teacher records before they could even be aggregated.
Update:
Please try the following query to see if you get results:
SELECT tsr.ID,
COUNT(sar.ID) AS numOfStudents
FROM Teacher_StudentRecord tsr
LEFT JOIN Student_ApplicationRecord sar
ON tsr.TeacherID = sar.ID
GROUP BY tsr.ID
If this gives you no results, then no teachers are actually connected to any students, and your data has a problem.
Here is a demo using your sample data. It works as expected:
SQLFiddle

mySQL creating a view with all combinations of rows from 2 tables out of 3?

First of all, here is the sqlfiddle I made: http://sqlfiddle.com/#!2/b9d06c
I have 3 tables, project, document_template, and project_document.
The project table contains a project id and a name.
The document template table contains a document template id and a name for the template.
The project document table contains an id, and a project id and document template id associated with it, as well as a date when it was sent.
I'm trying to create a view that contains all combinations of project ids and document template ids, which I can do easily with a
CROSS JOIN
However, after that I would like to add an additional column to the view named 'last_sent' that contains the latest date at which a template is used with a project, and in order to do that it has to look at the information in the project_document table.
I've included an example in comments at the bottom of the sqlfiddle code to show how it should look.
I've tried adding different left joins to the end of the view query but can't seem to get it to show how I want it to. :(
Any help is greatly appreciated!
SETUP:
basically get your Cartesian Product.. then join the project_id's and document_template_id's to the most recent date.. that date needs to be in a separate select with the id for the project and doc_temp so that you can join it appropriately.. finally use coalesce on the date so that when theres null values you put in a blank
QUERY:
SELECT
t1.project_id , t1.document_template_id , COALESCE(t.last_sent, ' ') AS last_sent
FROM
( SELECT p.project_id, dt.document_template_id
FROM project p, document_template dt
GROUP BY project_id, document_template_id
) t1
LEFT JOIN
( SELECT project_id, document_template_id, MAX(sent_on) AS last_sent
FROM project_document
GROUP BY document_template_id
) t ON t.project_id = t1.project_id
AND t.document_template_id = t1.document_template_id;
OUTPUT:
+--------------+--------------------------+---------------------+
| project_id | document_template_id | last_sent |
+--------------+--------------------------+---------------------+
| 1 | 1 | |
| 1 | 2 | 2014-08-12 10:00:23 |
| 1 | 3 | 2014-07-11 08:12:40 |
| 2 | 1 | |
| 2 | 2 | |
| 2 | 3 | |
+--------------+--------------------------+---------------------+
DEMO
Try
SELECT
p.project_id,
dt.document_template_id,
MAX(pd.sent_on) AS last_sent
FROM project p
CROSS JOIN document_template dt
LEFT OUTER JOIN project_document pd
ON p.project_id=pd.project_id
AND dt.document_template_id=pd.document_template_id
GROUP BY p.project_id, dt.document_template_id
ORDER BY p.project_id, dt.document_template_id
If you want empty instead null use COALESCE(MAX(pd.sent_on), '') AS last_sent instead
When you use aggregate functions as MAX, you should point group target by GROUP BY clause. Else you will have only one returned row.

MySql Impass - can't move forward

I currently have this sql statement that I wrote and it works but it's not quite what I want. I've been working on it for hours but can't seem to get any further.
select parent.id as parent_id, parent.subject as parent,s.id,s.subject from (select s.id, s.subject from subjects s where parent_id = 0) parent join subjects s on parent.id = s.parent_id order by parent.subject, s.subject
It's grabbing all the subjects and ordering correctly but I also want to return the parent subject (parent_id = 0) at the top of each grouping. This is because some parents may not have subjects underneath but I still need to return them. Also the ordering is off when I try to do it as I want the parent first then it's child subjects. Hope that makes sense but if not just ask.
Any help would be appreciated.
Thanks
Steve
You're talking about grouping sets of rows by their parent rows.
The only way I know how to do this in MySQL is using the GROUP_CONCAT() function which won't group the subjects by row, but rather create a grouped string.
Here's what you can do:
SELECT
a.id,
a.subject,
GROUP_CONCAT(CONCAT(b.id, ':::', b.subject) ORDER BY b.subject SEPARATOR '|||') AS subjectlist
FROM
subjects a
LEFT JOIN
subjects b ON a.id = b.parent_id
WHERE
a.parent_id = 0
GROUP BY
a.id,
a.subject
ORDER BY
a.subject
So this will give you a result set like:
id | subject | subjectlist
---------------------------------------------------------------------
2 | subj1 | 23:::childsubj1|||28:::childsubj4
3 | subj2 | 18:::childsubj8|||55:::childsubj16
4 | subj3 | NULL
Depending on what language you are using in your application, you may be able to "explode" the subjects string into arrays delimited first by ||| which separates each subject, then ::: which separates that subject's ID and name.
Obviously, the downside of this is you have to make sure that your child subject name does not contain either ||| or ::: or whichever delimiters you decide to use.
Edit: Experimentally, I came up with this alternative solution which may be closer to what you're looking for:
Try:
SELECT
c.subj,
c.id
FROM
(
SELECT
CONCAT('---> ', b.subject) AS subj,
b.id,
CONCAT(a.subject, b.subject) AS orderfactor
FROM
subjects a
INNER JOIN
subjects b ON a.id = b.parent_id
WHERE
a.parent_id = 0
UNION ALL
SELECT
subject AS subj,
id,
subject AS orderfactor
FROM
subjects
WHERE
parent_id = 0
) c
ORDER BY
c.orderfactor
This query should give you a result along the lines of:
subject | id |
----------------------------------------------------------
subj1 | 2 |
---> childsubj1 | 23 |
---> childsubj4 | 28 |
subj2 | 3 |
---> childsubj8 | 18 |
---> childsubj16 | 55 |
subj3 | 4 |
subj4 | 5 |
---> childsubj10 | 79 |

How to sort items in mysql based on data from another table?

I have the following tables:
word_list:
id | word
1 | ball
2 | car
3 | small
items_word_match:
itemid | wordid | in_title | in_description
1 | 1 | 1 | 0 //The word #1 occurs once in the title of the item
1 | 3 | 0 | 2 //The word #3 occurs twice in the title of the item
3 | 3 | 1 | 2
2 | 1 | 1 | 0
search:
wordid | importance
1 | 1
2 | 5
3 | 2 //word #3 is more important than the word #1 but less so than word #2
I want to sort the items based on the keywords from the search table and how important the keywords are.
And if the keyword is in the title the importance should increase by 1 and if the word appears 2 times the importance of the word should be importance*2
Neither of those answers from Denis or Johan are correct. Instead you could use this:
select
itemid,
sum(word_importance) as item_importance
from
(select
itemid,
search.wordid,
(
in_title * (importance + 1)
+ in_description * importance
) as word_importance
from
items_word_match,
search
where
i.wordid = s.wordid
)
group by itemid
As Johan pointed out, you need to add an order clause to the end, , order by item_importance desc
Feel a bit lazy today, so I'm just going to answer the question in the title:
How to sort items in mysql based on data from another table ?
You can sort the outcome of a query by any criterium you wish.
SELECT word_list.* FROM word_list
INNER JOIN search ON (search.wordid = wordlist.id)
ORDER BY search.importance, word_list.id DESC
Note that the JOIN needed to link the two tables together can have a profound effect on which rows in the word_list table are selected, but you need to do a JOIN somehow.
Otherwise MySQL will not know what the relationship between the two tables is and cannot sort the fields.
SELECT
i.itemid
, SUM( i.in_description * s.importance
+ i.in_title * ( s.importance + 1 )
)
AS item_importance
FROM
items_word_match i
LEFT JOIN
search s
ON s.wordid = i.wordid
GROUP BY
i.itemid
ORDER BY
item_importance DESC
CORRECTION:
I used LEFT JOIN catch the case when some words do not appear in the search table. But then the importance of those words seems appropriate to be 0 and not NULL, so the SUM should be changed into:
, SUM( i.in_description * COALESCE(s.importance, 0)
+ i.in_title * COALESCE(s.importance, 1)
)
your order by clause can contain fields from any of the tables:
select table1.*
from table1
join table2 using (table1_id)
order by table2.field, table1.field, etc.