SQL Statement to total all employee records - sql-server

I have a sql statement that is missing all employee names.
Table employee_list contains all employees for the company.
Table apps contain the employee that is assigned to the app
Table details contains the total dollar amount for the order
My query will not group and total for employees that did not have any apps. For example employee John had 5 apps for $250, Bill had 2 apps for $75 and Henry had 0 apps for $0 (no rows in apps or details table for Henry).
My query returns:
John 5 250.00
Bill 2 75.00
I need it to return
John 5 250.00
Bill 2 75.00
Henry 0 0.00
Any ideas? Here is my current code
SELECT employee_list.Fullname,
count(apps.acntnum),
sum(details.cost)
FROM employee_list
left join apps on employee_list.Fullname=apps.EmployeeName
LEFT JOIN details ON (apps.ID=details.ObjOwner_ID AND details.Active=1)
Group BY
employee_list.Fullname

The important thing is to be using a LEFT JOIN from your employee_list table and any subsequent tables you're joining to, and to not do anything that will filter out NULLs from the right-hand tables (because the NULLs would be for the 'missing' rows).
Your query is fine, but I suspect you're using it in a wider query, where you may inadvertently have an INNER JOIN or mention one of the columns in a WHERE clause.

I agree with all the other answers, however, you could also try this....
SELECT employee_list.Fullname,
(SELECT count(apps.acntnum) FROM apps WHERE employee_list.Fullname=apps.EmployeeName) AS Cnt,
(SELECT sum(details.cost) FROM apps LEFT JOIN details ON (apps.ID=details.ObjOwner_ID AND details.Active=1) WHERE employee_list.Fullname=apps.EmployeeName) AS cost
FROM employee_list
This will always return the full list of employees, and separately go and count/sum the other values.
This answer does not take performance into account.

Related

LAG and/or LEAD alternative without window function in Pstgresql

Well I'm using Postgresql(but it won't matter if you can advise a solution in any SQL syntax), I have a table like
employee
department
salary
1
sales
30,000
2
sales
25,000
3
marketing
45,000
4
marketing
55,000
so on...
What I want to achieve is:
employee
department
salary
difference
1
sales
30,000
0/null
2
sales
25,000
5,000
3
marketing
45,000
0/null
4
marketing
55,000
10,000
So technically I want to extract the value difference of consecutive rows, however I can't use the window functions (I don't know why, but it is must to avoid in this challenge)
in a perfect world, we'd be able to do lag() or lead() functions partitioned by department name and store the value difference in other column, but I don't know how to do it without them.
I tried subqueries multiple ways, but every time I ended up having NULL or 0 in new a column
You can use a self-join to table itself and join each employee with the previous row within the same department.
SELECT t1.employee, t1.department, t1.salary, ABS(t2.salary - t1.salary) AS difference
FROM tab t1
LEFT JOIN tab t2
ON t1.department = t2.department AND t1.employee = t2.employee +1

MSAccess/SQL lookup table for match field based on sum of current table.field

