Sum functions doesn't show total values in single row after grouping - sql

I have to showcase the total hours worked for each employee that worked on different dates.
I can use group by and sum function but I have multiple columns to showcase and it gives the same result sum with multiple rows. Additionally, I don't want to write every column name in group by clause as its not proper way of query writing.
I managed to get employees working hours from different tables using joins but couldn't sum up them in single row as a record. However it shows sum in multiple rows.
The script I used to get all the data is given below:
Select e.employeecode, Isnull(e.PreferredName,e.FirstNames) + '
' + e.LastName as [Name], w.Position
, sum(h.TotalHours) over (partition by e.employeecode order by e.employeecode)[TotalHours]
, e.Salary, e.startdate
From employee e
Left join Appointment ap on ap.EmployeeCode = e.EmployeeCode
left join Work w on w.WorkCode = ap.workCode
left join HistoricalAllowance h on h.EmployeeCode = e.EmployeeCode
left join TransPerPaySequence TP on tp.PaySequence = h.PaySequence
Where tp.PeriodEnd between '2021-04-24' and '2021-05-07'
and h.AllowanceCode in ('99','1000') and
and ap.isactive ='1'
group by e.EmployeeCode
It looks right now after writing a script:
employeecode
Name
Position
TotalHours
Salary
StartDate
1234
Anna
employee-Com
63.45
500
40792
1234
Anna
employee-Com
486.45
500
40792
2345
Jacky
Manager
126.9
700
41395
2345
Jacky
Manager
961.05
700
41395
2345
Jacky
Manager
67.05
700
41395
3456
Mahato
HR
402.3
570
41933
3456
Mahato
HR
67.05
570
41933
3456
Mahato
HR
126.9
570
41933
3456
Mahato
HR
126.9
570
41933
The way I wanted
employeecode
Name
Position
TotalHours
Salary
StartDate
1234
Anna
employee-Com
549.9
500
40792
2345
Jacky
Manager
1155
700
41395
3456
Mahato
HR
723.15
570
41933
Please help and let me know what I am missing.
Thanks In advance

I can appreciate not wanting to list all the columns for the GROUP BY . . . but for a slightly different reason: If you can avoid the aggregation then the query is likely to be faster.
And SQL Server offers a convenient solution, using OUTER APPLY:
select e.employeecode,
coalesce(e.PreferredName, e.FirstNames) + ' ' + e.LastName as Name,
w.Position,
h.TotalHoursTotalHours, e.Salary, e.startdate
From employee e Left join
Appointment ap
on ap.EmployeeCode = e.EmployeeCode left join
Work w
on w.WorkCode = ap.workCode outer apply
(select sum(h.TotalHours) as TotalHours
from HistoricalAllowance h join
TransPerPaySequence TP
on tp.PaySequence = h.PaySequence
where h.EmployeeCode = e.EmployeeCode and
tp.PeriodEnd between '2021-04-24' and '2021-05-07' and
h.AllowanceCode in ('99','1000')
) h
where ap.isactive = '1';

Remove the OVER(...) clause and then put every other (non SUMmed) column mentioned in the SELECT, into the GROUP BY also
Adhering to your it's not proper way of query writing rule is tripping you up on this one
ps, I've a doubt that the posted query in your question even runs - SQL server is not, to my knowledge, one of the few databases that will allow you to limit group by columns to just those on which all other selected, non aggregated columns are functionally dependent. Your query as posted should give a "column x in the select list is invalid because it is not an aggregate/in the group by" error

Related

Need Simple SQL direction

