Condition expected - can't figure out why - sql

I have the following query but it's not working. I want to see only one studentname because dated is unique.
SELECT r.classteacher,
r.studentname,
r.reggroup,
r.yeargroup,
rrl.cdated
FROM [dbo].[Reports] AS r
LEFT OUTER JOIN reportsreadlog AS rrl
ON r.rguid = rrl.creportguid
WHERE
(
SELECT TOP(1) r.studentname
FROM [dbo].[Reports] AS r
LEFT OUTER JOIN reportsreadlog AS rrl
ON r.rguid = rrl.creportguid
WHERE r.periodguid = '4390dc5f-eb21-4673-83f0-e7f973524916'
)
AND r.periodguid = '4390dc5f-eb21-4673-83f0-e7f973524916'
GROUP BY r.classteacher,
r.studentname,
r.reggroup,
r.yeargroup,
rrl.cdated
ORDER BY r.reggroup
Current result when I fix the error:
classteacher | studentname | reggroup | yeargroup | cdate
-------------+-------------+----------+-----------+------
Teacher 1 | Student 1 | class | year | dated
Teacher 1 | Student 1 | class | year | dated
Teacher 1 | Student 1 | class | year | dated
Desired result:
classteacher | studentname | reggroup | yeargroup | cdate
-------------+-------------+----------+-----------+------
Teacher 1 | Student 1 | class | year | dated
Teacher 1 | Student 2 | class | year | dated
Teacher 1 | Student 3 | class | year | dated

Without table definitions and sample/test data, its kind of hard to answer your question. So, please update your post to add them to assist further.
Meanwhile, please try below:
SELECT distinct r.classteacher,
r.studentname,
r.reggroup,
r.yeargroup,
rrl.cdated
FROM [dbo].[Reports] AS r
LEFT OUTER JOIN reportsreadlog AS rrl
ON r.rguid = rrl.creportguid
WHERE r.periodguid = '4390dc5f-eb21-4673-83f0-e7f973524916' --Based on my understanding you don't need other subquery condition here.
-- GROUP BY r.classteacher,
-- r.studentname,
-- r.reggroup,
-- r.yeargroup,
-- rrl.cdated
ORDER BY r.reggroup

Thank you all for helping out. In the end i gave up and did it the ugly way.
Ran the query twice and combined the results
Once and rrl.cdated is null and after that and rrl.cdated is not null

Related

In a query (no editing of tables) how do I join data without any similarities?