I've been battling this for the last week with many attempted solutions. I want to return the unique names in table with the sum of their points and their current dance level based on that sum. Ultimately I want compare the returned dance level with what is stored in the customer table against the customer and show only the records where the two dance levels are different (the stored dance level and the calculated dance level based on the current sum of the points.
The final solution will be a web page using ADODB connection to MSAccess DB (2013). But for starters just want it to work in MSAccess.
I have a MSAccess DB (2013) with the following tables.
PointsAllocation
CustomerID Points
100 2
101 1
102 1
100 1
101 4
DanceLevel
DLevel Threshold
Beginner 2
Intermediate 4
Advanced 6
Customer
CID Firstname Dancelevel1
100 Bob Beginner
101 Mary Beginner
102 Jacqui Beginner
I want to find the current DLevel for each customer by using the SUM of their Points in the first table. I have this first...
SELECT SUM(Points), CustomerID FROM PointsAllocation GROUP BY CustomerID
Works well and gives me total points per customer. I can then INNER JOIN this to the customer table to get the persons name. Perfect.
Now I want to add the DLevel from the DanceLevel table to the results where the SUM total is used to lookup the Threshold and not exceed the value so I get the following:
(1) (2) (3) (4)
Bob 3 Beginner Intermediate
Mary 5 Beginner Advanced
Where...
(1) Customer.Firstname
(2) SUM(PointsAllocation.Points)
(3) Customer.Dancelevel1
(4) Dancelevel.DLevel
Jacqui is not shown as her SUM of Points is less than or equal to 2 giving her a calculated dance level of Beginner and this already matches the her Dancelevel1 in the Customer table.
Any ideas anyone?
You can start from the customer table because you want to list every customer. Then left join it with a subquery that calculates the dance levels and point totals. The innermost subquery totals the points and then joins on valid dance levels and selects the max threshold value from the dance levels. Then left join on the DanceLevel table again on the threshold value to get the level's description.
Select Customer.Firstname,
CustomerDanceLevels.Points,
Customer.Dancelevel1,
Dancelevel.DLevel
from Customer
left join
(select CustomerID, Points, Min(Threshold) Threshold
from
(select CustomerID, sum(Points) Points
from PointsAllocation
group by CustomerID
) PointsTotal
left join DanceLevel
on PointsTotal.Points <= DanceLevel.Threshold
group by CustomerID, Points
) CustomerDanceLevels
on Customer.CID = CustomerDanceLevels.CustomerID
left join DanceLevel
on CustomerDanceLevels.Threshold = DanceLevel.Threshold

A challenge for you SQL lovers out there

I'm new to the working world an am fresh out of varsity. i started working an am creating a few reports via SQL reporting services as part of my training.
Here is quite a challenge that I am stuck at. Please help me finish this query. Here is how it goes!
There are several employees in the employee_table that each have unique identifier known as the emp_id
There is a time_sheet table that consists of several activities AND THE HOURS FOR EACH ONE for the employees and references them via emp_id. Each activity has a TIMESHEET_DATE that corresponds to the day all the activities were submitted(once a month). There are several activities with the same date because all those activities were submitted on the same day.
And there is a leave table that references the employees via emp_id. In the leave table, there is a column for the amount of days they took off and the starting day (Leave_FROM) of the leave.
I must create a parameter where the user inputs the month (easy peasy)...
Now in the report, column 1 must have their name (easy), column 2 must have their totals hours for the specified month (HOURS) and column 3 must show how many days they took leave for that month specified.
It can be tricky, not everybody has a entry in the leavetable, but everybody has got activities in the Time_Sheet table.
Here is what I have gotten so far from a query, but its not really helping me.
Unfortunately, I cannot upload pictures, so here is a link
http://imageshack.com/a/img822/8611/5czv.jpg
Oh yea, my flavor of SQL is SQL Server
You have a few different things you need to attack here.
First is getting information from the employee_table, regardless of what is in the other two tables. To do this, I would left join on both of the tables.
Your second battle is, now since you have multiple rows in your time_sheet table, you are going to get a record for every time_sheet record. That is not what you want. You can fix this by using a SUM Aggregate and a GROUP BY clause.
Next is the issue that you are going to have when nothing exists in leave table and it is returning NULL. If you add an ISNULL(value,0) around your leave table field, it will return 0 when no records exist on that table (for that employee).
Here is what your query should look like (not exactly sure on table/column naming):
I changed the query to use temp tables, so totals are stored separately. Since the temp tables will hold 0 for employees that don't have time/leave, you can do an inner join on your final query. Check this out for more information on temp tables.
SELECT e.emp_id, ISNULL(SUM(ts.Hours),0)[Time]
INTO #TotalTime
FROM employee e
LEFT JOIN time_sheet ts ON e.emp_id = ts.emp_id
GROUP BY e.emp_id
SELECT e.emp_id, ISNULL(SUM(l.days),0) [LeaveTime]
INTO #TotalLeave
FROM employee e
LEFT JOIN leaveTable l ON e.emp_id=l.emp_id
GROUP BY e.emp_id
SELECT e.Emp_Id,Time,LeaveTime FROM Employee e
INNER JOIN #TotalTime t ON e.Emp_Id=t.Emp_Id
INNER JOIN #TotalLeave l ON e.Emp_Id=l.Emp_Id
DROP TABLE #TotalLeave,#TotalTime
Here is the SQL Fiddle
Left join the leave table, if nobody took leave you won't get any results.