I'm trying to write a Ado SQL statement for my Access table and I'm getting the wrong results.
Employee Table
ID Name DriverID
1 Alex 1
2 Tom 2
3 Trevor 3
4 PHIL 0
5 Gina 4
Vehicle Table
ID PLATE EMPLOYEEID INSERVICE
1 123XYZ 1 N
2 456GFR 2 Y
3 TFV4FG 3 Y
4 F6GK7D 4 Y
5 GEY7GH 1 Y
I want result of All employes and to display the Vehcicle info if they are assigned to it.
Result should be
Name Plate
Alex GEY7GH
Tom 456GFR
Trevor TFV4FG
PHIL
Gina F6GK7D
SELECT Employee.ID, Employee.FirstName, Vehicles.Plate, Vehicles.InService
FROM Employee LEFT JOIN Vehicles ON Employee.ID = Vehicles.DriverID
WHERE (((Vehicles.InService)=True));
Does not display PHIL who is not assigned to a vehicle.
Just add the condition inside the join, making sure to use parentheses to avoid problems when joining with constants or anything but simple equals:
SELECT Employee.ID, Employee.FirstName, Vehicles.Plate, Vehicles.InService
FROM Employee LEFT JOIN Vehicles ON (Employee.ID = Vehicles.DriverID AND Vehicles.InService = True)
From the above tables, it looks like the DriverID Column in employee table aligns with the EmployeeID column in the vehicles table and the issue is the on clause in the join.
SELECT
Employee.ID
,Employee.FirstName
,Vehicles.Plate
,Vehicles.InService
FROM
Employee
LEFT JOIN Vehicles ON Employee.DRIVERID = Vehicles.EMPLOYEEID
WHERE
(((Vehicles.InService)=True));
Well, in a normal database, you would move the WHERE condition to the ON clause. But I don't think that MS Access supports this:
SELECT e.ID, e.FirstName, v.Plate, v.InService
FROM Employee as e LEFT JOIN
Vehicles as v
ON e.ID = v.DriverID AND v.InService = True;
An alternative is a subquery:
SELECT e.ID, e.FirstName, v.Plate, v.InService
FROM Employee as e LEFT JOIN
(SELECT v.*
FROM Vehicles as v
WHERE v.InService = True
) as v
ON e.ID = v.DriverID ;

Oracle, LEFT OUTER JOIN not returning all rows from left table, instead behaving like INNER JOIN

I'm doing a left outer join and only getting back matching rows like it was an inner join.
To simplify the data, my first table(ROW_SEG), or left table looks something like this:
ASN | DEPT NO
-----------------------
85 | 836
86 | null
87 | null
My second table(RF_MERCHANT_ORG) has DEPT_NAME, and some other things which i want to get when i have a dept number.
DEPT NO | DEPT_NAME
-----------------------
836 | some dept name 1
837 | some dept name 2
838 | some dept name 3
In this case after my join i'd only get 1 row, for ASN 85 that had a DEPT NO.
...omitting a bunch of SQL for simplicity
, ROW_SEG AS (
SELECT *
FROM VE_SI_EC_OI
WHERE ROW_NUM BETWEEN 1 AND 1000 -- screen pagination, hardcoding values
)
-- ROW_SEG has a count of 1000 now
, RFS_JOIN AS (
SELECT ROW_SEG.*
,MO.BYR_NO
,MO.BYR_NAME
,MO.DEPT_NAME
FROM ROW_SEG
LEFT OUTER JOIN RF_MERCHANT_ORG MO
ON ROW_SEG.DEPT_NO = MO.DEPT_NO
WHERE MO.ORG_NO = 100
)
SELECT * FROM RFS_JOIN; -- returns less than 1000
I only get back the number of rows equal to the number of rows that have dept nos. So in my little data example above i would only get 1 row for ASN 85, but i want all rows with BYR_NO, BYR_NAME, AND DEPT_NAME populated on rows where i had a DEPT_NO, and if not, then empty/null columns.
If ORG_NO is within the RF_MERCHANT_ORG table (using aliases consistently would help there) then acting like an inner join would then would be the correct result for the SQL being used.
The join should be this to make it act like a proper left join:
LEFT OUTER JOIN RF_MERCHANT_ORG MO ON ROW_SEG.DEPT_NO = MO.DEPT_NO AND MO.ORG_NO = 100
If ORG_NO is in RF_MERCHANGE_ORG, then that is likely to be the cause... the where condition is limiting the result set.

Using data from tables in a group by

