sql server query results based on another resultset - sql-server

My requirement is like below.
InstituteId is passed as parameter to my query, i need to get all the studentId's who are enrolled in any of the courses the institute is offering and they should be full time students..
There are chances that few users have registered but they are not enrolled in any courses, few users have enrolled on more than 1 course and in this case i need to display this user only once.
SELECT DISTINCT s.StudentId, f.[RegistrationId] from dbo.[Student] s
INNER JOIN dbo.FulltimeStudent f on s.StudentId = f.StundentId
WHERE CourseId in (
SELECT CourseId from dbo.[Institute] WHERE InstituteId = #InstututeId
)
I am seeing some performance lag with the above, I am seeing if there is any better approach to accomplish the above ?

Related

Query to show which games which friends don't have

I have three tables set up in Access. I want to make a query that shows which games someone doesn't have in common with me.
I tried using an unmatched query, but that didn't work since each person has at least one game in common with me.
I guess I'm unsure how to handle this. The GameTimePlayed table basically has the opposite of the information I want to query, so is it possible to query that and add a "Not" conditional to "GameName" or something?
This is for a final project for class, and isn't due for about another month. I don't expect anyone to answer this for me, but even just a point in the right direction would be greatly appreciated. Everything I've tried to find so far is basically about unmatched queries, which did not work for me.
--EDIT TO PROVIDE MORE INFO--
I have all of the games in FavoriteGames. However, not all of my friends (PersonID) have all of my FavoriteGames. I'd like a query to show a record of FirstName, LastName, GameName, for each PersonID, for each GameName that he/she does not have.
Expected Behavior Example: PersonID 10 only has one GameName in common with me. The query should return five records for PersonID 10
(every game except Rocket League).
Sample Data:
tbl_FavoriteGames
tbl_FriendsWithGame
tbl_GameTimePlayed
GameName is the Primary Key for tbl_FavoriteGames
PersonID is the Primary Key for tbl_FriendsWithGame
PersonID, GameName Foreign Keys form a Composite Primary Key for tbl_GameTimePlayed
This is the closest I have gotten so far (still way off though) in that it removes the specified GameName:
SELECT *
FROM tbl_GameTimePlayed
WHERE NOT EXISTS
(
SELECT *
FROM tbl_FriendsWithGame
WHERE tbl_GameTimePlayed.PersonID = tbl_FriendsWithGame.PersonID
AND tbl_GameTimePlayed.GameName = tbl_FavoriteGames.GameName
);
It prompts me to enter a GameName (no idea why). When I enter a GameName, it returns all records that don't have that specific GameName.
This returns 6 games for each person, whether or not the person actually has that game. Could be useful since it contains the people/games that aren't in common.
SELECT PersonID, GameName
FROM tbl_FriendsWithGame, tbl_FavoriteGames
WHERE EXISTS (SELECT PersonID FROM tbl_GameTimePlayed WHERE GameName = tbl_GameTimePlayed.GameName);
I tried "WHERE NOT EXISTS" and that returned 0 results.
--SECOND EDIT: SOLVED!!--
I took a fresh look at the problem today, and figured it out! I used the code mentioned above to query (qry_AllPeopleAllGames) a list of all of the games, for all of the people (so 6 entries per person):
SELECT PersonID, GameName
FROM tbl_FriendsWithGame, tbl_FavoriteGames
WHERE EXISTS (SELECT PersonID FROM tbl_GameTimePlayed WHERE GameName = tbl_GameTimePlayed.GameName);
Then, I made another query that compared the qry_AllPeopleAllGames list to my tbl_GameTimePlayed (which is the list of people, games they actually own, and hours played) and spit out a list of FirstName & LastInitial and GameName that don't exist in the real list:
SELECT [tbl_FriendsWithGame]![FirstName] & " " & [tbl_FriendsWithGame]![LastInitial] AS FullName, GameName
FROM qry_AllPeopleAllGames INNER JOIN tbl_FriendsWithGame ON qry_AllPeopleAllGames.PersonID = tbl_FriendsWithGame.PersonID
WHERE ((NOT Exists (SELECT PersonID, GameName
FROM tbl_GameTimePlayed
WHERE qry_AllPeopleAllGames.PersonID = tbl_GameTimePlayed.PersonID AND qry_AllPeopleAllGames.GameName = tbl_GameTimePlayed.GameName
)));
****NOTE:**** The first part of the SELECT is not needed, I just used it for easier viewing in my actual query results (showing first name/last initial in one field).
I'm really excited that I figured this out! I'm sure there are better/more efficient ways to do this, and if you want to share, please let me know!
I included this in my initial post, but I'll post this as the answer as well.
I took a fresh look at the problem today, and figured it out! Last night while trying to test random possible solutions, I accidently made a query that lists all of the games, for all of the people (so 6 entries per person). Today, I used it as part of the solution, qry_AllPeopleAllGames:
SELECT PersonID, GameName
FROM tbl_FriendsWithGame, tbl_FavoriteGames
WHERE EXISTS (SELECT PersonID FROM tbl_GameTimePlayed WHERE GameName = tbl_GameTimePlayed.GameName);
Then, I made another query that compared the qry_AllPeopleAllGames list to my tbl_GameTimePlayed, which is the real list of people/games/hours played.
It returns the FirstName&LastInitial and the GameName for each PersonID/GameName combo that doesn't appear in the tbl_GameTimePlayed table. Here is the code:
SELECT [tbl_FriendsWithGame]![FirstName] & " " & [tbl_FriendsWithGame]![LastInitial] AS FullName, GameName
FROM qry_AllPeopleAllGames INNER JOIN tbl_FriendsWithGame ON qry_AllPeopleAllGames.PersonID = tbl_FriendsWithGame.PersonID
WHERE ((NOT Exists (SELECT PersonID, GameName
FROM tbl_GameTimePlayed
WHERE qry_AllPeopleAllGames.PersonID = tbl_GameTimePlayed.PersonID AND qry_AllPeopleAllGames.GameName = tbl_GameTimePlayed.GameName
)));
NOTE: The first part of the SELECT is not needed, I just used it for easier viewing in my actual query results (showing first name/last initial in one field).
I'm really excited that I figured this out! I'm sure there are better/more efficient ways to do this, and if you want to share, please let me know!
You need a dataset of all possible pairs of friends/games in order to determine which games each friend does not have. Do you have a tbl_Friends? Consider:
Query1:
SELECT tblFriends.ID, tbl_FavoriteGames.ID FROM tblFriends, tbl_FavoriteGames;
That is a Cartesian query - without JOIN clause every record of each table will associate with each record of other table.
Query2:
SELECT Query1.tblFriends.ID, Query1.tbl_FavoriteGames.ID
FROM tbl_FriendsWithGame RIGHT JOIN Query1 ON (tbl_FriendsWithGame.GameID = Query1.tbl_FavoriteGames.ID) AND (tbl_FriendsWithGame.FriendID = Query1.tblFriends.ID) WHERE tbl_FriendsWithGame.GameID IS NULL;
Or if you don't have tbl_Friends
SELECT DISTINCT tbl_FriendsWithGame.FriendID, tbl_FavoriteGames.ID
FROM tbl_FavoriteGames, tbl_FriendsWithGame;
Then adjust Query2.

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

