How to Correct This SQL CODE? - sql-server

This is the question using AdvetureWorks2012.
Create a VIEW dbo.vw_Commissions to display the commissions earned last
year by all sales employees. Round the result set to two decimal places and do not include any salesperson who did not earn a commission. Include the
salesperson name, the commission earned, and the job title. Concatenate the
salesperson first and last names.
This code is not working for me. What am I screwing up?
USE AdventureWorks2012
GO
CREATE VIEW dbo.vw_Commissions
AS
SELECT
Sales.SalesPerson.SalesLastYear,
Person.Person.LastName,
Person.Person.FirstName,
HumanResources.Employee.JobTitle
FROM
Sales.SalesPerson
LEFT OUTER JOIN
Sales.SalesPerson ON Sales.SalesPerson.BusinessEntityID = Person.Person.BusinessEntityID
LEFT OUTER JOIN
Person.Person ON Person.Person.BusinessEntityID = HumanResources.Employee.BusinessEntityID

There are multiple problems with your query.
The biggest problem is it is not answering the questions asked. You are selecting SalesLastYear whereas the question asks to calculate the Commissions.
You are not filtering SalesPersons which have not earned any commission.
You need this to run only for last year.
Concatenate the FirstName and LastName.
The error you are getting is because you are using Sales.SalesPerson twice in your query. You need to give them alias names. However, I don't think you need two instances of the same table in this query. Also to use HumanResources.Employee.JobTitle column in the select list, you need to include table HumanResources.Employee in the from list.

Related

Find the Min and Max date from two tables from a sql select statement

Cant seem to wrap my head round this problem.
I have two tables one which has the following sample values:
Second table had the following values:
What i am trying to achieve is like the following:
So you can see the first table has the modules, what year and what term.
Based on these there is a start week and and end week.
The lookup table for the start and the finish unfortunatley is in a week basis and i need the begin week to match the second tables weekNo based on the season i guess and taking the start date being Sdate from that table to match what i am looking for and then the same applies to the end date.
Match the season and the endweek with the second tables WeekNo and Edate and only bring that date in.
Hope i made a bit of sense but i am hoping the third image shows what i am look for.
I've tried CTE, Group by, Partition by, order by, min, max and got nowhere :(
Dont really want to hard code anything, so was hoping you wonderful peps can help me out !!
Many thanks in advance :)
I suspect you are trying to achieve this by using one a single join between the tables - whereas what you actually need is two separate joins:
SELECT table1.module as mod_code,
table1.season as psl_code,
table2.Sdate as ypd_sdate,
table3.Edate as ypd_edate
FROM t1 as table1
JOIN t2 as table2 ON table2.yr = table1.year AND table2.season = table1.season AND table2.weekNo = table1.BeginWeek
JOIN t2 as table3 ON table3.yr = table1.year AND table3.season = table1.season AND table3.weekNo = table1.EndWeek

GROUP BY or Aggregation Function error message [duplicate]

