SQL - How to avoid the joining of three queries into one - sql

I have a SQL script that selects assignments that a student has been assigned. In order to find out if the student completed his assignment, i use a sub-query. Once the student finishes an assignment he should be able to work on the next one.
I figure I can do this by selected the top 1 assignment that has not been completed ( 0 value). Which I can do with an additional query, of the first query, but then i would need a third query to join that query together. Is there a way i can achieve this selection of the top 1 assignment that has a value of 0, with 2 queries or less?
First Attempt
SELECT ag.group_id,
ag.title,
ac.collection_id,
ag.order,
ac.NAME,
ac.isactive,
(SELECT top 1 iscompleted
FROM student_completion
WHERE fk_collection_id = collection_id
AND fk_student_id like '404')
AS isCompleted,
FROM assignments AS ag
JOIN assignments_collection AS ac
ON ag.fk_collection_id = ac.collection_id
Order BY group_id
/*
SELECT TOP 1 isCompleted
(SELECT ag.group_id,
ag.title,
ac.collection_id,
ag.order,
ac.NAME,
ac.isactive,
(SELECT top 1 iscompleted
FROM student_completion
WHERE fk_collection_id = collection_id
AND fk_student_id like '404')
AS isCompleted,
FROM assignments AS ag
JOIN assignments_collection AS ac
ON ag.fk_collection_id = ac.collection_id
Order BY group_id)
Where isCompleted = 0
.........
*/
Data
+----------+--------------+---------------+-------+----------------------+----------+-------------+
| group_id | title | collection_id | order | name | isactive | isCompleted |
+----------+--------------+---------------+-------+----------------------+----------+-------------+
| 1 | Assingment_1 | 5 | 0 | Welcome to Linux | 1 | 0 |
| 2 | Assingment_2 | 6 | 0 | Installation | 1 | 0 |
| 3 | Assingment_3 | 9 | 1 | Intro to Bash | 1 | 0 |
| 3 | Assingment_4 | 3 | 1 | Intro to Bash part 2 | 1 | 0 |
+----------+--------------+---------------+-------+----------------------+----------+-------------+
Expected Data
+----------+--------------+---------------+-------+----------------------+----------+-------------+-----------+
| group_id | title | collection_id | order | name | isactive | isCompleted | available |
+----------+--------------+---------------+-------+----------------------+----------+-------------+-----------+
| 1 | Assingment_1 | 5 | 0 | Welcome to Linux | 1 | 0 | 1 |
| 2 | Assingment_2 | 6 | 0 | Installation | 1 | 0 | 0 |
| 3 | Assingment_3 | 9 | 1 | Intro to Bash | 1 | 0 | 0 |
| 3 | Assingment_4 | 3 | 1 | Intro to Bash part 2 | 1 | 0 | 0 |
+----------+--------------+---------------+-------+----------------------+----------+-------------+-----------+
student_completion
+---------------+------------------+-------------+
| FK_studentKey | FK_collectionKey | isCompleted |
+---------------+------------------+-------------+
| 404 | 5 | 1 |
+---------------+------------------+-------------+

Your question is extremely lacking in explanation. I am taking a shot in the dark here. Does this do what you want?
select d.*
, available = sc.isCompleted
from Data d
left join student_completion sc on sc.FK_colletionKey = d.collection_id

Related

How to assign duplicate increment in SQL?

While going through SQL columns, if we find text match "NEW" in Calc column, update the incrementing a count starting with 1 in Results column.
It should look like this on the output:
The following uses an id column to resolve the order issue. Replace that with your corresponding expression. This also addresses the requirement to start the display sequence with 1 and also show 0 for the 'NEW' rows.
The SQL (updated):
SELECT logs.*
, CASE WHEN text = 'NEW' THEN 0
ELSE
COALESCE(SUM(CASE WHEN text = 'NEW' THEN 1 END) OVER (PARTITION BY xrank ORDER BY id)+1, 1)
END AS display
FROM logs
ORDER BY id
The result:
+----+-------+------+---------+
| id | xrank | text | display |
+----+-------+------+---------+
| 1 | 1 | A | 1 |
| 2 | 1 | B | 1 |
| 3 | 1 | C | 1 |
| 4 | 1 | NEW | 0 |
| 5 | 1 | D | 2 |
| 6 | 1 | Q | 2 |
| 7 | 1 | B | 2 |
| 8 | 1 | NEW | 0 |
| 9 | 1 | D | 3 |
| 10 | 1 | Z | 3 |
| 11 | 2 | A | 1 |
| 12 | 2 | B | 1 |
| 13 | 2 | C | 1 |
| 14 | 2 | NEW | 0 |
| 15 | 2 | D | 2 |
| 16 | 2 | Q | 2 |
| 17 | 2 | B | 2 |
| 18 | 2 | NEW | 0 |
| 19 | 2 | D | 3 |
| 20 | 2 | Z | 3 |
+----+-------+------+---------+
You need a column that specifies the ordering for the table. With that, just use a cumulative sum:
select t.*,
1 + sum(case when Calc = 'NEW' then 1 else 0 end) over (partition by Rank_Id order by Seq) as display
from t;

