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

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.

Related

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

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.

finding duplicate rows with different IDs based on multiple columns

please forgive me if my jargon is off. I'm still learning!
I just started using Teradata, and to be honest has been a lot of fun. however, I have hit a road block that has stumped me for a while.
I successfully selected a table from a database that looks like:
ID service date name
1 service1 1/5/15 john
2 service2 1/7/15 steve
3 service3 1/8/15 lola
4 service4 1/3/15 joan
5 service5 1/5/15 fred
6 service3 1/3/15 joan
7 service5 1/8/15 oscar
Now I want to search the data base again to find any duplicate IDs (example: to see if service service1 with date 1/5/15 with name john exists on another row with a different ID.)
At first, I did something like this:
SELECT ID, service, date, name
FROM table
WHERE table.service = ANY(service1, service2, service3, service4, service5, service3, service5)
AND table.date = ANY('1/5/15', '1/7/15, '1/8/15', '1/3/15', '1/5/15', '1/3/15', '1/8/15')
AND table.name = ANY('john', 'steve', 'lola', 'joan', 'fred', 'joan', 'oscar');
But this is giving me more rows than I wanted.
example:
ID service date name
92 service3 1/8/15 steve
is of no use to me since I am looking for IDs that have the same combination of service, date, and name as of any of the other IDs in the above table.
something like this would be favorable:
ID service date name
609 service3 1/8/15 lola
since it matches than of ID 3.
I was curious to see if it were possible to treat the three columns (service, date, name) as a vector and maybe select the rows that match it that way?
ex
......
WHERE (table.service, table.date, table.name) = ANY((service3,1/8/15,lola), (service1, 1/5/15, john), ...etc)
My Teradata is down right now, So I have yet to try the above example. Nevertheless, any thoughts/feedback is greatly appreciated!
The following query may be what you are trying to achieve. This selects IDs for which the combination of service, date, and name appears more than once.
SELECT t1.ID
FROM yourTable t1
INNER JOIN
(
SELECT service, date, name
FROM yourTable
GROUP BY service, date, name
HAVING COUNT(*) > 1
) t2
ON t1.service = t2.service AND
t1.date = t2.date AND
t1.name = t2.name
This is a simple task for a Windowed Aggregate:
SELECT *
FROM tab
QUALIFY
COUNT(*) OVER (PARTITION BY service, date, name) > 1
This counts the number of rows with the same combination of values (like Tim Biegeleisen's Derived Table) but unlike a Standard Aggregate it keeps all rows. The QUALIFY is a nice Teradata syntax extension to avoid a Derived Table.
Don't hardcode values in your query unless you absolutely have to. Instead, take the query you already wrote and join to that.
SELECT dupes.*
FROM (your query) yourquery
JOIN table dupes
ON yourquery.service = dupes.service
AND yourquery.date = dupes.date
AND yourquery.name = dupes.name

Table Join issue

Right now I've got a Main table in which I am uploading data. Because the Main table has many different duplicates, I Append various data out of the Main table into other tables such as, username, phone number, and locations in order to keep things optimized. Once I have everything stripped down from the Main table, I then append what's left into a final optimized Main table. Before this happens though, I run a select query joining all the stripped tables with the original Main table in order to connect the IDs from each table, with the correct data. For example:
Original Main Table
--Name---------Number------Due Date-------Location-------Charges Monthly-----Charges Total--
John Smith 111-1111 4/3 Chicago 234.56 500.23
Todd Jones 222-2222 4/3 New York 174.34 323.56
John Smith 111-1111 4/3 Chicago 274.56 670.23
Bill James 333-3333 4/3 Orlando 100.00 100.00
This gets split into 3 tables (name, number, location) and then there is a date table with all the dates for the year:
Name Table Number Table Location Table Due Date Table
--ID---Name------ -ID--Number--------- ---ID---Location---- --Date---
1 John Smith 1 111-1111 1 Chicago 4/1
2 Todd Jones 2 222-2222 2 New York 4/2
3 Bill James 3 333-3333 3 Orlando 4/3
Before The Original table gets stripped, I run a select query that grabs the ID from the 3 new tables, and joins them based on the connection they have with the original Main table.
Select Output
--Name ID----Number ID---Location ID---Due Date--
1 1 1 4/3
2 2 2 4/3
1 1 1 4/3
3 3 3 4/3
My issue comes when I need to introduce a new table that isn't able to be tied into the Original Main Table. I have an inventory table that, much like the original Main table, has duplicates and needs to be optimized. I do this by creating a secondary table that takes all the duplicated devices out and put them in their own table, and then strips the username and number out and puts them into their tables. I would like to add the IDs from this new device table into the select output that I have above. Resulting in:
Select Output
--Name ID----Number ID---Location ID---Due Date--Device ID---
1 1 1 4/3 1
2 2 2 4/3 1
1 1 1 4/3 2
3 3 3 4/3 1
Unlike the previous tables, the device table has no relationship to the originalMain Table, which is what is causing me so much headache. I can't seem to find a way to make this happen...is there anyway to accomplish this?
Any two tables can be joined. A table represents an application relationship. In some versions (not the original) of Entity-Relationship Modelling (notice that the "R" in E-R stands for "(application) relationship"!) a foreign key is sometimes called a "relationship". You do not need other tables or FKs to join any two tables.
Explain, in terms of its column names and the values for those names, exactly when a row should turn up in the result. Maybe you want:
SELECT *
FROM the stripped-and-ID'd version of the Original AS o
JOIN the stripped-and-ID'd version of the Device AS d
USING NameID, NumberID, LocationID and DueDate
Ie
SELECT *
FROM the stripped-and-ID'd version of the Original AS o
JOIN the stripped-and-ID'd version of the Device AS d
ON o.NameID=d.NameId AND o.NumberID=d.NumberID
AND o.LocationID=d.LocationID AND o.DueDateID=d.DueDate.
Suppose p(a,...) is some statement parameterized by a,... .
If o holds the rows where o(NameID,NumberID,LocationID,DueDate) and d holds the rows where d(NameID,NumberID,LocationID,DueDate,DeviceID) then the above holds the rows where o(NameID, NumberID, LocationID, DueDate) AND d(NameID,NumberID,LocationID,DueDate,DeviceID). But you really have not explained what rows you want.
The only way to "join" tables that have no relation is by unioning them together:
select attribute1, attribute2, ... , attributeN
from table1
where <predicate>
union // or union all
select attribute1, attribute2, ... , attributeN
from table2
where <predicate>
the where clauses are obviously optional
EDIT
optionally you could join the tables together by stating ON true which will act like a cross product

oracle - sql query select max from each base

I'm trying to solve this query where i need to find the the top balance at each base. Balance is in one table and bases are in another table.
This is the existing query i have that returns all the results but i need to find a way to limit it to 1 top result per baseID.
SELECT o.names.name t.accounts.bidd.baseID, MAX(t.accounts.balance)
FROM order o, table(c.accounts) t
WHERE t.accounts.acctype = 'verified'
GROUP BY o.names.name, t.accounts.bidd.baseID;
accounts is a nested table.
this is the output
Name accounts.BIDD.baseID MAX(T.accounts.BALANCE)
--------------- ------------------------- ---------------------------
Jerard 010 1251.21
john 012 3122.2
susan 012 3022.2
fin 012 3022.2
dan 010 1751.21
What i want the result to display is calculate the highest balance for each baseID and only display one record for that baseID.
So the output would look only display john for baseID 012 because he has the highest.
Any pointers in the right direction would be fantastic.
I think the problem is cause of the "Name" column. since you have three names mapped to one base id(12), it is considering all three records as unique ones and grouping them individually and not together.
Try to ignore the "Name" column in select query and in the "Group-by" clause.
SELECT t.accounts.bidd.baseID, MAX(t.accounts.balance)
FROM order o, table(c.accounts) t
WHERE t.accounts.acctype = 'verified'
GROUP BY t.accounts.bidd.baseID;

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. :-)