SQL NOT IN Query on MS ACCESS/VBA - sql

I have the following Course table with different extract dates.
ID | Course | ExtractDate
10 | 100000 | 2017-02-28
10 | 100001 | 2017-01-31
10 | 100002 | 2016-12-31
10 | 100003 | 2016-11-30
I need to perform the following SQL script to keep only records from the latest 3 months, and hence removing the record with Course code 10003.
Delete from [Course] where [ExtractDate] not in (select distinct top 3 [ExtractDate] from [Course] order by [ExtractDate] desc
Seems like VBA or MS Access does not support the function "not in", anybody has any workaround for this?
Thank you.

If you want records older than three months:
Delete from [Course]
where [ExtractDate] < dateadd("m", -3, date());
No set-based operation is needed.

NOT IN is the same as a left join where the target is null (thus there was no join)
SO...
from [Course]
where [ExtractDate] not in (
select distinct top 3 [ExtractDate]
from [Course]
order by [ExtractDate] desc
)
is the same as
from [Course]
left join (
select distinct top 3 [ExtractDate]
from [Course]
order by [ExtractDate] desc
) as x on [ExtractDate] = x.[ExtractDate]
where x.[ExtractDate] is null
However, I make no claim any of your code was correct -- just that those two are the same in SQL.

Related

SQL - Get MIN of a column for each key in other table

I have two tables as such:
ORDERS
Date
TransactID
COL3
2021-06
1234
4
2021-09
1238
8
Agg
Date
User
TransactID
2021-06
3333
1234
2021-03
3333
XXXX
2021-02
3333
XXXX
2021-09
4444
1238
2021-05
4444
XXXX
2021-01
4444
XXXX
In AGG, a User can have many transactions, the ORDERS table is just a subset of it.
For each TransactID in Orders, I need to go into the Agg table and get the MIN date for the User associated with the TransactID.
Then, I need to calculate the date difference between the ORDERS.Date and the minimum AGG.DATE. The result is stored in SDP.COL3. COL3 can basically be described as Days Since First Transaction.
I have never done a SQL problem that is this multistep, and need some guidance. Any help would be greatly appreciated!
If I've got it right
SELECT SDP.TXN_ID, sdp.dt, datediff(sdp.dt, min(a1.DT)) diff
FROM SDP
JOIN AGG a1 on a1.UserID =
(SELECT a2.UserID
FROM AGG a2
WHERE SDP.TXN_ID = a2.TXN_ID
ORDER BY a2.UserID
limit 1)
GROUP BY SDP.TXN_ID, sdp.dt
You can omit
ORDER BY a2.UserID
limit 1
provided each transaction is always belonging to a single user.
The fiddle
based on your SQL Fidddle (http://sqlfiddle.com/#!9/101497/1) this should get you started
SELECT TXN_ID, DT, USERID
FROM (SELECT ROW_NUMBER() OVER (PARTITION BY sdp.TXN_ID ORDER BY sdp.DT ASC) AS [index],
sdp.TXN_ID,
sdp.DT,
agg.USERID
FROM sdp
LEFT JOIN agg ON sdp.TXN_ID = agg.TXN_ID) A
WHERE [index] = 1
For more information you should look at
https://www.sqlshack.com/sql-partition-by-clause-overview/
https://www.sqltutorial.org/sql-window-functions/sql-partition-by/
https://learnsql.com/blog/partition-by-with-over-sql/

SQL Server FETCH NEXT X ONLY based on a specific column

I have a request to get some online courses.
This is an example of results I can get :
course_code | course_date | participant_name
------------+-------------+------------------
21175A | 2021-12-01 | Jon Doe
21175A | 2021-12-01 | Lisa Tia
21175A | 2021-13-01 | Jon Doe
21175A | 2021-13-01 | Lisa Tia
As you can see, I get multiple rows for the same course depending on the dates and the participants, so I have to do some process in PHP to put everything in an array with keys (course_code)
I want to make a pagination to get the 25 first courses (and not rows)
SELECT course_code, course_date, participant_name
FROM courses
ORDER BY course_date
OFFSET 0
FETCH NEXT 25 ROWS ONLY
This won't work because a course can take multiple rows (4 here). So I wonder if I can write a query to get the 25 first courses (based on the column course_code) even if there are 100 rows.
Do you have an idea ? Thank you in advance
EDIT : This is what I did :
WITH cte AS
(
SELECT course_code
FROM courses
WHERE course_date <= '2021-12-31'
ORDER BY course_code
OFFSET 0 ROWS
FETCH NEXT 25 ROWS ONLY
)
SELECT *
FROM courses
INNER JOIN cte ON cte.course_code = courses.course_code
WHERE course_date <= '2021-12-31'
I added the WHERE clause in both to make the request slower
Use DENSE_RANK:
WITH cte AS (
SELECT *, DENSE_RANK() OVER (ORDER BY course_code) dr
FROM courses
)
SELECT course_code, course_date, participant_name
FROM cte
WHERE dr <= 25
ORDER BY course_date;
For the pagination part, just change the inequality in the WHERE clause. For example, to get the second page of 25 courses per page, you would use WHERE dr > 25 AND dr <= 50.

SQL query to group by data but with order by clause

I have table booking in which I have data
GUEST_NO HOTEL_NO DATE_FROM DATE_TO ROOM_NO
1 1 2015-05-07 2015-05-08 103
1 1 2015-05-11 2015-05-12 104
1 1 2015-05-14 2015-05-15 103
1 1 2015-05-17 2015-05-20 101
2 2 2015-05-01 2015-05-02 204
2 2 2015-05-04 2015-05-05 203
2 2 2015-05-17 2015-05-22 202
What I want is to get the result as.
1 ) It should show output as Guest_no, Hotel_no, Room_no, and column with count as number of time previous three column combination repeated.
So OutPut should like
GUEST_NO HOTEL_NO ROOM_NO Count
1 1 103 2
1 1 104 1
1 1 101 1
2 2 204 1
etc. But I want result to in ordered way e.g.: The output should be order by bk.date_to desc
My query is as below its showing me count but if I use order by its not working
select bk.guest_no, bk.hotel_no, bk.room_no,
count(bk.guest_no+bk.hotel_no+bk.room_no) as noOfTimesRoomBooked
from booking bk
group by bk.guest_no, bk.hotel_no, bk.room_no, bk.date_to
order by bk.date_to desc
So with adding order by result is showing different , because as I added order by date_to column so i have to add this column is group by clause too which will end up in different result as below
GUEST_NO HOTEL_NO ROOM_NO Count
1 1 103 1
1 1 104 1
1 1 103 1
1 1 101 1
2 2 204 1
Which is not the output I want.
I want these four column but with order by desc of date_to column and count as no of repetition of first 3 columns
I think a good way to do this would be grouping by guest_no, hotel_no and room_no, and sorting by the maximum (i.e. most recent) booking date in each group.
SELECT
guest_no,
hotel_no,
room_no,
COUNT(1) AS BookingCount
FROM
booking
GROUP BY
guest_no,
hotel_no,
room_no
ORDER BY
MAX(date_to) DESC;
Maybe this is what you're looking for?
select
guest_no,
hotel_no,
room_no,
count(*) as Count
from
booking
group by
guest_no,
hotel_no,
room_no
order by
min(date_to) desc
Or maybe max() instead of min(). SQL Fiddle: http://sqlfiddle.com/#!6/e684c/3
You could try this.
select t.* from
(
select bk.guest_no, bk.hotel_no, bk.room_no, bk.date_to,
count(*) as noOfTimesBooked from booking bk
group by bk.guest_no, bk.hotel_no, bk.room_no, bk.date_to
) t
order by t.date_to
You will also have to select date_to and then group the result by it.
If you use 'group by' clause, SQL Server doesn't allow you to use 'order by'. So you can make a sub query and use 'order by' in the outer query.
SELECT * FROM
(select bk.guest_no,bk.hotel_no,bk.room_no
,count(bk.guest_no+bk.hotel_no+bk.room_no) as noOfTimesRoomBooked,
(SELECT MAX(date_to) FROM booking CK
WHERE CK.guest_no=BK.guest_no AND bk.hotel_no=CK.bk.hotel_no
bk.room_no=CK.ROOM_NO ) AS DATEBOOK
from booking bk
group by bk.guest_no,bk.hotel_no,bk.room_no,bk.date_to) A
ORDER BY DATEBOOK
IT MIGHT HELP YOU

How to find out if a field's values for a given item are only increasing?

For this problem, I'm using Access as a front end for SQL Server, and calling Access through Excel VBA, although I can use a direct ADO connection if there are some T-SQL specific functions that would be more useful here.
I have a table that logs state changes for a set of items, e.g.:
+-------+-------+------------+
| docID | state | date |
+-------+-------+------------+
| 103 | 5 | 10/15/2013 |
| 103 | 6 | 10/18/2013 |
| 102 | 3 | 10/22/2013 |
| 103 | 2 | 11/1/2013 |
| 102 | 7 | 11/8/2013 |
+-------+-------+------------+
For each unique docID, I want to figure out whether its state is only increasing from first date to last date, or if it ever decreases. In the above data set, 103 decreases and 102 only increases. We can assume that the entries will be in date order.
One way to find this would be to create an object for each docID and add these objects to a dictionary, loading each state change into a list and checking to see whether the state has decreased, something like this:
function isDecreasing(cl as changeList) as boolean
for c=2 to cl.count
if cl.item(c).state < cl.item(c-1).state then
isDecreasing=true
exit function
end if
next
isDecreasing=false
end function
But this will slow my query down a lot because I'll have to convert all the table data into objects. It also means I'll have to write a lot of additional code to create and manage the objects for calculating and generating reports.
Is there any way to write a query in SQL Server or Access that can perform the same type of analysis on the whole data set?
In his otherwise excellent answer, Gordon Linoff said:
You have a problem using Access-only functionality
Really?
For the given data, which I've put in a table called [StateChanges]:
docID state date
----- ----- ----------
103 5 2013-10-15
103 6 2013-10-18
102 3 2013-10-22
103 2 2013-11-01
102 7 2013-11-08
I can create the following saved query in Access named [PreviousDates]
SELECT t1.docID, t1.date, MAX(t2.date) AS PreviousDate
FROM
StateChanges t1
INNER JOIN
StateChanges t2
ON t2.docID = t1.docID
AND t2.date < t1.date
GROUP BY t1.docID, t1.date
It returns
docID date PreviousDate
----- ---------- ------------
102 2013-11-08 2013-10-22
103 2013-10-18 2013-10-15
103 2013-11-01 2013-10-18
Then I can use the following query to identify the [docID]'s where the [state] went down
SELECT curr.docID
FROM
(
PreviousDates pd
INNER JOIN
StateChanges curr
ON curr.date = pd.date
)
INNER JOIN
StateChanges prev
ON prev.date = pd.PreviousDate
WHERE curr.state < prev.state
It returns
docID
-----
103
In fact, both queries are so simple that we can combine them into a single query that does the whole thing in one shot:
SELECT curr.docID
FROM
(
(
SELECT t1.docID, t1.date, MAX(t2.date) AS PreviousDate
FROM
StateChanges t1
INNER JOIN
StateChanges t2
ON t2.docID = t1.docID
AND t2.date < t1.date
GROUP BY t1.docID, t1.date
) PreviousDates
INNER JOIN
StateChanges curr
ON curr.date = PreviousDates.date
)
INNER JOIN
StateChanges prev
ON prev.date = PreviousDates.PreviousDate
WHERE curr.state < prev.state
So where's the problem?
You have a problem using Access-only functionality. But, if you have SQL Server 2012, you can use lead()/lag() functionality. There is another way, just using row_number(), which is available since SQL Server 2005.
Here is the idea. Enumerate the rows within each docId first by state and also by date. If the enumerations are the same, then the sequence is non-decreasing (essentially increasing). If different, then there is a bump in the road. Here is the code:
select docid,
(case when sum(case when rn_ds <> rn_sd then 1 else 0 end) = 0 then 'Increasing'
else 'Decreasing'
end) as SequenceType
from (select d.*,
row_number() over (partition by docId order by date, state) as rn_ds,
row_number() over (partition by docId order by state, date) as rn_sd
from d
) d
group by docid;
Note that I've made the sort a little more complicated by using both fields. This handles the case when two dates in a row have the same state (probably not allowed, but might as well make the technique more stable).
Question:
For each unique docID, I want to figure out whether its state is only increasing from first date to last date, or if it ever decreases.
So what you want to know is, for a given record a, does there exist a record b where the date of a is earlier but the state of b is lower.
So just ask that.
select docID
from T a
where
exists (
select 1 from T b where b.date > a.date and b.state < a.state
)

Count two Columns with two Where Clauses

I know it's just late in the day and my brain is just fried....
Using Teradata, I need to COUNT DISTINCT MEMBERS that haven't had a TRANS in the past six months and also COUNT the number of TRANS they had historically (prior to the six months). We can just assume the cutoff date to be 01/01/2012. All table is contained in a single table.
For example:
Member | Tran Date
123 | 01/01/2011
789 | 06/01/2011
123 |10/31/2011
678 | 04/03/2011
789 | 06/01/2012
So 2 members had a total of 3 transactions dated prior to 1/1/2012 with no transactions later than 1/1/2012.
In this example, my result would be:
MEMBERS | TRANS
2 | 3
Try this solution:
SELECT
COUNT(DISTINCT member_id) AS MEMBERS,
COUNT(*) AS TRANS
FROM
tbl
WHERE
member_id NOT IN
(
SELECT DISTINCT member_id
FROM tbl
WHERE trans_date > '2012-01-01'
)
You can't do it in one SQL statement. Use subqueries. This is TSQL coz I am unfamiliar with Teradata.
DECLARE #CUTOFF DATETIME = DATEADD(MO,-6,GETDATE()) --6MTHS AGO
SELECT COUNT(MEMBERID) AS MEMBERS, SUM(TRANSCOUNT) AS TRANS FROM (
SELECT DISTINCT
MEMBERID,
(SELECT COUNT(*) TRANSDATE WHERE TRANSDATA.MEMBERID = MEMBER.MEMBERIF) AS TRANSCOUNT
FROM MEMBER WHERE NOT EXISTS
(SELECT * FROM TRANSDATA, MEMBER WHERE
TRANSDATA.MEMBERID = MEMBER.MEMBERIF
AND TRANDATE > #CUTOFF)
)