Find the missing dates - vba Sql - sql

I am trying to spot which student didn't submit his task and for what date. I want to check for every student whether current or not. I don't mind if the answer is in sql or vba code. More specification below:
Task Table
-------------------------------
SubID |ID | Task | Date
-------------------------------
1 |1 | Dance | 01-01-2014
2 |1 | Sing | 02-01-2014
3 |1 | shout | 05-01-2014
4 |2 | try | 02-01-2014
5 |3 | Okay | 01-01-2014
6 |2 | random| 06-01-2014
8 |4 |Jumping| 01-01-2014
9 |4 | try | 02-01-2014
10 |4 | Piano | 03-01-2014
11 |4 | try | 04-01-2014
12 |4 | guitar| 05-01-2014
13 |4 | try | 06-01-2014
Student table --the Date is in the dd-mm-yyyy format. - also it is a date/time datatype
ID | Name | Current
--------------------
1 | Ron | YES
2 | sqlJ | YES
3 | jque | NO
4 | holy | YES
5 | htdoc| YES
Desired Result:
Who Didn't submit their task between 01-01-2014 and 06-01-2014
ID | Date
---------------
1 | 03-01-2014
1 | 04-01-2014
1 | 06-01-2014
2 | 01-01-2014
2 | 03-01-2014
2 | 04-01-2014
2 | 05-01-2014
3 | 02-01-2014
3 | 03-01-2014
3 | 04-01-2014
3 | 05-01-2014
3 | 06-01-2014
What I have tried:
SELECT w.ID, w.Date, student.[first name], student.[last name], student.[id]
FROM tasktbl AS w
right join student
on w.id = student.[id];
//I was thinking of using a vba-for loop to iterate over the range of date and store it in an array spot every Id that doesn't have a date but it didn't work out quite well.
Any help ranging from pseudo-code to sql-code to vba-code (basically any hint towards my quest) will be appreciated

I used a calendar table which contains one row for each date. Cross joining that table with student, and restricting the date range to your 6 values gave me 30 rows (6 dates times 5 students):
SELECT sub.*
FROM
(
SELECT c.the_date, s.ID
FROM tblCalendar AS c, student AS s
WHERE c.the_date Between #1/1/2014# And #1/6/2014#
) AS sub
Then I used that as a subquery and LEFT JOINed it to tasktbl. So the rows where the "right side" is Null, are those where a student did not complete a task on the date in question.
SELECT sub.ID AS student_id, sub.the_date
FROM
(
SELECT c.the_date, s.ID
FROM tblCalendar AS c, student AS s
WHERE c.the_date Between #1/1/2014# And #1/6/2014#
) AS sub
LEFT JOIN tasktbl AS t
ON (sub.ID = t.ID) AND (sub.the_date = t.Date)
WHERE t.ID Is Null
ORDER BY sub.ID, sub.the_date;