names:
id, first, last
879 Scotty Anderson
549 Melvin Anderson
554 Freddy Appleton
321 Grace Appleton
112 Milton Appleton
189 Jackson Black
99 Elizabeth Black
298 Jordan Frey
parents:
id, student_id
549 879
321 554
112 554
99 189
298 189
Expected Output
(without the 'Student:' / 'Parent:')
Student: Anderson, Scotty
Parent: Anderson, Melvin
Student: Appleton, Freddy
Parent: Appleton, Grace
Parent: Appleton, Milton
Student: Black, Jackson
Parent: Black, Elizabeth
Parent: Frey, Jordan
Using the data above, how can I achieve the expected output?
I currently use SQL similar to this to get a list of current students and names.
select b.last, b.first
from term a, names b
where a.id = b.id(+)
order by b.last
Which returns:
Anderson, Scotty
Appleton, Freddy
Black, Jackson
My question is how to take the parents table and add to this query so it has this output:
Anderson, Scotty
Anderson, Melvin
Appleton, Freddy
Appleton, Grace
Appleton, Milton
Black, Jackson
Black, Elizabeth
Frey, Jordan
The idea in a query like this is to break the data down into something that helps you solve the problem, and then put it back together as needed. In this case I'm going to make use of common table expressions, which allows me to treat queries as tables and then recombine them handily.
Looking at the desired results it looks like we want to have the students appear first, followed by their mothers (ladies first :-), and then their fathers. So, OK, let's figure out how to extract the needed data. We can get the students and their associated data pretty simply:
select distinct p.student_id as student_id,
n.first,
n.last,
0 as type
from parents p
inner join names n
on n.id = p.student_id
The type column, with its constant value of zero, is just used to identify that this is a student. (You'll see why in a minute).
Now, getting the mother's is a bit more difficult because we don't have any gender information to use. However, we'll use what we have, which is names. We know that names like Melvin, Milton, and Jordan are "guy" names. (Yes, I know Jordan can be a girls name too. My daughter has a male coach named Jordan, and a female teammate named Jordan. Just go with it - for purposes of argument in this case Jordan is a guys name, 'K? 'K :-). So we'll use that information to help us identify the mom's:
select p.student_id, n.first, n.last, 1 as type
from parents p
inner join names n
on n.id = p.id
where first not in ('Melvin', 'Milton', 'Jordan')
Notice here that we assign the value of 1 to the type column for mothers.
Similarly, we'll find the dads:
select p.student_id, n.first, n.last, 2 as type
from parents p
inner join names n
on n.id = p.id
where first in ('Melvin', 'Milton', 'Jordan')
And here we assign a value of 2 for the type.
OK - given the above we just need to combine the data properly. We don't want to use a JOIN, however, because we want the names to get spit out one after the other from the query - and the way we do THAT in SQL is with the UNION or UNION ALL operator. (Generally, you're going to want to use UNION ALL, because UNION will check the result set to ensure there are no duplicates - which in the case of a large result set takes, oh, more or less FOREVER!). And so, the final query looks like:
with all_students as (select distinct p.student_id as student_id,
n.first,
n.last,
0 as type
from parents p
inner join names n
on n.id = p.student_id),
all_mothers as (select p.student_id, n.first, n.last, 1 as type
from parents p
inner join names n
on n.id = p.id
where first not in ('Melvin', 'Milton', 'Jordan')),
all_fathers as (select p.student_id, n.first, n.last, 2 as type
from parents p
inner join names n
on n.id = p.id
where first in ('Melvin', 'Milton', 'Jordan'))
select last || ', ' || first as name from
(select * from all_students
union all
select * from all_mothers
union all
select * from all_fathers)
order by student_id desc, type;
We just take the student data, followed by the mom data, followed by the dad data, then sort it by the student ID from highest to lowest (I just looked at the desired results to figure out that this should be a descending sort), and then by the type (which results in the student (type=0) being first, following by their mother (type=1) and then their father (type=2)).
SQLFiddle here
Share and enjoy.
generic SQL, mmmmm I'd like there to be A generic SQL :)
First off you want to stop using the antique (+) join syntax that is exclusive to Oracle
select b.last, b.first
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
That is way more generic! (nb: You can abbreviate to just LEFT JOIN)
Now to concatenate (Last Name comma space First Name) there are options some not generic
SQL Server/MySQL and others supporting CONCAT()
select CONCAT(b.last , ', ', b.first)
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
not all versions of Oracle or SQL Server support CONCAT()
Oracle's concat() only takes 2 parameters; grrrrr
ORACLE
select b.last || ', ' || b.first
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
In this form Oracle generally handles data type conversions automatically (I think, please check on date/timestamps maybe others)
TSQL (Sybase, MS SQL Server)
select b.last + ', ' + b.first
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
In this form you must explicitly cast/convert data types to n|var|char for concatenation if not already a string type
For your list of concatenated names:
You need in addition to the last name a method to retain the family group together, plus distinguish between student and parent. As you want just one column of names this indicates you need a column of id's that point to the last and first names. So making some assumptions about the table TERM my guess is you list the students from that, then append the parents that relate to that group of students, and finally to output the required list in the required order.
select
case when type = 1 then 'Student' else 'Parent' end as who
, names.last || ', ' || names.first as Name
from (
select
STUDENT_ID as name_id
, STUDENT_ID as family_id
, 1 as TYPE
from term
union all
select
PARENTS.ID as name_id
, PARENTS.STUDENT_ID as family_id
, 2 as TYPE
from PARENTS
inner join term on PARENTS.STUDENT_ID = term.STUDENT_ID
) sq
inner join NAMES ON sq.name_id = NAMES.ID
order by
names.last
, sq.family_id
, sq.type
see: http://sqlfiddle.com/#!4/01804/6
This is too long for a comment.
Your question doesn't make sense. The easy answer to the question is:
select last, first
from names;
But it seems unlikely that is what you want.
Your sample query mentions a table term. That is not mentioned elsewhere in the question. Please clarify the question or delete this one and ask another.
I think I see what you're trying to do. I think you could set up a derived table and then query it. Set up something like: case when student id= id then 1 else 0 as match or whatever. Then query your derived table and group by match.
I would do it like that in SQL:
Select last +', '+ first as fullname from names;