SQL Server 2008 View Group By

I have created the following view in SQL Server 2008 to create mailing lists for land owners:
SELECT
dbo.parcel.featid,
CAST(mms_db.dbo.TR_Roll_Master.FMT_ROLL_NO AS decimal(11, 3)) AS Roll,
dbo.parcel.survey, mms_db.dbo.Central_Name_Database.NAME AS Owner,
mms_db.dbo.Central_Name_Database.NAME_2 AS Owner2,
mms_db.dbo.Central_Name_Database.BOX_NUM,
mms_db.dbo.Central_Name_Database.APT_NUM,
mms_db.dbo.Central_Name_Database.FMT_STREET AS House_num,
mms_db.dbo.Central_Name_Database.CITY AS Town,
mms_db.dbo.Central_Name_Database.PROV_CD AS Prov,
mms_db.dbo.Central_Name_Database.POST_CD AS Post_code,
mms_db.dbo.TR_Roll_Number_Owners.NAME_CODE
FROM
mms_db.dbo.TR_Roll_Master
INNER JOIN
dbo.parcel ON mms_db.dbo.TR_Roll_Master.ROLL_NO = dbo.parcel.roll_no COLLATE SQL_Latin1_General_CP1_CI_AS
INNER JOIN
mms_db.dbo.TR_Roll_Number_Owners ON mms_db.dbo.TR_Roll_Master.ROLL_NO = mms_db.dbo.TR_Roll_Number_Owners.ROLL_NO
INNER JOIN
mms_db.dbo.Central_Name_Database ON mms_db.dbo.TR_Roll_Number_Owners.NAME_CODE = mms_db.dbo.Central_Name_Database.NAME_CODE
WHERE
(mms_db.dbo.TR_Roll_Master.DEL_ROLL NOT LIKE '%Y%') AND
(mms_db.dbo.TR_Roll_Master.ROLL_NO NOT LIKE 'P%') OR
(mms_db.dbo.TR_Roll_Master.DEL_ROLL IS NULL) AND (mms_db.dbo.TR_Roll_Master.ROLL_NO NOT LIKE 'P%') OR
(mms_db.dbo.TR_Roll_Master.DEL_ROLL NOT LIKE '%I%') AND
(mms_db.dbo.TR_Roll_Master.ROLL_NO NOT LIKE 'P%')
The view works fine however there are often duplicates as many people own more than one piece of land. I would like to group by Name_Code to eliminate the duplicates.
When I add:
Group by mms_db.dbo.TR_Roll_Number_Owners.NAME_CODE
to the end of the query I am returned with the following response:
SQL Execution Error.
Executed SQL statement: SELECT dbo.parcel.featid, CAST(mms_db.dbo.TR_Roll_Master.FMT_ROLL_NO AS decimal(11,3)) AS Roll,
dbo.parcel.survey,
mms_db.dbo.Central_NameDatabase.Name AS Owner,
mms_db.dbo.Central_Name_Database.NAME_2 AS Owner2,
mms_db.dbo.Central_Name_Database.B...
Error Source: .Net SQLClient Data Provider
Error Message: Column 'dbo.parcel.featid' is invalid in the select list
because it is not contained in either an aggregate function or the
GROUP BY clause.
I'm not sure what I need to change to make this work.
--Edit--
As a sample data, here is a condensed sample of what I would like to achieve
Roll Owner Box_Num Town Prov Post_code Name_Code
100 John Smith 50 Somewhere MB R3W 9T7 00478
200 John Smith 50 Somewhere MB R3W 9T7 00478
300 Peter Smith 72 Somewhere MB R3W 9T9 00592
400 John Smith 90 OtherPlace MB R2R 8V7 00682
John Smith has the name code of 00478. He owns both Roll 100 & 200, Peter Smith owns 300 and another person with the name of John Smith owns 400. Based on different Name_Code values I know that the two John Smith's are different people. I would like an output that would list John Smith with Name_Code 00478 1 time only while also listing Peter Smith and the other John Smith. Name_Code is the only value I can use for grouping as the rest could represent different people with the same name.
If you just want to eliminate duplicates, just use DISTINCT and exclude the columns representing other "people on more than one piece of land" from your query viz:
SELECT DISTINCT
NAME_CODE,
{column2},
{column3},
FROM
[MyView]
However, if you wish to perform aggregation of some sort, or show one random of the "people on more than one piece of land" then you will need the GROUP BY. All non-aggregated columns in the select need to appear in the group by:
SELECT
NAME_CODE,
... Other non aggregated fields here
COUNT(featid) AS NumFeatIds,
MIN(Owner2) AS FirstOwner,
... etc (other aggregated columns)
GROUP BY
NAME_CODE,
... All non-aggregated columns in the select.
Edit
To get the table listed in your edit, you would just need to ORDER BY Name_Code
However to get just one row of John Smith #00478, you need to compromise on the non-unique columns by either eliminating them entirely, using GROUP BY and aggregates on the rows, doing a GROUP_CONCAT type hack to e.g. comma separate them, or to pivot the duplicate row columns as extra columns on the one row.
Since you've mentioned GROUP repeatedly, it seems the aggregation route is necessary. John Smith #00478 has 2 properties, hence 2 discrete Roll values. So Roll can't appear in the aggregated result. So instead, you can return e.g. a count of the Rolls, or the MIN or MAX Roll, but not both Rows*. The other columns (Address related) are probably constant for all properties (assuming John Smith 00478 has one address), but unfortunately SqlServer will require you to include them in the GROUP.
I would suggest you try:
SELECT
COUNT(Roll) AS NumPropertiesOwned,
Owner,
Box_Num,
Town,
Prov,
Post_code,
Name_Code
FROM [MyNewView]
GROUP BY
Owner, Box_Num, Town, Prov, Post_code, Name_Code
ORDER BY Name_Code;
i.e. all the non-aggregated columns must be repeated in the GROUP BY
* unless you use the GROUP_CONCAT hack or the pivot route
its telling you what to do:
"Error Message: Column 'dbo.parcel.featid' is invalid in the select list
because it is not contained in either an aggregate function or the
GROUP BY clause."
This means you have to group the other (non-aggregated) fields too.