Combine the data of two tables using SQL Pivot and joins for a subquery

I'm trying to create a report to find the number of users subscribed to the notification type.
I am stuck with subqueries because if these two tables
Table 1
NotificationMaster
+----+-------+
| ID | Name |
+----+-------+
| 1 | Email |
| 2 | Push |
| 3 | Call |
+----+-------+
Table 2
NotificationPreference
+------------+------------------+------------+--------------+
| ResourceID | NotificationID | IsChecked | AccountID |
+------------+------------------+------------+--------------+
| 23 | 1 | 1 1 |
| 36 | 2 | 0 2 |
| 45 | 3 | 1 3 |
| 23 | 1 | 0 1 |
| 36 | 2 | 1 2 |
| 45 | 3 | 0 3 |
| 23 | 1 | 1 1 |
| 36 | 2 | 0 3 |
| 45 | 3 | 1 3 |
+------------+------------------+--------------------------+
Expected Output
Notification Vs Resource Count
+----------+-------+------+------+
| Accountid Email | Push | Call |
+----------+-------+------+------+
| 1 | 2 | 1 | 2 |
+----------+-------+------+------+
Other Tables
AccountName
+----+-------+
| ID | Name |
+----+-------+
| 1 | Blues |
+----+-------+
| 2 | Jazz |
+----+-------+
| 3 | Rock |
+----+-------+
ResourceNames
+----------+----------------+-----------+
| Resource | Name | AccountID |
+----------+----------------+-----------+
| 23 | MJ | 1 |
| 36 | Paul | 1 |
| 45 | Jay Z | 3 |
+----------+----------------+-----------+
Progress Till Now
SELECT A.ID
,A.Name
,count(R.id) AS 'Total Resource Count'
,(SELECT count(DISTINCT np.resourceid)
FROM NotificationPreference np
INNER JOIN NotificationMaster nm ON np.notificationid = nm.id
WHERE np.accountid = A.ID
AND nm.id = 1
) AS 'Email'
FROM AccountName A
LEFT JOIN [ResourceNames] R ON A.ID = R.[AccountID]
LEFT JOIN NotificationPreference np ON np.resourceid = R.ID
GROUP BY A.ID
,A.Name
The basic pivot use conditional COUNT() :
SELECT Accountid
, COUNT( CASE WHEN nm.Name = 'Email' THEN 1 END ) as Email
, COUNT( CASE WHEN nm.Name = 'Push' THEN 1 END ) as Push
, COUNT( CASE WHEN nm.Name = 'Call' THEN 1 END ) as Call
FROM NotificationPreference np
JOIN NotificationMaster nm
ON np.NotificationID = nm.id
GROUP BY Accountid

Semi-transposing a table in Oracle

I am having trouble semi-transposing the table below based on the 'LENGTH' column. I am using an Oracle database, sample data:
+-----------+-----------+--------+------+
| PERSON_ID | PERIOD_ID | LENGTH | FLAG |
+-----------+-----------+--------+------+
| 1 | 1 | 4 | 1 |
| 1 | 2 | 3 | 0 |
| 2 | 1 | 4 | 1 |
+-----------+-----------+--------+------+
I would like to lengthen this table based on the LENGTH row; basically duplicating the row for each value in the LENGTH column.
See the desired output table below:
+-----------+-----------+--------+------+
| PERSON_ID | PERIOD_ID | NUMBER | FLAG |
+-----------+-----------+--------+------+
| 1 | 1 | 1 | 1 |
| 1 | 1 | 2 | 1 |
| 1 | 1 | 3 | 1 |
| 1 | 1 | 4 | 1 |
| 1 | 2 | 1 | 0 |
| 1 | 2 | 2 | 0 |
| 1 | 2 | 3 | 0 |
| 2 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 2 | 1 | 3 | 1 |
| 2 | 1 | 4 | 1 |
+-----------+-----------+--------+------+
I typically work in Posgres so Oracle is new to me.
I've found some solutions using the connect by statement but they seem overly complicated, particularly when compared to the simple generate_series() command from Posgres.
A recursive CTE subtracting 1 from length until 1 is reached should work. (In Postgres too, BTW, should you need something working cross platform.)
WITH cte (person_id,
period_id,
number_,
flag)
AS
(
SELECT person_id,
period_id,
length number_,
flag
FROM elbat
UNION ALL
SELECT person_id,
period_id,
number_ - 1 number_,
flag
FROM cte
WHERE number_ > 1
)
SELECT *
FROM cte
ORDER BY person_id,
period_id,
number_;
db<>fiddle