SQL query get course number for certain student grades

I'm working through some problems and I can't seem to get the expected results for this one. The question is below with what is in my code right now and also the expected results. If anyone help that would be great. I'm just trying to get a understanding on this and can't seem to get my head around what exactly this is asking as you can see my code I have now isn't close to what the expected result is as of right now. Also I added the schema this will show whats in what table if needed for your guys help.
Question:
List the course number of courses wherein students have received grades for every one of the possible defined grade types. Order by course number.
My code so far:
SELECT g.Student_id, g.Grade_type_code
FROM Grade g LEFT OUTER JOIN Section s
ON g.Section_id = s.Section_id
GROUP BY g.Student_id, g.Grade_type_code
ORDER BY g.Student_id;
Any help would be great, also here is the Schema.
DBMS: I'm using Oracle SQL Developer
Here is the Expected Result
COURSE_NO
----------
20
25
100
120
122
125
130
135
Note: The Chapter for this problem is based off using
LEFT OUTER JOIN
My Current results
STUDENT_ID GRADE_TYPE_CODE
---------- ---------------
102 FI
102 HM
102 MT
102 PA
102 QZ
103 FI
103 HM
103 MT
103 PA
103 QZ
104 FI
104 HM
Based on your ER diagram I believe this query should return a list of courses whose enrolled students have collectively received all of the grade types listed in the GRADE_TYPE table.
select s.course_no,
c.descr,
count(distinct g.grade_type_code) as num_grade_types
from grade g
join enrollment e
on g.student_id = e.student_id
and g.section_id = e.section_id
join section s
on e.section_id = s.section_id
join course c
on s.course_no = c.course_no
group by s.course_no, c.descr
having count(distinct g.grade_type_code) = (select count(grade_type_code)
from grade_type)
I didn't notice your expected result was only the course # (you can just get rid of the columns you don't want from the select list). Also the join to the COURSE table is only there to get the course description, so if you don't want the course description selected, you do not need that join.
You need to select COURSE_NO instead. And also use JOIN and not LEFT JOUTER JOIN.
Something like this:
select COURSE_NO from
(
SELECT distinct (s.COURSE_NO)
FROM Grade g JOIN Section s
ON g.Section_id = s.Section_id
)
ORDER BY s.COURSE_NO;

