MySQL join question - sql

I'm trying to join 3 tables, somehow do an hierarchical inner join, and get data from the 3rd table. My starting point is the article_number (156118) from the article table. Here are the working sql statements and table structure, but there must be a way to join all this together in one, right?
// Get the parent task from the article
select task_parent
from article a, tasks t
where a.task_id = t.task_id
and a.article_number = 156118
// Get the task id for the 'Blog' task
select task_id
from tasks
where task_parent = 26093
and task_name like '%blog%'
// Get ALL the blog record
select *
from blogs
where task_id = 26091
---------Tables------------
* article table *
id | article_number | task_id
1 | 156118 | 26089
* tasks table *
id | task_name | task_parent
26089 | article | 26093
26091 | blogs | 26093
26093 | Main Task | 26093
* blog table *
id | task_id | content
1 | 102 | blah
2 | 102 | blah
3 | 102 | blah
-------------
* How do I get all of blog data with 1 SQl statement using just the article_number?
Thanks in advance!

EDIT: Revised answer after rereading question. You need two joins to the task table. One to get the parent of the article's task and a second to get the blog task with the same parent as the article's task.
select b.id, b.task_id, b.content
from article a
inner join tasks t1
on a.task_id = t1.task_id
inner join tasks t2
on t1.task_parent = t2.task_parent
and t2.task_name like '%blog%'
inner join blogs b
on t2.task_id = b.task_id
where a.article_number = 156118

Looks like you're wanting to tie them all together and just use the article number as the parameter...
Try:
select b.*
from blogs b, tasks t, tasks tp, article a
where b.task_id = t.task_id
and t.task_parent = tp.task_id
and tp.task_id = a.task_id
and a.article_number = 156118

Here you go.
SELECT * FROM a
INNER JOIN tasks USING (a.article_number)
INNER JOIN blogs USING (a.article_number)
WHERE a.article_number = 156118;

Related

Join Tables to return 1 or 0 based on multiple conditions

I am working on a project management website and have been asked for a new feature in a review meeting section.
A meeting is held to determine whether to proceed to the next phase, and I need to maintain a list of who attended each phase review meeting. I need to write an SQL query to return all people, with an additional column that states they have already been added before.
There are two tables involved to get my desired result, with the relevant columns listed below:
Name: PersonList
ID | Name | Division
Name: reviewParticipants
ProjectID | PersonID | GateID
The query I am looking for is something that returns all people in PersonList, with an additional "hasAttended" bit that is TRUE if reviewParticipants.ProjectID = 5 AND reviewParticpants.CurrentPhase = 'G0' ELSE FALSE.
PersonName | PersonID | hasAttended
Mr Smith | 1 | 1
Mr Jones | 2 | 0
I am not sure how to structure such a query with multiple conditions in a (left?) join, that would return as a different column name and data type, so I would appreciate if anybody can point me in the right direction?
With the result of this query I am going to add a series of checkboxes, and use this additional bit to mark it checked, or not, for page refreshes.
You can use LEFT JOIN as well:
SELECT DISTINCT p.*
,CASE WHEN rp.id IS NOT NULL THEN 1 ELSE 0 END AS hasAttended
FROM personlist p
LEFT JOIN reviewParticipants rp ON rp.personid = p.id
AND rp.projectid = 5
AND rp.currentphase = 'GO'
I agree with Gordon Linoff: I would prefer an int or tinyint over a bit value,
You can use exists to see if there is a matching row.
select p.*,
(case when exists (select 1
from reviewParticipants rp
where rp.personid = p.id and
rp.projectid = 5 and
rp.currentphase = 'GO'
)
then 1 else 0 end)
from personlist p;
I see no reason to prefer a bit over an integer, but you can return a bit if you really prefer.
This will do :
select a.* from PersonList a where a.hasAttended=1 and
a.Id in (select b.PersonId from reviewParticipants b
where b.ProjectID =5 and exists (
select 1 from reviewParticipants c where c.CurrentPhase = 'G0'and
c.Project =b.projectId
)
)

Returning a number when result set is null