Outputting Results from complicated database structure (SQL Server)

This will be a long question so I'll try and explain it as best as I can.
I've developed a simple reporting tool in which a number of results are stored and given a report id, these results were generated from a particular quote being used on the main system, with a huge list of these being stored in a quotes table. Here are the current batch:
REPORTS
REP_ID DESC QUOTE_ID
-----------------------------------
1 Test 1
2 Today 1
3 Last Week 2
RESULTS
RES_ID TITLE REFERENCE REP_ID
---------------------------------------------------
1 Equipment Toby 1
2 Inventory Carl 1
3 Stocks Guest 2
4 Portfolios Guest 3
QUOTE
QUOTE_ID QUOTE
------------------------------------
1 Booking a meeting room
2 Car Park Policy
3 New User Guide
So far, so good, a simple stored procedure was able to pull all the information necessary.
Now, the feature list has been upped to include categories and groups of the quotes. In the Reports table quote_id has been changed to group_id to link to the following tables.
REPORTS
- REPORT_ID
- DESC
- GROUP_ID
GROUP
- GROUP_ID
- GROUP
GROUP_CAT_JOIN
- GCJ_ID
- CAT_ID
- GROUP_ID
CATEGORIES
- CAT_ID
- CATEGORY
CAT_QUOTE_JOIN
- CQJ_ID
- CAT_ID
- QUOTE_ID
The idea of these changes is so that instead of running a report on a quote I should now write a report for a group where a group is a set of quotes for certain occasions. I should also be able to run a report on a category where a category is also a set of quotes for certain departments. The trick is that several categories can fall into one group.
To explain it further, the results table has a report_id that links to reports, reports has a group_id that links to groups, groups and categories are linked through a group_cat_join table, the same with categories and quotes through a cat_quote_join table.
In basic terms I should be able to pull all the results from either a group of quotes or a category of quotes. The query will aim to pull all the results from a certain report under either a certain category, a group or both. This puzzle has left me stumped for days now as inner joins don't appear to be working and I'm struggling to find other ways to solve the problem using SQL.
Can anyone here help me?
Here's some extra clarification.
I want to be able to return all the results within a category, but as of right now the solution below and the ones I've tried always output every solution within a description, which is not what I want.
Here's an example of the data I have in there at the moment
Results
RES_ID TITLE REFERENCE REP_ID
---------------------------------------------------
1 Equipment Toby 1
2 Inventory Carl 1
3 Stocks Guest 2
4 Portfolios Guest 3
Reports
REP_ID DESC GROUP_ID
-----------------------------------
1 Test 1
2 Today 1
3 Last Week 2
GROUP
GROUP_ID GROUP
---------------------------------
1 Standard
2 Target Week
GROUP_CAT_JOIN
GCJ_ID GROUP_ID CAT_ID
----------------------------------
1 1 1
2 1 2
3 2 3
CATEGORIES
CAT_ID CAT
-------------------------------
1 York Office
2 Glasgow Office
3 Aberdeen Office
CAT_QUOTE_JOIN
CQJ_ID CAT_ID QUOTE_ID
-----------------------------------
1 1 1
2 2 2
3 3 3
QUOTE
QUOTE_ID QUOTE
------------------------------------
1 Booking a meeting room
2 Car Park Policy
3 New User Guide
This is the test data I am using at the moment and to my knowledge it is similar to what will be run through once this is done. In all honesty I'm still trying to get my head around this structure.
The result I am looking for is if I choose to search by group I'll get everything within a group, if I choose everything inside a category I get everything just inside that category, and if I choose something from a category in a group I get everything inside that category. The problem at the moment is that whenever the group is referenced everything inside every category that's linked to the group is pulled.
The following will get the necessary rows from the results:
select
a.*
from
results a
inner join reports b on
a.rep_id = b.rep_id
and (-1 = #GroupID or
b.group_id = #GroupID)
and (-1 = #CatID or
b.cat_id = #CatID)
Note that I used -1 as the placeholder for all Groups and Categories. Obviously, use a value that makes sense to you. However, this way, you can specify a specific group_id or a specific cat_id and get the results that you want.
Additionally, if you want Group/Category/Quote details, you can always append more inner joins to get that info.
Also note that I added the Group_ID and Cat_ID conditions to the Reports table. This would be the SQL necessary if and only if you add a Cat_ID column to the Reports table. I know that your current table structure doesn't support this, but it needs to. Otherwise, as my grandfather used to say, "Boy, you can't get there from here." The issue here is that you want to limit reports by group and category, but reports only knows about group. Therefore, we need to tie something to the category from reports. Otherwise, it will never, ever, ever limit reports by category. The only thing that you can limit by both group and category is quotes. And that doesn't seem to be your requirement.
As an addendum: If you add cat_id to results instead of reports, the join condition should be:
and (-1 = #CatID or
a.cat_id = #CatID)
Is this what you are looking for?
SELECT a.*
FROM Results a
JOIN Reports b ON a.REP_Id = c.REP_Id
WHERE EXISTS (
SELECT * FROM CAT_QUOTE_JOIN c
WHERE c.QUOTE_ID = b.QUOTE_ID -- correlation to the outer query
AND c.CAT_ID = #CAT_ID -- parameterization
)
OR EXISTS (
-- note that subquery table aliases are not visible to other subqueries
-- so we can reuse the same letters
SELECT * FROM CAT_QUOTE_JOIN c, GROUP_CAT_JOIN d
WHERE c.CAT_ID = d.CAT_ID -- subquery join
AND c.QUOTE_ID = b.QUOTE_ID -- correlation to the outer query
AND d.GROUP_ID = #GROUP_ID -- parameterization
)

Resources