How to find out employees having same department but different shifts

I have a table employee which has different attributes like emp_code, naeme,...., deptt.
There is another table called nightShift which has fields- emp_code, shift_time.
Any employee which is not in nightShift table is automatically assumed to be in day shift.
Now I have to find out those deptt which has some employees working in night shift and some in normal shift
What can be a query for this.
Example
**Employees**
----------------------------------------
emp_code| Name | deptt
----------------------------------------
e1 John Ops
e2 Martin Ops
e3 Gary Infra
e4 John Facilities
e5 Michael Ops
e6 Alan Ops
e7 Tony Facilites
e8 Alex Infra
e9 Peter Infra
e10 Ron Ops
**nightShift**
----------------------------------------
emp_code | shift_time
----------------------------------------
e1 shiftA
e2 shiftA
e5 shiftB
e4 shiftB
e7 shiftC
Now in the output, I want only Deptt Ops, as some of its employees are in night shift(e1,e2,e5) and some in normal shift(e6,e10)
The output should NOT contain Infra as all employees(e3,e8,e9) are in normal shift and none in night shift.
The output should NOT contain Facilities as all employees(e4,e7) are in night shift and none in normal shift.
Can somebody help me with this?
Here is a group by version - join both tables in a left join and count nightShifts per department. If count is greater than zero but not equal to count of all workers in department, we have a match.
select employees.deptt
from employees
left join nightShift
on employees.emp_code = nightShift.emp_code
group by employees.deptt
having count (nightShift.emp_code) > 0
and count (employees.emp_code) <> count (nightShift.emp_code)
Test it on Sql Fiddle.
You could try something along these lines
select
distinct e.deptt
from
Employees e
inner join
NightShift n
on
n.emp_code = e.emp_code
Where
e.deptt not in ('Facilities', 'Facilites')
The inner join will eliminate everyone not working night shifts, then in the where we find any results not working in deptt Facilities
I think you need to count the number of night shift workers for each department, and the number of day shift workers for each department, and you're concerned with those departments where both counts are bigger than zero.
Stage 1: Night shift workers per department:
SELECT e.deptt, COUNT(*) AS headcount
FROM Employees AS e
JOIN NightShift AS n
ON n.emp_code = e.emp_code
GROUP BY e.deptt
Stage 2: Day shift workers per department
There are various possible strategies for this. One is to count the total workers for each department and subtract the number of nightshift workers:
SELECT d.deptt, (d.headcount - n.headcount) AS headcount
FROM (SELECT e2.deptt, COUNT(*) AS headcount
FROM Employees AS e2
GROUP BY e2.deptt
) AS d
JOIN (SELECT e.deptt, COUNT(*) AS headcount
FROM Employees AS e
JOIN NightShift AS n
ON n.emp_code = e.emp_code
GROUP BY e.deptt
) AS n
ON d.deptt = e.deptt
Stage 3: Pick those departments where the headcounts on the dayshift and night shift are both non-zero:
SELECT d.deptt, (d.headcount - n.headcount) AS dayshift, n.headcount AS nightshift
FROM (SELECT e2.deptt, COUNT(*) AS headcount
FROM Employees AS e2
GROUP BY e2.deptt
) AS d
JOIN (SELECT e.deptt, COUNT(*) AS headcount
FROM Employees AS e
JOIN NightShift AS n
ON n.emp_code = e.emp_code
GROUP BY e.deptt
) AS n
ON d.deptt = e.deptt
WHERE d.headcount > 0
AND n.headcount > 0
There might be a more succinct formulation, but I'm fairly sure this would give the correct answer.
Subject to the note that this has not been anywhere near an actual SQL DBMS so there could be some syntax errors in it.
I'm also assuming you're using a supported version of Informix (and not Informix OnLine or Informix SE). Some older versions of Informix would not support all this syntax but I believe all the 11.x versions (which are all currently supported) should all handle these queries.
You can probably simplify this along the lines of Nikola Markovinović's answer.