Each lot object contains a corresponding list of work orders. These work orders have tasks assigned to them which are structured by the task set on the lots parent (the phase). I am trying to get the LOT_ID back and a count of TASK_ID where the TASK_ID is found to exist for the where condition.
The problem is if the TASK_ID is not found, the result set is null and the LOT_ID is not returned at all.
I have uploaded a single row for LOT, PHASE, and WORK_ORDER to the following SQLFiddle. I would have added more data but there is a fun limiter .. err I mean character limiter to the editor.
SQLFiddle
SELECT W.[LOT_ID], COUNT(*) AS NUMBER_TASKS_FOUND
FROM [PHASE] P
JOIN [LOT] L ON L.[PHASE_ID] = P.[PHASE_ID]
JOIN [WORK_ORDER] W ON W.[LOT_ID] = L.[LOT_ID]
WHERE P.[TASK_SET_ID] = 1 AND W.[TASK_ID] = 41
GROUP BY W.[LOT_ID]
The query returns the expected result when the task id is found (46) but no result when the task id is not found (say 41). I'd expect in that case to see something like:
+--------+--------------------+
| LOT_ID | NUMBER_TASKS_FOUND |
+--------+--------------------+
| 500 | 0 |
| 506 | 0 |
+--------+--------------------+
I have a feeling this needs to be wrapped in a sub-query and then joined but I am uncertain what the syntax would be here.
My true objective is to be able to pass a list of TASK_ID and get back any LOT_ID that doesn't match, but for now I am just doing a query per task until I can figure that out.
You want to see all lots with their counts for the task. So either outer join the tasks or cross apply their count or use a subquery in the select clause.
select l.lot_id, count(wo.work_order_id) as number_tasks_found
from lot l
left join work_order wo on wo.lot_id = l.lot_id and wo.task_id = 41
where l.phase_id in (select p.phase_id from phase p where p.task_set_id = 1)
group by l.lot_id
order by l.lot_id;
or
select l.lot_id, w.number_tasks_found
from lot l
cross apply
(
select count(*) as number_tasks_found
from work_order wo
where wo.lot_id = l.lot_id
and wo.task_id = 41
) w
where l.phase_id in (select p.phase_id from phase p where p.task_set_id = 1)
order by l.lot_id;
or
select l.lot_id,
(
select count(*)
from work_order wo
where wo.lot_id = l.lot_id
and wo.task_id = 41
) as number_tasks_found
from lot l
where l.phase_id in (select p.phase_id from phase p where p.task_set_id = 1)
order by l.lot_id;
Another option would be to outer join the count and use COALESCE to turn null into zero in your result.

SQL query with two EXISTS statements behaving differently than expected

