sql select where not in - sql

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.

Related

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.

Select minimal count of grouping by result without window functions

As always, want to do with one sql request. Have a table of send attempts:
ID TIMESTAMP TASK_ID
1 2019-01-30 15:29:38 1
2 2019-01-30 15:29:39 1
3 2019-01-30 15:29:40 2
4 2019-01-30 15:29:41 3
Task table:
ID EMAIL
1 boxOne#test.com
2 boxOne#test.com
3 boxTwo#test.com
Purpose is to get task ids for unique emails that has minimal count of attempts (in our case is 2 and 3). Problem number one is that i want make some tests using H2 that not supports window functions. Problem two is that several tasks can have same email.
Tried this :
SELECT TASK.id, TASK.EMAIL, count(att.TASK_ID)
FROM TASK
JOIN ATTEMPTS on TASK.id = ATTEMPTS.TASK_ID
GROUP BY ATTEMPTS.TASK_ID
and have such result:
TASK.id EMAIL count(TASK.id)
1 boxOne#test.com 2
2 boxOne#test.com 1
3 boxTwo#test.com 1
but i need minimal count for each unique email like this:
TASK.id EMAIL count(TASK.id)
2 boxOne#test.com 1
3 boxTwo#test.com 1
min(count(TASK.id)) didn't work for me result is always zero.
Can this be done without window functions or i should accept temp result and process it in my code ?
Try to use a correlated subquery, HAVING and ALL
SELECT t.id, t.email, count(a.task_ID) cnt
FROM task t
JOIN attempts a on t.id = a.task_ID
GROUP BY t.id, t.email
HAVING count(a.task_ID) <= ALL
(
SELECT count(a.task_ID)
FROM task t2
JOIN attempts a on t2.id = a.task_ID
WHERE t2.email = t.email
GROUP BY t2.id
)
DEMO
you can try by using correlated subquery
select distinct t1.* from
(
SELECT TASK.id, TASK.EMAIL, count(att.TASK_ID) cnt
FROM TASK
JOIN ATTEMPTS on TASK.id = ATTEMPTS.TASK_ID
group by TASK.id, TASK.EMAIL
) t1 where t1.cnt= (select min(cnt) from
(SELECT TASK.id, TASK.EMAIL, count(att.TASK_ID) cnt
FROM TASK
JOIN ATTEMPTS on TASK.id = ATTEMPTS.TASK_ID
group by TASK.id, TASK.EMAIL
) t2 where t2.EMAIL=t1.EMAIL)

select distinct from join