I Have a query that finds a table, here's an example one.
Name |Age |Hair |Happy | Sad |
Jon | 15 | Black |NULL | NULL|
Kyle | 18 |Blonde |YES |NULL |
Brad | 17 | Blue |NULL |YES |
Name and age come from one table in a database, hair color comes from a second which is joined, and happy and sad come from a third table.My goal would be to make the first line of the chart like this:
Name |Age |Hair |Happy |Sad |
Jon | 15 |Black |Yes |Yes |
Basically I want to get rid of the rows under the first and get the non NULL data joined to the right. The problem is that there is no column where the Yes values are on the Jon row, so I have no idea how to get them there. Any suggestions?
PS. With the data I am using I can't just put a 'YES' in the 'Jon' row and call it a day, I would need to find the specific value from the lower rows and somehow get that value in the boxes that are NULL.
Do you just want COALESCE()?
COALESCE(Happy, 'Yes') as happy
COALESCE() replaces a NULL value with another value.
If you want to join on a NULL value work with nested selects. The inner select gets an Id for NULLs, the outer select joins
select COALESCE(x.Happy, yn_table.description) as happy, ...
from
(select
t1.Happy,
CASE WHEN t1.Happy is null THEN 1 END as happy_id
from t1 ...) x
left join yn_table
on x.xhappy_id = yn_table.id
If you apply an ORDER BY to the query, you can then select the first row relative to this order with WHERE rownum = 1. If you don't apply an ORDER BY, then the order is random.
After reading your new comment...
the sense is that in my real data the yes under the other names will be a number of a piece of equipment. I want the numbers of the equipment in one row instead of having like 8 rows with only 4 ' yes' values and the rest null.
... I come to the conclusion that this a XY problem.
You are asking about a detail you think will solve your problem, instead of explaining the problem and asking how to solve it.
If you want to store several pieces of equipment per person, you need three tables.
You need a Person table, an Article table and a junction table relating articles to persons to equip them. Let's call this table Equipment.
Person
------
PersonId (Primary Key)
Name
optional attributes like age, hair color
Article
-------
ArticleId (Primary Key)
Description
optional attributes like weight, color etc.
Equipment
---------
PersonId (Primary Key, Foreign Key to table Person)
ArticleId (Primary Key, Foreign Key to table Article)
Quantity (optional, if each person can have only one of each article, we don't need this)
Let's say we have
Person: PersonId | Name
1 | Jon
2 | Kyle
3 | Brad
Article: ArticleId | Description
1 | Hat
2 | Bottle
3 | Bag
4 | Camera
5 | Shoes
Equipment: PersonId | ArticleId | Quantity
1 | 1 | 1
1 | 4 | 1
1 | 5 | 1
2 | 3 | 2
2 | 4 | 1
Now Jon has a hat, a camera and shoes. Kyle has 2 bags and one camera. Brad has nothing.
You can query the persons and their equipment like this
SELECT
p.PersonId, p.Name, a.ArticleId, a.Description AS Equipment, e.Quantity
FROM
Person p
LEFT JOIN Equipment e
ON p.PersonId = e.PersonId
LEFT JOIN Article a
ON e.ArticleId = a.ArticleId
ORDER BY p.Name, a.Description
The result will be
PersonId | Name | ArticleId | Equipment | Quantity
---------+------+-----------+-----------+---------
3 | Brad | NULL | NULL | NULL
1 | Jon | 4 | Camera | 1
1 | Jon | 1 | Hat | 1
1 | Jon | 5 | Shoes | 1
2 | Kyle | 3 | Bag | 2
2 | Kyle | 4 | Camera | 1
See example: http://sqlfiddle.com/#!4/7e05d/2/0
Since you tagged the question with the oracle tag, you could just use NVL(), which allows you to specify a value that would replace a NULL value in the column you select from.
Assuming that you want the 1st row because it contains the smallest age:
- wrap your query inside a CTE
- in another CTE get the 1st row of the query
- in another CTE get the max values of Happy and Sad of your query (for your sample data they both are 'YES')
- cross join the last 2 CTEs.
with
cte as (
<your query here>
),
firstrow as (
select name, age, hair from cte
order by age
fetch first row only
),
maxs as (
select max(happy) happy, max(sad) sad
from cte
)
select f.*, m.*
from firstrow f cross join maxs m
You can try this:
SELECT A.Name,
A.Age,
B.Hair,
C.Happy,
C.Sad
FROM A
INNER JOIN B
ON A.Name = B.Name
INNER JOIN C
ON A.Name = B.Name
(Assuming that Name is the key columns in the 3 tables)

Creating user time report that includes zero hour weeks

I'm having a heck of a time putting together a query that I thought would be quite simple. I have a table that records total hours spent on a task and the user that reported those hours. I need to put together a query that returns how many hours a given user charged to each week of the year (including weeks where no hours were charged).
Expected Output:
|USER_ID | START_DATE | END_DATE | HOURS |
-------------------------------------------
|'JIM' | 4/28/2019 | 5/4/2019 | 6 |
|'JIM' | 5/5/2019 | 5/11/2019 | 0 |
|'JIM' | 5/12/2019 | 5/18/2019 | 16 |
I have a function that returns the start and end date of the week for each day, so I used that and joined it to the task table by date and summed up the hours. This gets me very close, but since I'm joining on date I obviously end up with NULL for the USER_ID on all zero hour rows.
Current Output:
|USER_ID | START_DATE | END_DATE | HOURS |
-------------------------------------------
|'JIM' | 4/28/2019 | 5/4/2019 | 6 |
| NULL | 5/5/2019 | 5/11/2019 | 0 |
|'JIM' | 5/12/2019 | 5/18/2019 | 16 |
I've tried a few other approaches, but each time I end up hitting the same problem. Any ideas?
Schema:
---------------------------------
| TASK_LOG |
---------------------------------
|USER_ID | DATE_ENTERED | HOURS |
-------------------------------
|'JIM' | 4/28/2019 | 6 |
|'JIM' | 5/12/2019 | 6 |
|'JIM' | 5/13/2019 | 10 |
------------------------------------
| DATE_HELPER_TABLE |
|(This is actually a function, but I|
| put it in a table to simplify) |
-------------------------------------
|DATE | START_OF_WEEK | END_OF_WEEK |
-------------------------------------
|5/3/2019 | 4/28/2019 | 5/4/2019 |
|5/4/2019 | 4/28/2019 | 5/4/2019 |
|5/5/2019 | 5/5/2019 | 5/11/2019 |
| ETC ... |
Query:
SELECT HRS.USER_ID
,DHT.START_OF_WEEK
,DHT.END_OF_WEEK
,SUM(HOURS)
FROM DATE_HELPER_TABLE DHT
LEFT JOIN (
SELECT TL.USER_ID
,TL.HOURS
,DHT2.START_OF_WEEK
,DHT2.END_OF_WEEK
FROM TASK_LOG TL
JOIN DATE_HELPER_TABLE DHT2 ON DHT2.DATE_VALUE = TL.DATE_ENTERED
WHERE TL.USER_ID = 'JIM1'
) HRS ON HRS.START_OF_WEEK = DHT.START_OF_WEEK
GROUP BY USER_ID
,DHT.START_OF_WEEK
,DHT.END_OF_WEEK
ORDER BY DHT.START_OF_WEEK
http://sqlfiddle.com/#!18/02d43/3 (note: for this sql fiddle, I converted my date helper function into a table to simplify)
Cross join the users (in question) and include them in the join condition. Use coalesce() to get 0 instead of NULL for the hours of weeks where no work was done.
SELECT u.user_id,
dht.start_of_week,
dht.end_of_week,
coalesce(sum(hrs.hours), 0)
FROM date_helper_table dht
CROSS JOIN (VALUES ('JIM1')) u (user_id)
LEFT JOIN (SELECT tl.user_id,
dht2.start_of_week,
tl.hours
FROM task_log tl
INNER JOIN date_helper_table dht2
ON dht2.date_value = tl.date_entered) hrs
ON hrs.user_id = u.user_id
AND hrs.start_of_week = dht.start_of_week
GROUP BY u.user_id,
dht.start_of_week,
dht.end_of_week
ORDER BY dht.start_of_week;
I used a VALUES clause here to list the users. If you only want to get the times for particular users you can do so too (or use any other subquery, or ...). Otherwise you can use your user table (which you didn't post, so I had to use that substitute).
However the figures that are produced by this (and your original query) look strange to me. In the fiddle your user has worked for a total of 23 hours in the task_log table. Yet your sums in the result are 24 and 80, that is way to much on its own and even worse taking into account, that 1 hour in task_log isn't even on a date listed in date_helper_table.
I suspect you get more accurate figures if you just join task_log, not that weird derived table.
SELECT u.user_id,
dht.start_of_week,
dht.end_of_week,
coalesce(sum(tl.hours), 0)
FROM date_helper_table dht
CROSS JOIN (VALUES ('JIM1')) u (user_id)
LEFT JOIN task_log tl
ON tl.user_id = u.user_id
AND tl.date_entered = dht.date_value
GROUP BY u.user_id,
dht.start_of_week,
dht.end_of_week
ORDER BY dht.start_of_week;
But maybe that's just me.
SQL Fiddle
http://sqlfiddle.com/#!18/02d43/65
Using your SQL fiddle, I simply updated the select statement to account for and convert null values. As far as I can tell, there is nothing in your post that makes this option not viable. Please let me know if this is not the case and I will update. (This is not intended to detract from sticky bit's answer, but to offer an alternative)
SELECT ISNULL(HRS.USER_ID, '') as [USER_ID]
,DHT.START_OF_WEEK
,DHT.END_OF_WEEK
,SUM(ISNULL(HOURS,0)) as [SUM]
FROM DATE_HELPER_TABLE DHT
LEFT JOIN (
SELECT TL.USER_ID
,TL.HOURS
,DHT2.START_OF_WEEK
,DHT2.END_OF_WEEK
FROM TASK_LOG TL
JOIN DATE_HELPER_TABLE DHT2 ON DHT2.DATE_VALUE = TL.DATE_ENTERED
WHERE TL.USER_ID = 'JIM1'
) HRS ON HRS.START_OF_WEEK = DHT.START_OF_WEEK
GROUP BY USER_ID
,DHT.START_OF_WEEK
,DHT.END_OF_WEEK
ORDER BY DHT.START_OF_WEEK
Create a dates table that includes all dates for the next 100 years in the first column, the week of the year, day of the month etc in the next.
Then select from that dates table and left join everything else. Do isnull function to replace nulls with zeros.

Laravel Nested Query

i'm having hard time getting the right query for this in laravel.
I have table Exams.
id | exam_name
---|------------
1 | First Exam
2 | Second Exam
3 | Third Exam
And Students score table
id | exam_id | score
----|---------|-------
1 | 1 | 15
2 | 1 | 12
3 | 1 | 10
4 | 2 | 7
5 | 2 | 16
6 | 2 | 13
And i want to get the average scores for all exams that looks like
exam_name | average_score
------------|--------------
First Exam | 12.33
Second Exam | 12
Third Exam | 0 or NULL
I have tried left join but it returns multiple rows per exam.
I can also think of using nested Query but don't know how it works in Laravel.
DB::table('exams')
->leftJoin('student_score','exam_id','=','exams.id')
->select('exam_name', DB::raw('AVG(score) as average_score'))
->groupBy('exam_name')->get();
You can get the AVG score by using above query.
Hope this helps.
Do the left join with Students table :
select e.exam_name, avg(s.score) avg_score
from Exams e
left join Students s on s.exam_id = e.id
group by e.exam_name;
You can also use subquery with correlation approach
select *,
(select avg(s.score) from Students where e.exam_id = id) avg_score
from Exams e;
You don't need to join tables at all. You can solve this with plain laravel.
// in your controller
$exams = Exam::with('students')->get();
// in your view you should replace this with blade
foreach ($exams as $exam) {
$exam->students->avg('score'); // Average score for each exam
}
Your exam model should have:
public function students()
{
return $this->hasMany(student::class);
}
and your students class must have the inverse relation:
public function exam() {
return $this->belongsTo(exam::class);
}

sql find all instances where all are part of distinct group

I'm sure a similar question to this has already been asked and answered, but I haven't been able to find anything in search, please be gentle.
I would like to know all the names of faculty members in a database who teach in every room of a building. The tables are very bare, but they are:
class:
+--------+---------+------+------+
| cname | meetsat | room | fid |
+--------+---------+------+------+
| class | 8 | R128 | 5 |
| class2 | 9 | R129 | 6 |
| class3 | 9 | R128 | 5 |
+--------+---------+------+------+
faculty:
+-----+---------------+--------+
| fid | fname | deptid |
+-----+---------------+--------+
| 5 | i.teach | 999 |
| 6 | other guy | 998 |
| 8 | another woman | 997 |
+-----+---------------+--------+
Through discussion with other users so far, I have:
(SELECT f.fname
FROM faculty f, class c
WHERE f.fid = c.fid)
UNION
(select c.fid
from class c
group by c.fid
having count(distinct room) = (select count(distinct c2.room) from class
c2));
current output:
+-----------+
| fname |
+-----------+
| i.teach |
| other guy |
+-----------+
desired output should be:
+---------+
| fname |
+---------+
| i.teach |
+---------+
I think I only need to join correctly. The course materials I have are extremely bare-bones and don't offer much in concept instruction, so I don't know who to apply them in different situations.
Here's a query that may do what you require, implementing your algorithm of comparing counts. It is an alternative to the HAVING posted by Gordon
SELECT * FROM
(SELECT count(distinct room) as countAllRooms FROM class) ar
INNER JOIN
(SELECT c.fid, count(distinct c.room) as countRoomsPerTeacher FROM class c GROUP BY c.fid) rpt
ON
rpt.countRoomsPerTeacher = ar.countAllRooms
INNER JOIN
faculty f
ON
f.fid = rpt.fid
In relation to your query on Gordon's answer, the safest way to join the faculty table:
Select * from faculty inner join
(
select c.fid
from class C
group by c.fid
having count(distinct room) = (select count(distinct c2.room) from class c2)
) ff
on ff.fid = faculty.fid
I wouldn't normally format an sql like this but I've done this deliberately to show the bits I added and which bits were Gordon's
You should avoid trying to join the he faculty table into the inner query that does the grouping as it will force you to add more columns to your select list, which forces you to add more to your group by, which breaks your counting,, better to consider Gordon's query a "faculty finder" that runs in isolation as a sub query and is joined later
You are looking for having:
select c.fid
from class c
group by c.fid
having count(distinct c.room) = (select count(distinct c2.room) from class c2);
Getting the name is just a matter of joining in the faculty table.
What about inner join or I didn’t understand your question.
Select f.name from faculty f inner join class c on f.fid=c.fid

Multiple select or distinct

I am new to sql so looking for a little help - got the first part down however I am having issues with the second part.
I got the three tables tied together. First I needed to tie tblPatient.ID = tblPatientVisit.PatientID together to eliminate dups which works
Now I need to take those results and eliminate dups in the MRN but my query is only returning one result which is WRONG - LOL
Query
select
tblPatient.id,
tblPatient.firstname,
tblPatient.lastname,
tblPatient.dob,
tblPatient.mrn,
tblPatientSmokingScreenOrder.SmokeStatus,
tblPatientVisit.VisitNo
from
tblPatient,
tblPatientSmokingScreenOrder,
tblPatientVisit
Where
tblPatient.ID = tblPatientVisit.PatientID
and tblPatientVisit.ID = tblPatientSmokingScreenOrder.VisitID
and tblPatient.ID in(
Select Distinct
tblPatient.mrn
From
tblPatient
where
isdate(DOB) = 1
and Convert(date,DOB) <'12/10/2000'
and tblPatientVisit.PatientType = 'I')
Actual Results:
ID | firstName | LastName | DOB | MRN | SmokeStatus | VisitNO
12 | Test Guy | Today | 12/12/1023 | 0015396 | Never Smoker | 0013957431
Desired Results:
90 | BOB | BUILDER | 02/24/1974 | 0015476 | Former Smoker | 0015476001
77 | DORA | EXPLORER | 06/04/1929 | 0015463 | Never Smoker | 0015463001
76 | MELODY | VALENTINE | 09/17/1954 | 0015461 | Current | 0015461001
32 | STRAWBERRY | SHORTCAKE | 07/06/1945 | 0015415 | Current | 0015415001
32 | STRAWBERRY | SHORTCAKE | 07/06/1945 | 0015415 | Never Smoker | 0015415001
32 | STRAWBERRY | SHORTCAKE | 07/06/1945 | 0015415 | Former Smoker | 0015415001
12 | Test Guy | Today | 12/12/1023 | 0015345 | Never Smoker | 0013957431
Anyone have any suggestions on how I go down to the next level and get all the rows with one unique MRN. From the data above I should have 5 in my list. Any help would be appreciated.
Thanks
If I had to guess -- The only thing that looks odd (but maybe it's OK) is that you're comparing patient.ID from your parent query to patient.mrn in the subquery.
Beyond that -- things to check:
(1)
Do you get all your patients with the inner query?
Select Distinct
tblPatient.mrn
From
tblPatient
where
isdate(DOB) = 1
and Convert(date,DOB) <'12/10/2000'
and tblPatientVisit.PatientType = 'I'
(2)
What is your patient type for the missing records? (Your filtering it to tblPatientVisit.PatientType = 'I' -- do the missing records have that patient type as well?)
Perhaps you need to invert the logic here. As in,
Select Distinct
patients.mrn1
From (select
tblPatient.id as id1,
tblPatient.firstname as firstname1,
tblPatient.lastname as lastname1,
tblPatient.dob as DOB1,
tblPatient.mrn as mrn1,
tblPatientSmokingScreenOrder.SmokeStatus as SmokeStatus1,
tblPatientVisit.VisitNo as VisitNo1,
tblPatientVisit.PatientType as PatientType1,
from
tblPatient,
tblPatientSmokingScreenOrder,
tblPatientVisit
Where
tblPatient.ID = tblPatientVisit.PatientID
and tblPatientVisit.ID = tblPatientSmokingScreenOrder.VisitID
) as patients
where
isdate(patients.DOB1) = 1
and Convert(date,patients.DOB1) <'12/10/2000'
and patients.PatientType1 = 'I');
Cleaned it up a bit and I think they were right. MRN wont match patient id, at least not from your example data. You should not need an inner query. This query should give you what you want.
SELECT DISTINCT
p.id,
p.firstname,
p.lastname,
p.dob,
p.mrn,
s.SmokeStatus,
v.VisitNo
FROM
tblPatient p
JOIN tblPatientVisit v ON p.id = v.patientId
JOIN tblPatientSmokingScreenOrder s ON v.id = s.visitId
WHERE
isdate(p.DOB) = 1
AND CONVERT(date,p.DOB) <'12/10/2000'
AND v.PatientType = 'I'