SQL - Stored Procedures for reporting using Reporting Service

I'm using Reporting Service in a ASP.NET MVC4 project (using Visual Studio 2010) to generate reports and I'm working with stored procedures. I have a table Product which can belong to persons. That's the concept of allocation. A product can be allocated to different persons (many-to-many relation) and I have an intermediate table between those two :
The thing I want tot do in my report is to display the information related to each existing product including the owner. In this situation, I can have 2 types of owners : the company (so the product has never been allocated or the last allocation has an EndDate) or a person (so the last allocation doesn't have an EndDate or has a "future" EndDate).
I know that all these verifications should be made in my report template but for the SQL stored procedure, I have no idea about how to do to get the last allocation for each product (if there is any).
Any idea which could help is welcomed.
EDIT : Updated query
SELECT pr.SerialNumber, coalesce (p.LastName, 'US') as Owner
FROM bm_Products pr
LEFT OUTER JOIN
(Select Id_Person, Id_Product, max(ISNULL(EndDate,getdate()+1)) as MaxAllocDate
FROM bm_ProductAllocations a
WHERE EndDate > getdate()
group by Id_Person, Id_Product
having max(ISNULL(EndDate, getdate()+1)) =
(select max(isnull(EndDate, getdate()+1))
from bm_ProductAllocations where Id_Product = a.Id_Product)
) pa on pr.Id_Product = pa.Id_Product
LEFT OUTER JOIN bm_Persons p on pa.Id_Person = p.Id_Person
The result :
SerialNumber Owner
-------------------------------------------------- --------------------------------------------------
78745148154815204 US
84512048150410522 US
84512841520415205 US
87451284512485120 US
56123051215215215 US
48512485487487856 US
CNU1510ZL0 US
8456656551521 US
4154854854151 US
4851205230047 US
4511120521050 US
84515151320541201 US
74161230326524165 US
I don't see "company" for owner on the table shown, so i'll assume for now that it is in the products table, a hardcoded 'US' because you own it, or you can provide an update.
SELECT pr.Product, coalesce (p.person, 'US') as Owner
FROM Product pr
LEFT OUTER JOIN
(Select id_person, id_product, max(ISNULL(EndDate,getdate()+1)) as MaxAllocDate
FROM Product_Allocation a
WHERE Enddate > getdate()
group by id_person, id_product
having max(ISNULL(EndDate, getdate()+1)) =
(select max(isnull(Enddate, getdate()+1))
from product_allocation where id_product = a.id_Product)
) pa on pr.id_product = pa.id_product
LEFT OUTER JOIN person p on pa.id_person = p.id_person
The subquery will give you the max end date by product and person. since we need the subquery to return the person, you would possibly get multiple id_person records for each product if you have multiple allocations that haven't expired. The "having" clause will use the already aggregated end date to match to the max end date by product without the person, so you will get only the "most future" allocation end date. Since the product allocation is joined to the person, and they are left joined to the product table, if there are no unexpired or future allocations, or there are no allocations at all, then you will get 'US' as your owner. you can always add in another table join directly from product if your "company" is somewhere else, and substitute that for 'US'.
I don't have a data set to work off of that exactly duplicates this, so I can't test it, but I did test the having process and it functioned correctly.
if there are other assumption like, only one allocation at a time, etc, this could be simplified. Also you could work this through a memory table with only current and future allocations by product, but I believe this should work. You may need to adjust the 'getdate()' if you are working off date only not date time. If that's the case then use cast(convert(varchar(10), getdate(), 101) as datetime) and return only the date portion of the getdate function.
Hope this helps you out

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.

Creating relationships in Microsoft Access

I'm creating a database to track my students' participation in classes. This is what I've set up so far. I'm working in Access 2007.
Participant Master table - name, contact info, enrolled class, enrolled semester. Enrolled class (Class A, Class B, Class C) and enrolled semester (Semester 1, Semester 2) are defined in tables. Primary key is an autoincrement number but students all get a school ID number (ParticipantID).
Query1 pulls name & address for students enrolled in class A, semester 2
(SELECT name, address FROM ParticipantMaster WHERE EnrClass = "Class A" and EnrSem = "Semester 2"). The query works.
DailySessionLog is a table to represent each daily class. Includes fields for date, instructor name (check from list), discusssion topic (check from list).
Now I want to link DailySessionLog to Query1 -- letting me check off every day whether a student was there for None, Partial, Half, or Full session that day. I'm having trouble linking these and creating a subform. Any help?
I tried having a ParticipantID field in DailySessionLog which I linked to ParticipantID in Query1. It doesn't recognize if it's a one:one or :many relationship. If I go ahead and create a subform using the Access wizard it treats the Participant data as the "higher" form and the DailySessionLog data as the "sub" form. I want it to be the other way around.
Thanks for helping!
To create a one-to-one or one-to-many relationship, you should link DailySessionLog to ParticipantMaster rather than to Query1. You would then create a query to show the daily session logs of a given class for a given semester. Example:
SELECT {field list} FROM ParticipantMaster INNER JOIN DailySessionLog ON {join expression} WHERE ParticipantMaster.EnrClass = "Class A" AND ParticipantMaster.EnrSem = "Semester 2"
However, it would be better to use variable parameters rather than hard-coded strings. Example:
SELECT {field list} FROM ParticipantMaster INNER JOIN DailySessionLog ON {join expression} WHERE ParticipantMaster.EnrClass = [ClassName] AND ParticipantMaster.EnrSem = [SemesterName]
Or, to use a value from a control on an open form:
SELECT {field list} FROM ParticipantMaster INNER JOIN DailySessionLog ON {join expression} WHERE ParticipantMaster.EnrClass = [Forms]![FormName]![ClassControlName] AND ParticipantMaster.EnrSem = [Forms]![FormName]![SemesterControlName]
EDIT
Actually, you want to use this AND xQbert's idea, so, with table names like this for brevity:
Participants (a.k.a. ParticipantMaster)
Sessions (a.k.a DailySessionLog)
ParticipantSession (a.k.a. Participant_daily_session_log)
the first query would be more like this:
SELECT {field list}
FROM
Participants
INNER JOIN ParticipantSession ON Participant.ID = ParticipantSession.ParticipantID
INNER JOIN Sessions ON ParticipantSession.SessionID = Session.ID
Where do you intend the database to "Store" the participation?
I think the problem is you need another table: a Particpiant_Daily_sessioN_log which would store the results of your daily log for each student participation.
Think about the table dailysessionlog you don't want instructor name, topic and date listed for EACH student do you?
So what you have is a many students may attend class and a class may have many students. This means you have a many to many which needs to be resolved before access can figure out what you want to do.
Think of the following tables:
Participant (ParticipantID)
Class (ClassID)
Session (SessionID, ClassID)
ClassParticipants (ClassId, ParticipantID, Semester, year
SessionParticipants (SessionID, ClassID, ParticipantID)

Resources