Access SQL Query: Comparing Date In Select Statement - sql

I have a problem that I simply cannot seem to figure out. I have a list of employees with different travel dates and I want to display all of them in a cascading list format. The problem is that I only want to see employees once, and only the date closest to today.
For example I could have 'Smith' in there multiple times with dates before and after today, as we also keep historical records. This means I can't just do min, as it will try and display a date before today, and max is too far forward.
The code example below ALMOST works. The problem is in the select statement. I want to show the minimum date after today, but instead it gives me 0's and -1's where the dates should be. There might just be another way to do this all together, but this is the only configuration that seems to allow the other information such as Site, Position, and Comments to be displayed correctly alongside it.
SELECT A.`Last Name` AS [Last Name], Min(A.`Date In`) > Now() AS [Date In], Max(B.Site) AS Site, Max(B.Position), Max(B.Comments) AS Comments
FROM Deployments AS A
INNER JOIN Deployments AS B ON A.ID = B.ID
GROUP BY A.`FSR Name`
HAVING (((Max(A.`Actual TEP IN`))>Now()));
I did a group by Name because I only want to see each individual once. If I don't add the table to itself with a join it gives a self reference error. This is my first time posting so I hope this makes sense! All help will be greatly appreciated!

Not sure what DB you're on, but in general, you need to return MIN(date) instead of the result of the comparison "Min(Date) > Now()" - I'm guessing this is where you're seeing 0's and -1's, since that would be the result of the comparison, when you want the minimum date value itself.
Also, if you are just wanting people who have a trip date in the future, just restrict your query with a WHERE clause, do a GROUP BY, and you get rid of the self-join. Also note that the example below aligns some discrepancies in your OP like where you're selecting based on "Last Name" but grouping on "FSR Name" - these things must be consistent, whichever field you're concerned about.
Example:
SELECT A.[FSR Name] AS [FSR Name],
Min(A.[Date In]) AS [Date In],
Max(A.Site) AS Site,
Max(A.Position) AS Position,
Max(A.Comments) AS Comments
FROM Deployments AS A
WHERE A.[Date In] > Now()
GROUP BY A.[FSR Name];
EDIT: If you need to make sure that Site,Position,Comments all came from the same row, you have to do something like one of these options:
If you have a Primary Key:
select * from Deployments A3 where A3.pk_value =
(select max(A2.pk_value) from Deployments A2
where A2.[Date In] =
(select Max([Date In]) from Deployments A where A.[FSR Name] = A2.[FSR Name])
and A2.[FSR Name] = A3.[FSR Name]
)
This guarantees you to get 1 row per FSR Name, even if there are multiple rows for that FSR with the same "latest" date.
Otherwise, you can leave out the secondary query dealing with the pk_value, but you run a risk of getting multiple rows for an FSR that has multiple records with the same "latest" date.
Note: when you get to queries this complex, running on a full-featured database (SQL Server, Oracle, anything but Access) allows you to use much more sophistication. For this example, "Windowing Functions" would give you the answer without as much wrangling. Not sure if you're stuck with Access for now, but consider this for the future, anyway.

Try something like this
Select A.LastName, A.DateIn, A.Site, A.Position, A.Comments
From deployments a
Where not exists (Select *
From deployments b
Where b.id <> a.id
and (abs(datediff(d, getdate(), a.datein))) > abs(datediff(d, getdate(), b.datein))
or abs(datediff(d, getdate(), a.datein)) = abs(datediff(d, getdate(), b.datein) and a.id > b.id))
Instead of the funny mins and maxes that you are using to try to get the row with the datein that is closest to today, try using datediff. With this function, you can specify what type of date or time value you are looking to compare (day, month, year, minute) and then find the difference between two different datetimes. In this case, I used getdate() to find the current date and time. Then, we want the datein with the least value for datediff, the datein that is closest to today. Datediff will return positive or negative values, so I used abs to get the absolute value of the result. I did this because it doesn't matter if the date is before today or after today.
Then we are looking in the deployment table. The subquery says that we should look at all the values which are not the current value. Then, find all the rows that have a smaller datediff than the current record. Also, find all the records that have the same datediff as the current record and a smaller id. We will only include the current record if there isn't anything that fits this criteria. It is a little weird to think about, but this type of query should help you find what you are looking for a lot easier. The only thing is that you will need to add criteria in the where clause of the subquery to determine which entries to compare. As it stands, this query will look at all of the entries in your deployments table and pull back the one row that has a datein closest to today. Since you want one row for each person, this will need few more specifications.