This question already has answers here:
GROUP BY / aggregate function confusion in SQL
(5 answers)
Closed 3 years ago.
I got an error -
Column 'Employee.EmpID' is invalid in the select list because it is
not contained in either an aggregate function or the GROUP BY clause.
select loc.LocationID, emp.EmpID
from Employee as emp full join Location as loc
on emp.LocationID = loc.LocationID
group by loc.LocationID
This situation fits into the answer given by Bill Karwin.
correction for above, fits into answer by ExactaBox -
select loc.LocationID, count(emp.EmpID) -- not count(*), don't want to count nulls
from Employee as emp full join Location as loc
on emp.LocationID = loc.LocationID
group by loc.LocationID
ORIGINAL QUESTION -
For the SQL query -
select *
from Employee as emp full join Location as loc
on emp.LocationID = loc.LocationID
group by (loc.LocationID)
I don't understand why I get this error. All I want to do is join the tables and then group all the employees in a particular location together.
I think I have a partial explanation for my own question. Tell me if its ok -
To group all employees that work in the same location we have to first mention the LocationID.
Then, we cannot/do not mention each employee ID next to it. Rather, we mention the total number of employees in that location, ie we should SUM() the employees working in that location. Why do we do it the latter way, i am not sure.
So, this explains the "it is not contained in either an aggregate function" part of the error.
What is the explanation for the GROUP BY clause part of the error ?
Suppose I have the following table T:
a b
--------
1 abc
1 def
1 ghi
2 jkl
2 mno
2 pqr
And I do the following query:
SELECT a, b
FROM T
GROUP BY a
The output should have two rows, one row where a=1 and a second row where a=2.
But what should the value of b show on each of these two rows? There are three possibilities in each case, and nothing in the query makes it clear which value to choose for b in each group. It's ambiguous.
This demonstrates the single-value rule, which prohibits the undefined results you get when you run a GROUP BY query, and you include any columns in the select-list that are neither part of the grouping criteria, nor appear in aggregate functions (SUM, MIN, MAX, etc.).
Fixing it might look like this:
SELECT a, MAX(b) AS x
FROM T
GROUP BY a
Now it's clear that you want the following result:
a x
--------
1 ghi
2 pqr
Your query will work in MYSQL if you set to disable ONLY_FULL_GROUP_BY server mode (and by default It is). But in this case, you are using different RDBMS. So to make your query work, add all non-aggregated columns to your GROUP BY clause, eg
SELECT col1, col2, SUM(col3) totalSUM
FROM tableName
GROUP BY col1, col2
Non-Aggregated columns means the column is not pass into aggregated functions like SUM, MAX, COUNT, etc..
Basically, what this error is saying is that if you are going to use the GROUP BY clause, then your result is going to be a relation/table with a row for each group, so in your SELECT statement you can only "select" the column that you are grouping by and use aggregate functions on that column because the other columns will not appear in the resulting table.
"All I want to do is join the tables and then group all the employees
in a particular location together."
It sounds like what you want is for the output of the SQL statement to list every employee in the company, but first all the people in the Anaheim office, then the people in the Buffalo office, then the people in the Cleveland office (A, B, C, get it, obviously I don't know what locations you have).
In that case, lose the GROUP BY statement. All you need is ORDER BY loc.LocationID

Last Date Attended from 2 Tables SQL Server

I have searched the forum, and couldn't find an answer. So I apologize if this is out there. This seems simple in my mind, however, I can't seem to get the correct code.
I have 2 tables. STUDENT_TERMS_VIEW table holds STTR_STUDENT, STTR_TERM and TERMS table, holds the TERM_END_DATE. I need to find a way to select the student's last term based on MAX(TERM_END_DATE), but I get STTR_TERM duplicate rows per student. I need to get 1 row per student and their last term attended.
EDIT: Ok so both tables are linked by TERM.
View Code Here
As you can see, I am getting duplicate TERMS for the same student, even though I am pulling MAX(TERM_END_DATE)
SELECT * FROM
(SELECT STUDENT_TERMS_VIEW.STTR_STUDENT,
STUDENT_TERMS_VIEW.STTR_TERM,
TERMS.TERM_END_DATE
FROM STUDENT_TERMS_VIEW
JOIN STUDENT_TERMS_VIEW ON TERMS.TERMS_ID = STUDENT_TERMS_VIEW.STTR_TERM
ORDER BY TERMS.TERM_END_DATE DESC,STUDENT_TERMS_VIEW.STTR_STUDENT)
GROUP BY STUDENT_TERMS_VIEW.STTR_STUDENT
Your query is getting the max of the combination of (STTR_STUDENT and STTR_TERM). If you only want to get the max term of each student, you should only GROUP BY STUDENT_TERMS_VIEW.STTR_STUDENT. Try the query below.
SELECT stv.STTR_STUDENT, MAX(t.TERM_END_DATE)
FROM STUDENT_TERMS_VIEW stv
JOIN TERMS t ON t.TERMS_ID = stv.STTR_TERM
GROUP BY stv.STTR_STUDENT
If you also need to get the term, join it back to STUDENT_TERMS_VIEW and TERMS.
SELECT s.STTR_STUDENT, s.STTR_TERM, t.TERM_END_DATE
FROM (
SELECT stv.STTR_STUDENT, MAX(t.TERM_END_DATE) AS 'MaxDate'
FROM STUDENT_TERMS_VIEW stv
JOIN TERMS t ON t.TERMS_ID = stv.STTR_TERM
GROUP BY stv.STTR_STUDENT
) a
JOIN STUDENT_TERMS_VIEW s ON s.STTR_STUDENT = a.STTR_STUDENT
JOIN TERMS t ON t.TERMS_ID = s.STTR_TERM AND t.TERM_END_DATE = a.TERM_END_DATE

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.

How can I convert a view containing a START WITH...CONNECT BY sub-query to SQL Server?

I am trying to convert a view from an Oracle RDBMS to SQL Server. The view looks like:
create or replace view user_part_v
as
select part_region.part_id, users.id as users_id
from part_region, users
where part_region.region_id in(select region_id
from region_relation
start with region_id = users.region_id
connect by parent_region_id = prior region_id)
Having read about recursive CTE's and also about their use in sub-queries, my best guess at translating the above into SQL Server syntax is:
create view user_part_v
as
with region_structure(region_id, parent_region_id) as (
select region_id
, parent_region_id
from region_relation
where parent_region_id = users.region_id
union all
select r.region_id
, r.parent_region_id
from region_relation r
join region_structure rs on rs.parent_region_id = r.region_id
)
select part_region.part_id, users.id as users_id
from part_region, users
where part_region.region_id in(select region_id from region_structure)
Obviously this gives me an error about the reference to users.region_id in the CTE definition.
How can I achieve the same result in SQL Server as I get from the Oracle view?
Background
I am working on the conversion of a system from running on an Oracle 11g RDMS to SQL Server 2008. This system is a relatively large Java EE based system, using JPA (Hibernate) to query from the database.
Many of the queries use the above mentioned view to restrict the results returned to those appropriate for the current user. If I cannot convert the view directly then the conversion will be much harder as I will need to change all of the places where we query the database to achieve the same result.
The tables referenced by this view have a structure similar to:
USERS
ID
REGION_ID
REGION
ID
NAME
REGION_RELATIONSHIP
PARENT_REGION_ID
REGION_ID
PART
ID
PARTNO
DESCRIPTION
PART_REGION
PART_ID
REGION_ID
So, we have regions, arranged into a hierarchy. A user may be assigned to a region. A part may be assigned to many regions. A user may only see the parts assigned to their region. The regions reference various geographic regions:
World
Europe
Germany
France
...
North America
Canada
USA
New York
...
If a part, #123, is assigned to the region USA, and the user is assigned to the region New York, then the user should be able to see that part.
UPDATE: I was able to work around the error by creating a separate view that contained the necessary data, and then have my main view join to this view. This has the system working, but I have not yet done thorough correctness or performance testing yet. I am still open to suggestions for better solutions.
I reformatted your original query to make it easier for me to read.
create or replace view user_part_v
as
select part_region.part_id, users.id as users_id
from part_region, users
where part_region.region_id in(
select region_id
from region_relation
start with region_id = users.region_id
connect by parent_region_id = prior region_id
);
Let's examine what's going on in this query.
select part_region.part_id, users.id as users_id
from part_region, users
This is an old-style join where the tables are cartesian joined and then the results are reduced by the subsequent where clause(s).
where part_region.region_id in(
select region_id
from region_relation
start with region_id = users.region_id
connect by parent_region_id = prior region_id
);
The sub-query that's using the connect by statement is using the region_id from the users table in outer query to define the starting point for the recursion.
Then the in clause checks to see if the region_id for the part_region is found in the results of the recursive query.
This recursion follows the parent-child linkages given in the region_relation table.
So the combination of doing an in clause with a sub-query that references the parent and the old-style join means that you have to consider what the query is meant to accomplish and approach it from that direction (rather than just a tweaked re-arrangement of the old query) to be able to translate it into a single recursive CTE.
This query also will return multiple rows if the part is assigned to multiple regions along the same branch of the region heirarchy. e.g. if the part is assigned to both North America and USA a user assigned to New York will get two rows returned for their users_id with the same part_id number.
Given the Oracle view and the background you gave of what the view is supposed to do, I think what you're looking for is something more like this:
create view user_part_v
as
with user_regions(users_id, region_id, parent_region_id) as (
select u.users_id, u.region_id, rr.parent_region_id
from users u
left join region_relation rr on u.region_id = rr.region_id
union all
select ur.users_id, rr.region_id, rr.parent_region_id
from user_regions ur
inner join region_relation rr on ur.parent_region_id = rr.region_id
)
select pr.part_id, ur.users_id
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;
Note that I've added the users_id to the output of the recursive CTE, and then just done a simple inner join of the part_region table and the CTE results.
Let me break down the query for you.
select u.users_id, u.region_id, rr.parent_region_id
from users u
left join region_relation rr on u.region_id = rr.region_id
This is the starting set for our recursion. We're taking the region_relation table and joining it against the users table, to get the starting point for the recursion for every user. That starting point being the region the user is assigned to along with the parent_region_id for that region. A left join is done here and the region_id is pulled from the user table in case the user is assigned to a top-most region (which means there won't be an entry in the region_relation table for that region).
select ur.users_id, rr.region_id, rr.parent_region_id
from user_regions ur
inner join region_relation rr on ur.parent_region_id = rr.region_id
This is the recursive part of the CTE. We take the existing results for each user, then add rows for each user for the parent regions of the existing set. This recursion happens until we run out of parents. (i.e. we hit rows that have no entries for their region_id in the region_relationship table.)
select pr.part_id, ur.users_id
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;
This is the part where we grab our final result set. Assuming (as I do from your description) that each region has only one parent (which would mean that there's only one row in region_relationship for each region_id), a simple join will return all the users that should be able to view the part based on the part's region_id. This is because there is exactly one row returned per user for the user's assigned region, and one row per user for each parent region up to the heirarchy root.
NOTE:
Both the original query and this one do have a limitation that I want to make sure you are aware of. If the part is assigned to a region that is lower in the heirarchy than the user (i.e. a region that is a descendent of the user's region like the part being assigned to New York and the user to USA instead of the other way around), the user won't see that part. The part has to be assigned to either the user's assigned region, or one higher in the region heirarchy.
Another thing is that this query still exhibits the case I mentioned above about the original query, where if a part is assigned to multiple regions along the same branch of the heirarchy that multiple rows will be returned for the same combination of users_id and part_id. I did this because I wasn't sure if you wanted that behavior changed or not.
If this is actually an issue and you want to eliminate the duplicates, then you can replace the query below the CTE with this one:
select p.part_id, u.users_id
from part p
cross join users u
where exists (
select 1
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;
where pr.part_id = p.part_id
and ur.users_id = u.users_id
);
This does a cartesian join between the part table and the users table and then only returns rows where the combination of the two has at least one row in the results of the subquery, which are the results that we are trying to de-duplicate.

Resources