My Tables look like this
Table 1 Table 2
Users Options
id name id user_id option
------- --- -------- --------
1 Donald 1 1 access1
2 John 2 1 access2
3 Bruce 3 1 access3
4 Paul 4 2 access1
5 Ronald 5 2 access3
6 Steve 6 3 access1
Now, i want to select join these to find a user which has only access1
If i do something like
select t1.id,t1.name,t2.id,t2.user_id,t2.option
from table1 t1, table2 t2
where t1.id=t2.user_id
and option='access1';
This does not give me unique results, as in the example i need only user_id=3 my data has has these in hundreds
I also tried something like
select user_id from table2 where option='access1'
and user_id not in (select user_id from table2 where option<>'access1')
There have been other unsuccessful attempts too but i am stuck here
You can do this using a EXISTS subquery (technically, a left semijoin):
SELECT id, name
FROM table1
WHERE EXISTS(
SELECT * FROM table2
WHERE table1.id = table2.user_id
AND table2.option = 'access1'
)
If you want only users that have access1 and not any other access, add NOT EXISTS (a left anti-semi-join; there's a term to impress your colleagues!):
AND NOT EXISTS (
SELECT * FROM table2
WHERE table1.id = table2.user_id
AND table2.option <> 'access1'
)
bool_and makes it simple
with users (id,name) as ( values
(1,'donald'),
(2,'john'),
(3,'bruce'),
(4,'paul'),
(5,'ronald'),
(6,'steve')
), options (id,user_id,option) as ( values
(1,1,'access1'),
(2,1,'access2'),
(3,1,'access3'),
(4,2,'access1'),
(5,2,'access3'),
(6,3,'access1')
)
select u.id, u.name
from
users u
inner join
options o on o.user_id = u.id
group by 1, 2
having bool_and(o.option = 'access1')
;
id | name
----+-------
3 | bruce
If you want the user that has only access1, I would use aggregation:
select user_id
from table2
group by user_id
having min(option) = max(option) and min(option) = 'access1';
WITH users(id,name) AS ( VALUES
(1,'Donald'),
(2,'John'),
(3,'Bruce'),
(4,'Paul'),
(5,'Ronald'),
(6,'Steve')
), options(id,user_id,option) AS ( VALUES
(1,1,'access1'),
(2,1,'access2'),
(3,1,'access3'),
(4,2,'access1'),
(5,2,'access3'),
(6,3,'access1')
), user_access_count AS (
SELECT op.user_id,count(op.option) AS access_count
FROM options op
WHERE EXISTS(
SELECT 1 FROM options
WHERE option = 'access1'
)
GROUP BY op.user_id
)
SELECT u.id,u.name
FROM users u
INNER JOIN user_access_count uac ON uac.user_id = u.id
WHERE uac.access_count = 1;

Is there a simpler way to write this query? [MS SQL Server]

I'm wondering if there is a simpler way to accomplish my goal than what I've come up with.
I am returning a specific attribute that applies to an object. The objects go through multiple iterations and the attributes might change slightly from iteration to iteration. The iteration will only be added to the table if the attribute changes. So the most recent iteration might not be in the table.
Each attribute is uniquely identified by a combination of the Attribute ID (AttribId) and Generation ID (GenId).
Object_Table
ObjectId | AttribId | GenId
32 | 2 | 3
33 | 3 | 1
Attribute_Table
AttribId | GenId | AttribDesc
1 | 1 | Text
2 | 1 | Some Text
2 | 2 | Some Different Text
3 | 1 | Other Text
When I query on a specific object I would like it to return an exact match if possible. For example, Object ID 33 would return "Other Text".
But if there is no exact match, I would like for the most recent generation (largest Gen ID) to be returned. For example, Object ID 32 would return "Some Different Text". Since there is no Attribute ID 2 from Gen 3, it uses the description from the most recent iteration of the Attribute which is Gen ID 2.
This is what I've come up with to accomplish that goal:
SELECT attr.AttribDesc
FROM Attribute_Table AS attr
JOIN Object_Table AS obj
ON obj.AttribId = obj.AttribId
WHERE attr.GenId = (SELECT MIN(GenId)
FROM(SELECT CASE obj2.GenId
WHEN attr2.GenId THEN attr2.GenId
ELSE(SELECT MAX(attr3.GenId)
FROM Attribute_Table AS attr3
JOIN Object_Table AS obj3
ON obj3.AttribId = attr3.AttribId
WHERE obj3.AttribId = 2
)
END AS GenId
FROM Attribute_Table AS attr2
JOIN Object_Table AS obj2
ON attr2.AttribId = obj2.AttribId
WHERE obj2.AttribId = 2
) AS ListOfGens
)
Is there a simpler way to accomplish this? I feel that there should be, but I'm relatively new to SQL and can't think of anything else.
Thanks!
The following query will return the matching value, if found, otherwise use a correlated subquery to return the value with the highest GenId and matching AttribId:
SELECT obj.Object_Id,
CASE WHEN attr1.AttribDesc IS NOT NULL THEN attr1.AttribDesc ELSE attr2.AttribDesc END AS AttribDesc
FROM Object_Table AS obj
LEFT JOIN Attribute_Table AS attr1
ON attr1.AttribId = obj.AttribId AND attr1.GenId = obj.GenId
LEFT JOIN Attribute_Table AS attr2
ON attr2.AttribId = obj.AttribId AND attr2.GenId = (
SELECT max(GenId)
FROM Attribute_Table AS attr3
WHERE attr3.AttribId = obj.AttribId)
In the case where there is no matching record at all with the given AttribId, it will return NULL. If you want to get no record at all in this case, make the second JOIN an INNER JOIN rather than a LEFT JOIN.
Try this...
Incase the logic doesn't find a match for the Object_table GENID it maps it to the next highest GENID in the ON clause of the JOIN.
SELECT AttribDesc
FROM object_TABLE A
INNER JOIN Attribute_Table B
ON A.AttrbId = B.AttrbId
AND (
CASE
WHEN A.Genid <> B.Genid
THEN (
SELECT MAX(C.Genid)
FROM Attribute_Table C
WHERE A.AttrbId = C.AttrbId
)
ELSE A.Genid
END
) -- Selecting the right GENID in the join clause should do the job
= B.Genid
This should work:
with x as (
select *, row_number() over (partition by AttribId order by GenId desc) as rn
from Attribute_Table
)
select isnull(a.attribdesc, x.attribdesc)
from Object_Table o
left join Attribute_Table a
on o.AttribId = a.AttribId and o.GenId = a.GenId
left join x on o.AttribId = x.AttribId and rn = 1

MySQL join question

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;