Related

Ms Access query previous day's date

How to bulid a query in Ms Access to include the day before amounts as an opening balance. So on running the query i enter 3/10/18 in the WorkDay parameter box and records for 3/10/18 and 2/10/18 is shown. The Table is setup as follows:
WorkDay....TranactionID....Amount
2/10/18......Opening........1000
2/10/18......Credit.........500
2/10/18.......Debit.........300
3/10/18.......Credit........700
3/10/18.......Debit.........200
So if I run the query for 3/10/18 it should return
WorkDay....TranactionID....Amount
2/10/18......[Expr].........800
3/10/18.......Credit........700
3/10/18.......Debit.........200
If you are using the GUI add DateAdd("d",-1,[MyDateParameter]) to the OR line under [MyDateParameter] in the Workday field.
For SQL WHERE statement you would use
WorkDay=[MyDateParameter] OR Workday=DateAdd("d",-1,[MyDateParameter])
Obviously substitute [MyDateParameter] with whatever your date parameter actually is.
First some notes about the request:
The desired results imposes different requirements for the current day vs the previous day, so there must be two different queries. If you want them in one result set, you would need to use a UNION.
(You could write a single SQL UNION query, but since UNION queries do not work at all with the visual designer, you are left to write and test the query without any advantages of the query Design View. My preference is therefore to create two saved queries instead of embedded subqueries, then create a UNION which combines the results of the saved queries.)
Neither the question, nor answers to comments indicate what to do with any exceptions, like missing dates, weekends, etc. The following queries take the "day before" literally without exception.
The other difficulty is that the Credit entries also have a positive amount, so you must handle them specially. If Credits were saved with negative values, the summation would be simple and direct.
QueryCurrent:
PARAMETERS [Which WorkDay] DateTime;
SELECT S.WorkDay, S.TransactionID, Sum(S.[Amount]) As Amount
FROM [SomeUnspecifiedTable] As S
WHERE S.WorkDay = [Which WorkDay]
GROUP BY S.WorkDay, S.TransactionID
QueryPrevious:
PARAMETERS [Which WorkDay] DateTime;
SELECT S.WorkDay, "[Expr]" As TransactionID,
Sum(IIF(S.TransactionID = "Credit", -1, 1) * S.[Amount]) As Amount
FROM [SomeUnspecifiedTable] As S
WHERE S.WorkDay = ([Which WorkDay] - 1)
GROUP BY S.WorkDay
Union query:
SELECT * FROM QueryCurrent
UNION
SELECT * FROM QueryPrevious
ORDER BY [WorkDay]
Notes about the solution:
You could also use DateAdd() function, but add/subtracting integers from dates defaults to a change of days.

Criteria in SQL/ms-access is only considering day of the month, not full date

