Matching 3 tables one by one and geting a final column value - sql

I have 3 tables with the columns as example below:
Transaction Table (tran_table), which contains "Customer Number" value as:
CustomerN Date Usercode
Staff Table (staff), which contains "(staff's) Customer Number" and "PersonID" as:
CustomerN PersonID UserCode
And, staff title log (staff_history) by date which contains the info about whether a staff is active or left; "PersonID" and "Active" as
PersonID IsActive positionname StartDate EndDate UserCode
1 Yes branch Manager 01.01.2020 01.01.4570 daniel
1 Yes Sales representative 15.11.2018 31.12.2019 daniel
2 No Sales associate 01.01.2018 31.05.2020 mary
3 Yes Intern 01.01.2018 31.12.2019 josh
3 Yes Sales associate 01.01.2020 28.02.2020 josh
3 Yes Sales Representative 01.03.2020 01.01.4570 josh
I would like to get, if the customer who make a transaction is/was our staff (left or active), a column value that contains "IsActive", as "Active";"Left" or "NULL" for non-staff transactions.
I have tried this code below with no success
select b.AccountNumber, b.CustomerName ,aa.IsActive ,
(*) (select
(*) top 1 aa.positionname
(*) from staff_history
(*) where
(*) b.date between aa.StartDate and aa.EndDate
(*) and (b.username=aa.UserCode))
(*) as [Title],
from tran_table as b
left join staff_history as aa WITH (NOLOCK) ON b.UserName=aa.UserCode
left join staff  as ab WITH (NOLOCK) ON b.AccountNumber=ab.AccountNumber and ab.PersonId=aa.PersonId
where
aa.positionname is not null
order by aa.positionname
Edit #1 to DenStudent: Sure, the result with this code ise sth like:
CustomerN Date Usercode IsActive
15874 01.01.2020 josh Yes
8431 05.03.2020 mary No
55147 07.05.2020 daniel Yes
the problem here is, it matches the staff's situation who MADE the transaction instead of customer (is/was a staff or not). All usercodes belongs to a staff thus, there is no "null" value.
The result that i expect is:
CustomerN Date Usercode IsActive
15874 01.01.2020 josh Yes (1)
8431 05.03.2020 mary NULL (2)
55147 07.05.2020 daniel No (3)
(1) the customer number 15874 belongs to an active staff
(2) the customer number 8431 belongs to no a former or active staff
(3) the customer number 55147 belongs to a former staff
Edit #2 to Marc Guillot: there are tons of additional tables and columns. I wanted to short it to the ones that relevant to this query. however, yes, the date column in transaction table, matches with staff_history table to see the staff's title on the date of transaction. i have added several columns and the code (with(*) signed lines) if it works with you.