You should be able to do this without any VBA.
select s.id, t.date
from
( --this will populate every possible date, for every student
select distinct s.id
from students s
) s
cross join
(
select distinct t.date
from taskTable t
) t
left join taskTable tt on s.id = tt.id and t.date = tt.date
where tt.date is null
--(optional) order by clause, if needed
Essentially what this is doing is pulling one entry for each possible student (from the "s" table) and one entry for each possible date (from the "t" table) and doing a cross join, which generates a result set with a row containing each possible date for each possible student. By left joining this back to the taskTable (where the taskTable's date field is null) will only return the entries from the cross join table that do not have tasks associated with them.
I would also recommend you re-think the design of the TaskTable, and instead of tracking only completed tasks, track all tasks and add a field "completed" which stores if the task has been completed (Yes) or not (No).

Related

MS Access SQL join only display extra records

I'm trying to display left over records after matching one-to-one rows. How do I display extra/left over records after joining two tables?
Suppose I have two tables, A and B. They both display the the same transactions at the end of the day. However, Table A has more detail about the records but is late in getting updated. Table B, on the other hand, has limited information about transactions but is updated several hours before Table A.
I need a query that can return which records have yet to appear in Table A from Table B.
TABLE A
+-------+-----+---------+----------+---------------------------+
| NAME | ID | AMOUNT | TYPE | PROCESSED TIMESTAMP |
+-------+-----+---------+----------+---------------------------+
| ABC | 123 | -420.07 | PURCHASE | 2018-09-06-08.26.32.000000|
| ABC | 123 | 420.07 | REFUND | 2018-09-06-07.12.18.000000|
| BBC | 456 | -5.00 | PURCHASE | 2018-09-06-10.25.13.000000|
+-------+-----+---------+----------+---------------------------+
TABLE B
+----+----------+---------------------------+
| ID | AMOUNT | RECEIVED TIMESTAMP |
+----+----------+---------------------------+
|123 | -420.07 | 2018-09-05-09.26.15.000000|
|123 | 420.07 | 2018-09-05-08.12.03.000000|
|123 | -420.07 | 2018-09-05-08.40.00.000000|
|456 | -5.00 | 2018-09-05-08.45.00.000000|
+----+----------+---------------------------+
QUERY RESULTS
+----+----------+
| ID | AMOUNT |
+----+----------+
|123 | -420.07 |
+----+----------+
I can manage to find all the records related to the ID that is "off balance" but I need only the specific records that are extra:
SELECT * FROM b
WHERE id
IN
(SELECT d.id AS id
FROM
(SELECT * FROM
(SELECT id, ROUND(SUM(amount),2) AS balance FROM a GROUP BY id) c
RIGHT JOIN
(SELECT id, ROUND(SUM(amount),2) AS balance FROM b GROUP BY id) d
ON c.id = d.id
WHERE c.balance <> d.balance))
Yields...
+----+----------+
| ID | AMOUNT |
+----+----------+
|123 | -420.07 |
|123 | 420.07 |
|123 | -420.07 |
+----+----------+
You need to read up more on joins . There are 3 basic joins which can make life much simpler.
INNER JOIN: First, this is not asked, but the query you have provided for finding off balance items is too complicated. It can be simplified by an inner join.
Inner join is a set operation which will basically get data from both tables (set) which match the condition.
select * from
(
(select id, sum(amount) from a group by id) group_A
INNER JOIN (select id, sum(amount) from b group by id) group_B
ON group_A.id = group_B.id
WHERE group_A.balance != group_B.balance
)
LEFT/RIGHT OUTER JOIN: Left outer join is an operation which will return all the data that is present in both sets and also the data that is in left set but not the right set. Right join is essential same operation on the right set. Important to notice that the extra records pulled here would be null for the side where they do not exist.
Since you want records which are present in table B but not in A, there are multiple ways of achieving this, one would be to get records present in both tables (inner join) and then get all the records in table B but not in the inner join done earlier. Using definition of group_A/group_B from the inner join example.
select id from b where id not in (
select id from group_A INNER JOIN group_B on group_A.id = group_B.id)
Or you can do a right outer join and then using the property of that fields fetched from table A would be coming as null, can filter out the required ID.
select group_B.id from group_A RIGHT OUTER JOIN group_B ON group_A.id = group_B.id
where group_A.id is null
Please use the primary keys on the joins to get the correct results as mentioned by user #ComputerVersteher
I think, you should add PK col.
I can't match data with table A and B, and can't seperate 2 rows at table B.
+----+----------+---------------------------+
| ID | AMOUNT | RECEIVED TIMESTAMP |
+----+----------+---------------------------+
|123 | -420.07 | 2018-09-05-09.26.15.000000|<-
|123 | 420.07 | 2018-09-05-08.12.03.000000|
|123 | -420.07 | 2018-09-05-08.40.00.000000|<-
|456 | -5.00 | 2018-09-05-08.45.00.000000|
+----+----------+---------------------------+
I add new col(deal_no) and made it.
https://www.db-fiddle.com/f/3GfZoQwGhBLf7YWf2RucBF/4
select tmp_B.deal_no, tmp_B.id, tmp_B.amount, tmp_A.deal_no
from tmp_B
left outer join tmp_A
on tmp_A.deal_no = tmp_B.deal_no
where tmp_A.deal_no is null

my sql join Query with have count [duplicate]

This question already has an answer here:
How to select records with duplicate just one field and all other field value?
(1 answer)
Closed 5 years ago.
I am not sure how it can be done. If have any solution please help me....
i have 2 table course and schedule now i want to show all data from course and if schedule table have any data then show it..
i have table name = schedule
--------------------------------------------------------
| course_id | room | day | time |
--------------------------------------------
| 2 | 401 |saturday| 9:00 - 13:00 |
--------------------------------------------
| 3 | 401 | sunday | 9:00 - 13:00 |
--------------------------------------------
| 2 | 402 | monday | 9:00 - 13:00 |
--------------------------------------------
| 3 | 403 | tuesday| 14:00 - 17:00|
--------------------------------------------
| 4 | 401 | tuesday| 9:00 - 13:00 |
--------------------------------------------
| 2 | 402 |wednesday|14:00 - 17:00|
--------------------------------------------
and another table name = course
----------------------------------
| id | course_code | course_name |
----------------------------------
| 2 | cse1 | cse |
---------------------------------
| 3 | eee | eee |
---------------------------------
| 4 | ct1 | ct |
---------------------------------
| 5 | ct2 | ct |
----------------------------------
| 6 | cse2 | ct |
----------------------------------
Now how to get output like this..
----------------------------------
| course code | course name | info |
|----------------------------------
|cse1|cse|401,satueday-9:00-13:00; 402,monday-9:00-13:00; 402,wednesday-14:00-17:00|
|------------------------
|cse2|cse|not assigned|
|----|---|-----------------
|eee |eee|401,sunday-9:00-13:00;403,tuesday-14:00-17:00 |
|----|---|-----------------
|ct1 |ct |401,tuesday-9:00-13:00|
|----|---|----------------
|ct2 |ct | not assigned|
|----|---|----------------
First you need to join the two tables together, then aggregate your "schedules" to a single string value grouped on the courses.
Disclaimer : code is not tested as I don't have a MySQL db anywhere near!
I am used to Oracle so I did not know which function worked in MySQL to aggregate strings so after a quick search, I found https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat-ws
SELECT c.course_code,
c.course_name,
CONCAT_WS(',',CONCAT(s.room,',',s.day,'-',s.time)) as info
FROM COURSE c
LEFT JOIN SCHEDULE s ON c.id = s.course_id
GROUP BY c.course_code,c.course_name
It should yield a result close to what you are expecting, except for the "not assigned" which will do something I can't predict depending on how WS_CONCAT handles nulls.
Explanation:
CONCAT_WS is an aggregator function, much like MAX or SUM. Only, it takes a separator as firts argument, and the columns you want to aggregate after.
If you want to display extra columns (such as course code and name) when querying aggregates, you need to specify them in your GROUP BY clause.
Also, try changing LEFT JOIN for INNER JOIN and notice the difference : courses without schedules won't be returned.
The group_concat function is your friend here (combined with coalesce).
select c.course_code,
c.course_name,
coalesce(group_concat(s.room, ', ', s.day, '-', s.time separator ';'), 'not assigned') info
from course c
left outer join schedule s on c.id = s.course_id
group by c.course_code;
One way you can do this is with the XML method:
SELECT room + ' ' + day + ' ' + time + ', ' AS 'data()'
FROM schedule
FOR XML PATH('')

sql subquery statement with a join

i am not able to think on how to build an sql statement that fits my needs, if this is just not possibl eplease let me know.
i have a table like this(ill ommit the columns not making dificulties to me)
table: servicio
------------------------------------------------------------
id | swo | date | issueValue |
1 | 15-001 | 2015-01-29 01:52:59 | 2
1 | 15-002 | 2015-01-30 01:24:00 | 2
------------------------------------------------------------
table: comments
------------------------------------------------------------
id | swo | date | Area |
1 | 15-001 | 2015-01-29 01:52:59 | 2
1 | 15-002 | 2015-01-30 01:24:00 | 1
1 | 15-002 | 2015-01-30 01:50:00 | 3
------------------------------------------------------------
i want to select the rows in servicio but include the latest Area assigned to each swo. the result should be something like this.
------------------------------------------------------------
id | swo | date | Area |
1 | 15-001 | 2015-01-29 01:52:59 | 2
1 | 15-002 | 2015-01-30 01:24:00 | 3
------------------------------------------------------------
so how can i make the sql statement check for top (1) and return the area value in it?
In SQL Server you can do this with apply, a subquery, or row_number(). Here is the first method:
select s.*, c.area
from servicio s outer apply
(select top 1 c.*
from comments c
where c.swo = s.swo
order by c.date desc
) c;
With this method, you can pull out additional columns if you like.
This is solution with Rownumber and subquery. In subquery you are selecting all rows which match between tables. Creating Ascending order for Comment table date column, Partitioning for every SWO (Partitioning means it will reset counter on every different SWO). 1 = Last date for particular SWO. In final select you just put WHERE clause with Rownum = 1
SELECT swo ,date ,issueValue ,Area
FROM
(SELECT
SRV.swo
,SRV.date
,SRV.issueValue
,CMNT.Area
,ROW_NUMBER() OVER (PARTITION BY CMNT.swo ORDER BY CMNT.date DESC) AS Dsc_Rank
FROM servicio AS SRV
LEFT JOIN comments AS CMNT
ON (SRV.swo=CMNT.swo)
) AS Temp
WHERE Temp.Dsc_Rank = 1 /* Descending Date Rank 1 = Latest date from Comment table*/
Note: for example in Teradata you can omit sub query and use Qualify clause

Column 'Course.Course_Name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause

I need to link two tables columns, please help me. This my code:
SELECT Student.Stu_Course_ID, Course.Course_Name, COUNT(Student.Stu_ID) AS NoOfStudent FROM Student
INNER JOIN Course
ON Student.Stu_Course_ID=Course.Course_ID
GROUP BY Stu_Course_ID;
This is my course table:
__________________________________________
|Course_ID | Course_Name |
|1 | B.Eng in Software Engineering |
|2 | M.Eng in Software Engineering |
|3 | BSC in Business IT |
I got number of students from student table
_____________________________
|Stu_Course_ID | NoOfStudents |
|1 | 30 |
|2 | 12 |
|3 | 20 |
This is what i want
____________________________________________________________
|Stu_Course_ID | Course_Name | NoOfStudents|
|1 | B.Eng in Software Engineering | 30 |
|2 | M.Eng in Software Engineering | 12 |
|3 | BSC in Business IT | 20 |
You need to add Course.Course_Name to your group by clause:
SELECT Student.Stu_Course_ID,
Course.Course_Name,
COUNT(Student.Stu_ID) AS NoOfStudent
FROM Student
INNER JOIN Course
ON Student.Stu_Course_ID=Course.Course_ID
GROUP BY Student.Stu_Course_ID, Course.Course_Name;
Imagine the following simple table (T):
ID | Column1 | Column2 |
----|---------+----------|
1 | A | X |
2 | A | Y |
Your query is similary to this:
SELECT ID, Column1, COUNT(*) AS Count
FROM T
GROUP BY Column1;
So, you know you have 2 records for A in column1, so you expect a count of 2, however, you are also selecting ID, there are two different values for ID where Column1 = A, so the following result:
ID | Column1 | Count |
----|---------+----------|
1 | A | 2 |
Is no more or less correct than
ID | Column1 | Count |
----|---------+----------|
2 | A | 2 |
This is why ID cannot be contained in the select list, unless it included in the group by clause, or as part of an aggregate function.
For what it's worth, if Course_ID is the primary key in the table Course then following query is legal according to the SQL Standard, and will work in Postgresql, and I suspect at some point Microsoft will build this functionality into SQL Server too:
SELECT Course.Course_ID,
Course.Course_Name,
COUNT(Student.Stu_ID) AS NoOfStudent
FROM Student
INNER JOIN Course
ON Student.Stu_Course_ID=Course.Course_ID
GROUP BY Course.Course_ID;
The reason for this is that since Course.Course_ID is the primary key of Course there can be no duplicates of this in the table, therefore there can only be one value for Course_name for each Course_ID
give columns names after group by statements which you want to retreive so you have to also give Course.Course_Name as well...

Multiple table join with condition from one table

I apologize in advance if something like this has already been discussed elsewhere, but if it has, I was unable to find it (I'm not even sure how to search such a thing). I'm trying to join two tables, "employees" and "leave." I want to list every employee from the "employees" table AND populate the report with leave data from the "leave" table where the 'leave date' (bdate column in the leave table) is greater than January 1st, 2014 (or current year). The problem is that not every employee has leave data, so I'm finding that a normal join only fetches data from those employees who actually have leave data. I think what I want is a left join, but I'm only getting records from both tables where there is actually data for that employee in both tables (hope that makes sense).
Select bunch_of_columns, leave.bdate, SUM(leave.Vhours) as TotalVacationHours, SUM(leave.shours) as TotalSickHours
from employees
left join leave on employees.id=leave.id
where employees.user_active ='1' AND leave.BDate >= '2014-01-01'
group by employees.id
Order by employees.user_last
This produces ONE record of an individual who has a leave record after "2014-01-01." I want a complete list of employee records from the employee table with available data from the leave table (and blank if there is none) where the "bdate" column in the leave table is greater than new years day.
I want this:
+-----+----------+---------------+---------------+--------------+
|ID | Name | Vacation Hrs | Sick Hrs | Date |
+-----+----------+---------------+---------------+--------------+
| 1 | Bob | 5 | 8 | 2014-01-01 |
| 2 | Lucy | NULL | NULL | NULL |
| 3 | Jerry | NULL | NULL | NULL |
| 4 | Dieter | 3 | 5 | 2014-01-08 |
| 5 | Sprockets| NULL | NULL | NULL |
+-----+----------+---------------+---------------+--------------+
Not this:
+-----+----------+---------------+---------------+--------------+
| row | Name | Vacation Hrs | Sick Hrs | Date |
+-----+----------+---------------+---------------+--------------+
| 1 | Bob | 5 | 8 | 2014-01-01 |
| 4 | Dieter | 3 | 5 | 2014-01-08 |
+-----+----------+---------------+---------------+--------------+
It's because of your WHERE condition.
leave.BDate >= '2014-01-01'
If you do a LEFT JOIN and then filter a column in the right table to something that can't be NULL, it's equivalent to doing an INNER JOIN.
If there's no leave date then the record doesn't fit the criteria. You should check instead that:
(leave.BDate >= '2014-01-01' OR leave.BDate IS NULL)
another way to write it (as pointed out by OGHaza) is apply the date condition to the JOIN portion
Select
bunch_of_columns,
leave.bdate,
COALESCE( SUM(leave.Vhours), 0 ) as TotalVacationHours,
COALESCE( SUM(leave.shours), 0 ) as TotalSickHours
from
employees
left join leave
on employees.id=leave.id
AND leave.BDate >= '2014-01-01'
where
employees.user_active ='1'
group by
employees.id
Order by
employees.user_last
Try Using Full Outer Join for such condition.
From MSDN
The full outer join or full join returns all rows from both tables, matching up the rows wherever a match can be made and placing NULLs in the places where no matching row exists.