NB: this is a follow up question from Syntax of MS Access/SQL sub-query including aggregate functions.
I am trying to produce a database to manage maintenance of equipment. I have two tables:
One (Inventory) containing details of each piece of equipment, including Purchase Date and Service Period,
One containing details of work done (WorkDone), including the date the work was carried out (Work Date).
I would like a query that displays the date that it should be next serviced. So far I have:
SELECT Max(NZ(DateAdd('m', i.[Service Period], w.[Work Date]),
DateAdd('m', i.[Service Period], i.[Purchase Date]))
) AS NextServiceDate, i.Equipement
FROM Inventory i LEFT JOIN WorkDone w ON i.ID = w.Equipment
GROUP BY i.Equipement
I would now like to order by NextServiceDate and only show entries where NextServiceDate is in the next week. However adding
HAVING (((Max(Nz(DateAdd('m',i.[Service Period],w.[Work Date]),DateAdd('m',i.[Service Period],i.[Purchase Date]))))<DateAdd('ww',1,Date()))
ORDER BY Max(Nz(DateAdd('m',i.[Service Period],w.[Work Date]),DateAdd('m',i.[Service Period],i.[Purchase Date])));
only shows when the day of the month is less than one week from now (e.g. if it is the 1st today it will show all entries where NextServiceDate occurs in the first 7 days of any month of any year, past or future). For some reason it is only considering the day of the month and not the full date. I don't understand why...
NB: I have a British system so date format is dd-mm-yyyy.
After a cursory review of your code, I'm unsure whether the instances of i.Equipement is a typo (since your JOIN clause refers to a similar field w.Equipment), or whether these two fields are intentionally named differently?
I can't see anything else immediately wrong with your code, but it may be clearer and easier to debug if you were to restructure the code to the following:
SELECT
Max(sub.dat) as NextServiceDate, sub.eqp
FROM
(
SELECT
DateAdd('m',i.[Service Period],Nz(w.[Work Date],i[Purchase Date])) as dat, i.Equipement as eqp
FROM
Inventory i LEFT JOIN WorkDone w ON i.ID = w.Equipment
) AS sub
GROUP BY
sub.eqp
HAVING
Max(sub.dat) < DateAdd('ww',1,Date())
ORDER BY
Max(sub.dat)
Note that the difference in regional date formats will only have an effect when you are specifying literal dates (for example, as criteria), in which case you would need to adhere to the format #mm/dd/yyyy#.

SQL statement selecting most recent date no longer working

I've had a sudden failure in one of my reporting routines and have traced it back to the having portion of my statement. The function this has been serving, up until 2 days ago, was selecting the most recent date from the dbo.data_feed_file table (column name: File_Date).
Statement follows
HAVING (dbo.data_feed_file.file_date = (Select MAX(File_Date) as Expr1
FROM dbo.data_feed_file AS data_feed_file_1))
First: is there an alternative way to write this? I've gotten my report working by removing the statement, it's just 2.5 million more lines than I want. I know I can hard code the date to pull just the specific date I want, but automation is obviously preferred.
Second: Does anyone know what could cause this to spontaneously fail? I'm the only person with access to edit this query so I know nothing was changed (no really, nothing changed).
Thanks in advance.
Edit: To add clarification: There is no error message, the column headers are showing up as anticipated but no data is populated, it's just blank fields (as though nothing met the having criteria). The statement completes as though there is nothing wrong. I've confirmed there are no NULL values in the File_Date column.
I can think of two reasons why no rows would return. The first is that the subquery is returning NULL. This is easily fixed as:
HAVING (dbo.data_feed_file.file_date = (Select MAX(File_Date) as Expr1
FROM dbo.data_feed_file AS data_feed_file_1
where file_date is not null))
The second is that File_Date is stored as a datetime, rather than a date. If so, you might have a where clause that filters out the most recent value, and be missing it in the having clause. If you intend dates, but the value is stored as a datetime, then you can try:
HAVING (cast(dbo.data_feed_file.file_date as date) =
(Select cast(MAX(File_Date) as date) as Expr1
FROM dbo.data_feed_file AS data_feed_file_1
where file_date is not null))

Is there a way to handle immutability that's robust and scalable?

Since bigquery is append-only, I was thinking about stamping each record I upload to it with an 'effective date' similar to how peoplesoft works, if anybody is familiar with that pattern.
Then, I could issue a select statement and join on the max effective date
select UTC_USEC_TO_MONTH(timestamp) as month, sum(amt)/100 as sales
from foo.orders as all
join (select id, max(effdt) as max_effdt from foo.orders group by id) as latest
on all.effdt = latest.max_effdt and all.id = latest.id
group by month
order by month;
Unfortunately, I believe this won't scale because of the big query 'small joins' restriction, so I wanted to see if anyone else had thought around this use case.
Yes, adding a timestamp for each record (or in some cases, a flag that captures the state of a particular record) is the right approach. The small side of a BigQuery "Small Join" can actually return at least 8MB (this value is compressed on our end, so is usually 2 to 10 times larger), so for "lookup" table type subqueries, this can actually provide a lot of records.
In your case, it's not clear to me what the exact query you are trying to run is.. it looks like you are trying to return the most recent sales times of every individual item - and then JOIN this information with the SUM of sales amt per month of each item? Can you provide more info about the query?
It might be possible to do this all in one query. For example, in our wikipedia dataset, an example might look something like...
SELECT contributor_username, UTC_USEC_TO_MONTH(timestamp * 1000000) as month,
SUM(num_characters) as total_characters_used FROM
[publicdata:samples.wikipedia] WHERE (contributor_username != '' or
contributor_username IS NOT NULL) AND timestamp > 1133395200
AND timestamp < 1157068800 GROUP BY contributor_username, month
ORDER BY contributor_username DESC, month DESC;
...to provide wikipedia contributions per user per month (like sales per month per item). This result is actually really large, so you would have to limit by date range.
UPDATE (based on comments below) a similar query that finds "num_characters" for the latest wikipedia revisions by contributors after a particular time...
SELECT current.contributor_username, current.num_characters
FROM
(SELECT contributor_username, num_characters, timestamp as time FROM [publicdata:samples.wikipedia] WHERE contributor_username != '' AND contributor_username IS NOT NULL)
AS current
JOIN
(SELECT contributor_username, MAX(timestamp) as time FROM [publicdata:samples.wikipedia] WHERE contributor_username != '' AND contributor_username IS NOT NULL AND timestamp > 1265073722 GROUP BY contributor_username) AS latest
ON
current.contributor_username = latest.contributor_username
AND
current.time = latest.time;
If your query requires you to use first build a large aggregate (for example, you need to run essentially an accurate COUNT DISTINCT) another option is to break this query up into two queries. The first query could provide the max effective date by month along with a count and save this result as a new table. Then, could run a sum query on the resulting table.
You could also store monthly sales records in separate tables, and only query the particular table for the months you are interested in, simplifying your monthly sales summaries (this could also be a more economical use of BigQuery). When you need to find aggregates across all tables, you could run your queries with multiple tables listed after the FROM clause.

SQL multiple join with count, having trouble

First of all this is a homework assignment so I'm looking for assistance, not solutions. Let me try to explain my schema. I have three tables we'll call users (with columns id and name), parties (with columns id, partydate, and user_id) and questions (with columns id, createdate, and user_id). My requirement is to show for every user the number of parties within the last year and questions created within the last year. At first I had something like this:
SELECT users.id, users.name,
COUNT(parties.id) AS numparties, COUNT(qustions.id) AS numquestions
FROM users
FULL JOIN parties ON users.id=parties.user_id
FULL JOIN questions ON users.id=questions.user_id
WHERE (parties.partydate > NOW() - interval '1 year' OR parties.partydate IS NULL)
OR (questions.createdate > NOW() - interval '1 year' OR questions.createdate IS NULL)
GROUP BY users.id, users.name
Now this works, almost! The problem is, if a user has no parties nor questions within the past year, they don't show up at all in the result. I want such a user to show up, I just want it to show them with 0 for each numparties and numquestions.
What I think I need here is some sort of conditional counting, where I only want to COUNT(parties.id) WHERE that party's partydate is within the past year, and the same for questions. I'm just unsure how to do that. I have a hacky-workaround way to do what I want, where I basically UNION the above query with a near identical copy of itself, except I use SUM(0) for numparties and numquestions and my WHERE statement is just where the date is <= instead of >. I feel this is not the best way to go about it.
Any pointers in the right direction? Thanks for the help!
Take a look at this: http://www.w3schools.com/sql/sql_join_left.asp. I think it might point you in the right direction.
Think I've got it with this:
SUM(CASE WHEN (parties.partydate > NOW() - interval '1 year') THEN 1 ELSE 0 END) as numparties
and just removed the WHERE clauses.
I think I'd resort to a subquery for this. Homework questions are fun to answer, I can heavily psuedo code this. Take the entire query you have and call it 'x'.
First thing you'll want is a list of all users regardless of how many questions they've asked.
Select distinct users.id,users.name from users
that will give you a full list of your users. The query you have above gives you there calls...so left join the two together.
Select (fields you want)
from users
left join (enter you query above here) x on x.id = users.id
Hopefully the logic here makes sense for you. Use one query to get the list of users and join that to the subquery to get their counts.
edit to add: this will bring back nulls anytime there are no records. You can make your select statement show nulls as 0's