It's hard to say because I personally don't see what you are storing on staff_history. You are only storing the periods of activity ?, or you also store periods of inactivity ?, what happens with the EnDate for the current period ?, you leave it at null ?.
If you show us the content on those tables for your sample result, then we can help you better.
It seems that you are looking for something like this:
select b.AccountNumber, b.CustomerName, aa.IsActive, aa.positionname as [Title]
from tran_table as b
outer apply (select top 1 aa.positionname, IsActive
from staff_history as aa
where b.date between aa.StartDate and aa.EndDate
and b.username=aa.UserCode
order by b.StartDate desc) as aa
left join staff as ab on b.AccountNumber=ab.AccountNumber and ab.PersonId=aa.PersonId
where aa.positionname is not null
order by aa.positionname
This returns the positionname and state of activity at the day of the transaction (or null if it's not present in staff_history).
I have used outer apply, which is like a left join, but instead of using a condition you directly provide with a subquery all the records that you want to join.

Related

How to get the values corresponding to another table?

I'm new to SQL and am a bit confused on how I would write a query in order to get the count of state in a different table.
Ie i have this table [student]
id
school_code
0
0123
1
2345
2
2345
And this other table [school]
school_code
name
State
0123
xxyy
New Jersey
2345
xyxy
Washington
3456
yxyx
Colarado
I want to find out how I would get this table which tells me the entries for state by checking each student and making a count of how often that state occurs, ordered by most occurrences in student table.
State
No. times occured (iterating through student)
Washington
2
New Jersey
1
SELECT school.state, count(school.state)
FROM student, school
WHERE student.school_code = school.school_code
GROUP BY school.state
ORDER BY count(school.state)`
I'm not sure whether this would be iterating through each student and counting them?
Or just natural-joinging student and school and then counting all the states
When I run this on data supplied, the numbers of times occurred is a really low number which doesn't seem right?
We can simply JOIN the two tables and COUNT the school code in the students table, with GROUP BY state:
SELECT
sc.state, COUNT(st.school_code)
FROM
school sc
JOIN student st
ON sc.school_code = st.school_code
GROUP BY sc.state;
We can try out here: db<>fiddle

MS Access - Continuous Form Select with Dropdown from Another Table

I've been using Databasedevelopment.co.uk's excellent example on how to do a continuous form select with a invisible button overlaying a checkbox to assign employees to a specific shift. I'd like to make it so that said continuous form also has a dropdown of the different Paycodes so that when they are selected I can use a combobox to indicate "Regular Pay, Overtime, etc....". I'm running into a wall because with the query as-is from the example, the recordset for the Paycode field is not updateable.
Messing with the primary key for the employee's table fixes the paycode issue but prevents the selection code from working properly.
I'm a bit out of my depth here, what's the easiest way to accomplish this?
SELECT CAT.EmployeeID, CAT.FirstName, CAT.LastName, ASGN_TEMP.ShiftNum, ASGN.PayCode, IIf(ASGN_TEMP.[ShiftNum] Is Null,0,-1) AS IsSelected
FROM tblEmployees AS CAT
LEFT JOIN (SELECT ASGN.EmployeeID, ASGN.ShiftNum, ASGN.PayCode FROM tblAssignedEmployees AS ASGN
WHERE ASGN.ShiftNum = Forms!frmMainMenu![txtShiftNum]) AS ASGN_TEMP
ON CAT.EmployeeID = ASGN_TEMP.EmployeeID;
Paycode is a static table with an ID, a Paycode and a description and would only correspond with each record in "tblAssignedEmployee". That is to say, there is no relationship between the employee or the shift with what Paycodes are available, I'd just like a second table for ease of updates.
---EDIT---
Table: Employees
ID
EmployeeID
Firstname
LastName
1
1234
Bob
Jones
2
9999
Mary
Sue
Table: AssignedEmployees
ID
EmployeeID
ShiftNum
PayCode
1
1234
1
OT
2
9999
2
Regular
3
1234
2
OT
Table: PayCodes
ID
PayCode
Desc
1
Regular
Regular Pay
2
OT
Overtime

How to select a record from SQL that does not have a certain value

I have a list of students who have several levels of English to complete. Is there a way to find those students who are doing English I and haven't moved to the next level
ID STUDENTID NAME ENGLISHLEVEL STARTDATE ENDDATE
----------------------------------------------------------------
1 001 Eric English-1 2017-01-01 2018-01-01
2 002 Brian English-1 2017-01-01 2017-01-31
3 002 Brian English-2 2017-02-01 2017-03-01
4 003 David English-1 2017-05-01 2017-06-01
5 003 David English-2 2017-06-02 2017-07-03
I have a list similar to above for thousands of students and want to know how I can query the table to show me those students who did English-1 but never got started with English-2 or English-3
Advice would be appreciated.
You can join the table to itself with an outer join. Have the outer joined table check for English2/3, and then filter out any results where there is a match.
select
eng1.*
from
students_table eng1
left outer join students_table eng2
on (
eng1.STUDENTID=eng2.STUDENTID
and eng2.ENGLISHLEVEL in ('English-2', 'English-3')
)
where
eng2.STUDENTID is null -- Filter out rows where an eng2 row was found
You can use not exists :
select t.*
from table t
where not exists (select 1
from table t1
where t1.STUDENTID = t.STUDENTID and
t1.ENGLISHLEVEL in ('English-2', 'English-3')
);
Your English levels can be sorted alphabetically:
select studentid, name
from t
group by studentid, name
having max(englishlevel) = 'English-1'
You can just use IN in your WHERE statement to get those students who did English-1
SELECT * FROM tblStudents
WHERE ENGLISHLEVEL IN ('English-1')
What does "students who are doing English I" mean in data terms in the table in your post?
What does "and havent moved to the next level" mean in data terms in the table?
Do you have a table that contains all possible values of ENGLISHLEVEL?
How are the STARTDATE and ENDDATE columns populated?
If the ENDDATE was only populated once the person has actually completed the training, you could use the NULL value there to look for people that have not completed a training. That might be the way to go here.
If you have a table that defines all possible values of ENGLISHLEVEL, then you can compare the count of distinct ENGLISHLEVEL values with the count of values in the ENGLISHLEVEL table, and that will tell you which students have completed all the courses. You can also do this comparison for each training that way.

Joining a table to two one-to-many relationship tables in SQL Server

Happy Friday folks,
I'm trying to write an SSRS report displaying data from three (actually about 12, but only three relevant) tables that have akward relationships and the SQL query behind the data is proving difficult.
There are three entities involved - a Purchase Order, a Sales Order, and a Delivery. The problem is the a Purchase Order can have many sales orders, and also many deliveries which are NOT linked to the sales orders...that would be too easy.
Both the Sales Order and Delivery tables can be linked to the Purchase Order table by foreign keys and an intermediate table each.
I need to basically list Purchase Orders, a list of sales orders and a list of deliveries next to them, with NULLs for any fields that aren't valid so that'll give the required output in SSRS/when read by a human, ie, for a purchase order with 2 sales orders and 4 delivery dates;
PO SO Delivery
1234 ABC 05/10
1234 DEF 09/10
1234 NULL 10/12
1234 NULL 14/12
The above (when grouped by PO) will tell the users there are two sales orders and four (unlinked) delivery dates.
Likewise if there are more SOs than deliveries, we need NULLs in the Delivery column;
PO SO Delivery
1234 ABC 03/08
1234 DEF NULL
1234 GHI NULL
1234 JKL NULL
Above would be the case with 4 SOs and one delivery date.
Using Left Outer joins alone gives too much duplication - in this case 8 rows, as it gives 4 delivery dates for each match on the sales order;
PO SO Delivery
1234 ABC 05/10
1234 ABC 09/10
1234 ABC 10/12
1234 ABC 14/12
1234 DEF 05/10
1234 DEF 09/10
1234 DEF 10/12
1234 DEF 14/12
It's fine that the PO column is duplicated as SSRS can visually group that - but the SO/Delivery fields can't be allowed to duplicate as this can't be got rid of in the report - if I group the column in SSRS by SO then it still spits out 4 delivery dates for each one.
The only situation our query works nice is when there is just one SO per PO. In that case the single PO and SO numbers are duplicated together for x deliveries and can both be neatly grouped in SSRS. Unfortunately this is a rare occurence in the data.
I've thought of trying to use some sort of windowing function or CROSS APPLY but both fall down as they will repeat for every PO number listed and end up spitting out too much data.
At the point of thinking this just isn't set-based enough to be doable in SQL, I know the data is horrible..
Any help much appreciated.
EDIT - basical sqlfiddle link to the table schemas. Omitted many columns which aren't relevant. http://sqlfiddle.com/#!2/5ba16
Example data...
Purchase Order
PO_Number Style
1001 Black work boots
1002 Green hat
1006 Red Scarf
Sales Order
Sales_order_number PO_number Qty Retailer
A100-21 1001 15 Walmart
A100-22 1001 29 Walmart
A200-31 1006 1000 Asda
Delivery
Delivery_ID Delivery_Date PO_number
1543285 10/05/2014 1001
1543286 12/05/2014 1001
1543287 17/05/2014 1001
1543288 21/05/2014 1002
If you assign row numbers to the elements in salesorders and deliveries, you can link on that.
Something like this
declare #salesorders table (po int, so varchar(10))
declare #deliveries table (po int, delivery date)
declare #purchaseorders table (po int)
insert #purchaseorders values (123),(456)
insert #salesorders values (123,'a'),(123,'b'),(456,'c')
insert #deliveries values (123,'2014-1-1'),(456,'2014-2-1'),(456,'2014-2-1')
select *
from
(
select numbers.number, p.po, so.so, d.delivery from #purchaseorders p
cross join (Select number from master..spt_values where type='p') numbers
left join (select *,ROW_NUMBER() over (partition by po order by so) sor from #salesorders ) so
on p.po = so.po and numbers.number = so.sor
left join (select * , ROW_NUMBER() over (partition by po order by delivery) dor from #deliveries) d
on p.po = d.po and numbers.number = d.dor
) v
where so is not null or delivery is not null
order by po,number

GROUP BY and aggregate function query

I am looking at making a simple leader board for a time trial. A member may perform many time trials, but I only want for their fastest result to be displayed. My table columns are as follows:
Members { ID (PK), Forename, Surname }
TimeTrials { ID (PK), MemberID, Date, Time, Distance }
An example dataset would be:
Forename | Surname | Date | Time | Distance
Bill Smith 01-01-11 1.14 100
Dave Jones 04-09-11 2.33 100
Bill Smith 02-03-11 1.1 100
My resulting answer from the example above would be:
Forename | Surname | Date | Time | Distance
Bill Smith 02-03-11 1.1 100
Dave Jones 04-09-11 2.33 100
I have this so far, but access complains that I am not using Date as part of an aggregate function:
SELECT Members.Forename, Members.Surname, Min(TimeTrials.Time) AS MinOfTime, TimeTrials.Date
FROM Members
INNER JOIN TimeTrials ON Members.ID = TimeTrials.Member
GROUP BY Members.Forename, Members.Surname, TimeTrials.Distance
HAVING TimeTrials.Distance = 100
ORDER BY MIN(TimeTrials.Time);
IF I remove the Date from the SELECT the query works (without the date). I have tried using FIRST upon the TimeTrials.Date, but that will return the first date which is normally incorrect.
Obviously putting the Date as part of the GROUP BY would not return the result set that I am after.
Make this task easier on yourself by starting with a smaller piece of the problem. First get the minimum Time from TimeTrials for each combination of MemberID and Distance.
SELECT
tt.MemberID,
tt.Distance,
Min(tt.Time) AS MinOfTime
FROM TimeTrials AS tt
GROUP BY
tt.MemberID,
tt.Distance;
Assuming that SQL is correct, use it in a subquery which you join back to TimeTrials again.
SELECT tt2.*
FROM
TimeTrials AS tt2
INNER JOIN
(
SELECT
tt.MemberID,
tt.Distance,
Min(tt.Time) AS MinOfTime
FROM TimeTrials AS tt
GROUP BY
tt.MemberID,
tt.Distance
) AS sub
ON
tt2.MemberID = sub.MemberID
AND tt2.Distance = sub.Distance
AND tt2.Time = sub.MinOfTime
WHERE tt2.Distance = 100
ORDER BY tt2.Time;
Finally, you can join that query to Members to get Forename and Surname. Your question shows you already know how to do that, so I'll leave it for you. :-)