The following SQL query is intended to find label_item_lists which have label_items with given names.
SELECT lils.id FROM label_item_lists AS lils
INNER JOIN label_items AS items ON lils.id = items.label_item_list_id
WHERE EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
OR EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
It properly returns the ids of label_item_lists having an item with either name. However, the same query using the AND operator rather than OR returns no results.
SELECT lils.id FROM label_item_lists AS lils
INNER JOIN label_items AS items ON lils.id = items.label_item_list_id
WHERE EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
AND EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
There are definitely label_item_list entries that have label_items matching both names provided. In fact the OR SQL query returns the id twice for these entries, but the AND query returns no results. For this reason I think I might be missing an important piece of info on how these JOINed queries with EXISTS work. Thanks for any assistance!
----------------------------------------------------------------
| label_items | id | name | label_item_list_id |
----------------------------------------------------------------
| Row1 | 1 | foo | 1 |
----------------------------------------------------------------
| Row2 | 2 | bar | 1 |
----------------------------------------------------------------
| Row3 | 3 | bar | 2 |
----------------------------------------------------------------
--------------------------------
| label_item_lists | id |
--------------------------------
| Row1 | 1 |
--------------------------------
| Row2 | 2 |
--------------------------------
I want to return the first label_item_list but not the second, as it only has one of the two names I am searching for, 'foo' and 'bar'.
try changing the where condition from
WHERE EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
AND EXISTS(SELECT * FROM label_item_lists WHERE items.name=?)
to
WHERE EXISTS(SELECT * FROM label_item_lists lst WHERE lst.name=?)
AND EXISTS(SELECT * FROM label_item_lists lst WHERE lst.name=?)
In your query AND will not return anything because on same output row it will apply both filters which will never happen hence it is giving blank output.
And Or operator will never check condition after OR operator until first condition is false.
Try something like this, # is just a place holder to distinguish between two searches:
select * from label_items lil
where label_item_list_id
in (
select li.label_item_list_id from
label_items li
inner join label_items l1
on li.label_item_list_id = l1.label_item_list_id
and li.name <> l1.name
where concat(li.name,'#',l1.name) = 'foo#bar')
This is what I eventually came up with! I'm not 100% confident yet, but it has worked so far. I added a bit of functionality in Ruby and ActiveRecord to allow for as many necessary matches as desired and to return only those which match exactly (without any extra names not in the list).
items = ["foo", "bar"]
db = ActiveRecord::Base.connection
query = <<-EOS
SELECT lils.id FROM label_item_lists AS lils
JOIN label_items AS items ON items.label_item_list_id = lils.id
WHERE lils.id IN (
SELECT label_item_list_id FROM label_items AS items
WHERE items.name IN (#{(['?'] * items.length).join(',')})
) AND lils.id NOT IN (
SELECT label_item_list_id FROM label_items AS items
WHERE items.name NOT IN (#{(['?'] * items.length).join(',')})
)
GROUP BY lils.id
HAVING COUNT(DISTINCT items.name) = #{items.length}
EOS
query = ActiveRecord::Base.send(:sanitize_sql_array, [query, *(items*2)])
Basically it checks that a name is both IN the list of given names (items array) AND it also checks that the name IS NOT outside (or NOT IN) the list of given names. Any list of label_items that has a non-matching name is excluded by the latter IN query. This is helpful so that a label_item_list with both "foo" and "bar" but also "lorem" is not included.

SQL - Fallback to default language when translate does not exist

Recording texts in different languages in a table (tbl_i18n) with the following structure:
text_FK | language_FK | value
-----------------------------
1 | 1 | hello
1 | 2 | hallo
2 | 1 | world
3 | 1 | test
gives texts of a specific language (id = 2) with a simple join like:
SELECT [value] FROM tbl_i18n i
JOIN tbl_products p ON p.text_id = i.text_FK
JOIN tbl_languages l ON l.id = i.language_FK AND i.language FK = 2;
and the result is:
value
-------
hallo
How could aforementioned select statement changed so we could have got a default language and when translate for a text fields does not exist their fallback text will be shown and the result will became:
value
-------
hallo
world
test
LEFT JOIN the language table twice. The first time for wanted language, the second time for fallback value. Use COALESCE to pick wanted language if available, otherwise fallback language.
SELECT coalesce(l1.[value], l2.[value])
FROM tbl_i18n i
JOIN tbl_products p ON p.text_id = i.text_FK
LEFT JOIN tbl_languages l1 ON l.id = i.language_FK AND i.language_FK = 2
LEFT JOIN tbl_languages l2 ON l.id = i.language_FK AND i.language_FK = 1;
I think in simple english you want the highest available language_FK for each text_FK.
WITH X AS (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY text_FK ORDER BY language_FK DESC)rn
FROM TableName
)
SELECT *
FROM X
WHERE X.rn = 1

sql select where not in

I'm trying to select some data from a task table, but not any subtasks which a user may have created for themselves. So, I want to filter out any tasks that have a parent_taskid which is a task_id already assigned to that user.
E.g.
UserID | Parent_TaskID | TaskID
------ | ------------- | ------
435 | 149329 | 161280
435 | 149330 | 210717
435 | 149330 | 228100
435 | 156991 | 149330
169 | 161280 | 546540
169 | 456842 | 458764
So from the table above TaskIDs 210717 & 228100 would be removed from my select because their parent (149330) is a taskID already assigned to that user - making them subtasks. - but 546540 would not be removed because it is a taskID assigned to another user.
So I'm thinking something like
select Task.taskID, Task.Parent_taskID, Task.userID
from task
where Task.Parent_TaskID not in (??? select taskID from task where ???)
Any ideas?
SELECT
t1.taskID,
t1.Parent_taskID,
t1.userID
FROM task t1
LEFT OUTER JOIN task t2
ON t1.userID = t2.userID
AND t2.taskID = t1.Parent_taskId
WHERE t2.taskID IS NULL
Your NOT IN will be
select t.taskID, t.Parent_taskID, t.userID
from task t
where t.Parent_taskID not in (
select tp.taskID
from task tp
where tp.userID = t.userID
)
another good (and readable) solution is to use the NOT EXISTS
select t.taskID, t.Parent_taskID, t.userID
from task t
where not exists (
select 1
from task tp
where tp.taskID = t.Parent_taskID
and tp.userID = t.userID
)
use left outer join and is null in the where statement like this:
SELECT
t.taskID,
t.Parent_taskID,
t.userID
FROM task t
LEFT OUTER JOIN task t2
ON t2.taskID = t.Parent_taskID
AND t2.userID = t.userID
WHERE ts.taskID IS NULL
You could use a not exists subquery to filter out rows with the same parent. This works if there are only two levels of tasks, and tasks cannot have grandchildren.
select *
from Table1 parent
where not exists
(
select *
from Table1 child
where parent.UserID = child.UserID
and parent.Parent_TaskID = child.TaskID
)
Live example at SQL Fiddle.