Filter out combination of fields in HAVING clause

I know this might be pretty basic but I thought it'll be much quicker here.
+----------+----------+---------+
| PersonID | No Shows | Cancels |
+----------+----------+---------+
| 1 | 2 | 0 |
| 1 | 5 | 1 |
| 1 | 0 | 0 |
| 2 | 0 | 0 |
| 2 | 0 | 0 |
| 3 | 1 | 0 |
| 3 | 0 | 0 |
| 4 | 1 | 1 |
| 4 | 3 | 2 |
| 4 | 0 | 0 |
+----------+----------+---------+
I'm trying to eliminate people which have combination of both Total of NoShows and Cancels as 0. Eg: PersonID=2
Attempt:
SELECT
PersonID
,SUM(NoShows) AS TotalNS
,SUM(Cancels) AS TotalCanc
FROM Table1
GROUP BY PersonID
HAVING SUM(NoShows) > 0 AND SUM(Cancels) > 0
This obviously won't work since it'll start removing persons if either of NoShows or Cancels is 0. Eg: PersonID=3
Desired Output:
+--------------+----------+---------+
| PersonID | No Shows | Cancels |
+--------------+----------+---------+
| 1 | 7 | 1 |
| 3 | 1 | 0 |
| 4 | 4 | 3 |
+--------------+----------+---------+
Try this out and let me know in case you face any difficulties.
SELECT
PersonID
,SUM(NoShows) AS TotalNS
,SUM(Cancels) AS TotalCanc
FROM Table1
GROUP BY PersonID
HAVING SUM(NoShows) > 0 or SUM(Cancels) > 0
Your question is:
I'm trying to obtain only those which have combination of both Total
of NoShows and Cancels as 0. Eg: PersonID=2
The answer is:
SELECT PersonID
FROM Table1
GROUP BY PersonID
HAVING SUM(NoShows) = 0 AND SUM(Cancels) = 0;
It is unclear to me why you would accept an answer with different logic.

How to create a query for items that appears only in one list

The scenario I am working on is as follows:-
A number of interviews are conducted a food poisoning case
A query called qryFoodInCase ( fldCaseID, fldFood) is generate consisting of all the food mentioned in all the interviews
An other query call qryFoodInInterview( fldCaseID, fldInterviewID, fldFood) consist of the food mentioned in each Interview
Now I am after the sql for a query that will return the food that was not consumed by an interviewee but consumed by one or more other interviewees.
The closest I've got is:
select Q1.fldCaseID,Q1.fldfood,Q2.fldInterviewID,fldGotSick
from qryFoodInCases as Q1
left join
(select * from qryFoodInInterview where qryFoodInInterview.fldInterviewID=1) as Q2
on Q1.fldFood=Q2.fldFood
where Q1.fldCaseID=1
The field Q2.fldInterviewID returns 1 for the food consumed and null for the food not consumed. However, I don't want to hard code the fldInterviewID in the sql. I would like a similar recordset returned for all the interviews in one query.
The SQL for qryFoodInCase and qryFoodInInterview are as follows:-
CREATE VIEW `qryFoodInCases`
AS
SELECT tblCases.fldCaseID
,fldfood
,count(tblFoodHistory.fldFoodID) AS fldFoodFrequency
FROM tblFood
INNER JOIN tblFoodHistory
ON tblFoodHistory.fldFoodID = tblFood.fldFoodID
INNER JOIN tblMealHistory
ON tblFoodHistory.fldMealID = tblMealHistory.fldMealHistoryID
INNER JOIN tblInterviews
ON tblInterviews.fldInterviewID = tblMealHistory.fldInterviewID
INNER JOIN tblCases
ON tblCases.fldCaseID = tblInterviews.fldCaseID
GROUP BY tblCases.fldCaseID, tblFood.fldFood
OUTPUT:
+-----------+------------+------------------+
| fldCaseID | fldFood | fldFoodFrequency |
+-----------+------------+------------------+
| 1 | Banana | 3 |
| 1 | Beans | 5 |
| 1 | Cabagge | 3 |
| 1 | Chicken | 1 |
| 1 | Pork | 5 |
| 1 | Potatoes | 1 |
| 1 | Rice | 1 |
| 1 | fried fish | 1 |
| 2 | Cabagge | 1 |
| 2 | Chicken | 2 |
| 2 | Potatoes | 1 |
| 2 | Rice | 1 |
| 2 | Salad | 1 |
+-----------+------------+------------------+
and
CREATE VIEW `qryFoodInInterview`
AS
SELECT tblInterviews.fldCaseID
,tblInterviews.fldInterviewID
,tblFood.fldFood
,tblInterviews.fldGotSick
FROM tblInterviews
INNER JOIN tblMealHistory
ON tblInterviews.fldInterviewID = tblMealHistory.fldInterviewID
INNER JOIN tblFoodHistory
ON tblFoodHistory.fldMealID = tblMealHistory.fldMealHistoryID
INNER JOIN tblFood
ON tblFood.fldFoodID = tblFoodHistory.fldFoodID
GROUP BY tblInterviews.fldInterviewID, tblFoodHistory.fldFoodID
OUTPUT
+-----------+----------------+------------+------------+
| fldCaseID | fldInterviewID | fldFood | fldGotSick |
+-----------+----------------+------------+------------+
| 1 | 1 | Pork | 0 |
| 1 | 1 | Banana | 0 |
| 1 | 1 | Rice | 0 |
| 1 | 1 | Potatoes | 0 |
| 1 | 2 | Chicken | 1 |
| 1 | 2 | Banana | 1 |
| 1 | 2 | Beans | 1 |
| 1 | 4 | Pork | 1 |
| 1 | 4 | fried fish | 1 |
| 1 | 4 | Beans | 1 |
| 2 | 6 | Salad | 0 |
| 2 | 6 | Chicken | 0 |
| 2 | 6 | Cabagge | 0 |
| 2 | 6 | Rice | 0 |
| 2 | 6 | Potatoes | 0 |
| 1 | 8 | Pork | 0 |
| 1 | 8 | Cabagge | 0 |
| 1 | 9 | Pork | 1 |
| 1 | 9 | Banana | 1 |
| 1 | 9 | Beans | 1 |
| 1 | 10 | Cabagge | 1 |
| 1 | 10 | Beans | 1 |
| 1 | 11 | Pork | 1 |
| 1 | 11 | Cabagge | 1 |
| 1 | 11 | Beans | 1 |
+-----------+----------------+------------+------------+
SQL Fiddle Demo
create a cross join off all food with all interviewrs
then a left join to see which one didnt had interview
the null mean interview didnt consume food
and exists mean someone different to interview consume food
.
SELECT F.fldFood, I.fldInterviewID, FI.fldInterviewID
FROM qryFoodInCases F, (SELECT DISTINCT fldInterviewID FROM qryFoodInInterview) I
LEFT JOIN qryFoodInInterview FI
ON F.fldFood = FI.fldFood
AND I.fldInterviewID = FI.fldInterviewID
WHERE FI.fldInterviewID IS NULL
AND EXISTS (SELECT 1
FROM qryFoodInInterview Q1
WHERE Q1.fldFood = F.fldFood
AND Q1.fldInterviewID <> I.fldInterviewID)
;
The trick was do the thing one step at the time. Once I realize I need all the combinations and create the cross join, the rest was easy.
select Q1.fldCaseID,Q1.fldfood,Q2.fldInterviewID,fldGotSick
from qryFoodInCases as Q1
left join
(select * from qryFoodInInterview QF1
JOIN qryFoodInInterview QF2
where QF1.fldInterviewID <> QF2.fldInterviewID
AND QF1.fldInterviewID > QF2.fldInterviewID
) as Q2
on Q1.fldFood=Q2.fldFood
where Q1.fldCaseID=1
As you mentioned you want to duplicate the items FoodInterviews, self JOIN will suffice your question. Please try this out , caution I didn't tested it.
Fixing Missed Self Join Condition to fix duplicates.