SQL set date_out automatically if today ends - sql

I have a visitor system. when the visitor checks in, it sets a date_in. and when the visitor checks out it sets a date_out.
but if a visitor forget to check out. it says he has no date_out so I try to figure out how to set the date_out if the visitor didn't check out.
an example:
Check in: 2015-01-19 12:00:00
visitor forget to check out that day.
so if date is: 2015-01-19 23:59:59. I want it to set it automatically on the date_out.
because with another query I ask all the visitor without a date_out to show. so I can see who is in the building that day.
is there any way to do this automatically?
Table structure
date_in is set when the visitor checks in with his name.

My suggestion - rather than editing the data (and subsequently not being able to identify genuine check-outs at 23:59), just update the query:
SELECT * /* TODO - actual columns */
FROM visits
WHERE
date_out IS NULL AND
date_in >= DATEADD(day,DATEDIFF(day,0,CURRENT_TIMESTAMP),0)
Where the expression DATEADD(day,DATEDIFF(day,0,CURRENT_TIMESTAMP),0) is just a way of saying "midnight at the start of today".
The above query should give you the same results as what you're asking for, but leave the actual recorded data intact. That is, only people who've entered today but not left are reported.
The reason I'd recommend not changing the recorded data is in case you decide to change the rules later - i.e. using a different interval than one day to consider a visitor to have left.

You could create a job that runs 23:59:59 every day that fills the date_out with data.

Assuming you have the data in a single row, you can just update the values periodically:
update visitors
set check_out = cast(convert(varchar(10), date_in, 121) + ' 23:59:59') as datetime)
where check_out is null;
This can be done at your leisure, rather than guaranteeing that some job run at a specific time.

Related

Finding the Closest Unbooked Dates Using SQL

Scenario
A user selects a date. Based on the selection I check whether the date & time is booked or not (No issues here).
If a date & time is booked, I need to show them n alternative dates. Based on their date and time parameters, and those proposed alternative dates have to be as close as to their chosen date as possible. The list of alternative dates should start from the date the query is ran on My backend handles this.
My Progress So Far
SELECT alternative_date
FROM GENERATE_SERIES(
TIMESTAMP '2022-08-20 05:00:00',
date_trunc('month', TIMESTAMP '2022-08-20 07:00:00') + INTERVAL '1 month - 1 day',
INTERVAL '1 day'
) AS G(alternative_date)
WHERE NOT EXISTS(
SELECT * FROM events T
WHERE T.bookDate::DATE = G.alternative_date::DATE
)
The code above uses the GENERATE_SERIES(...) function in PSQL. It searches for all dates, starting from 2022-08-20, and up to the end of August. It specifically returns the dates which does not exist in the bookDate column (Meaning it has not yet been booked).
Problems I Need Help With
When searching for alternative dates, I'm providing 3 important things
The user's preferred booking date, so I can suggest which other dates are close to him that he can choose? How would I go about doing this? It's the part where I'm facing most trouble.
The user's start and end times, so when providing a list of alternative dates, I can tell him, hey there's free space between 06 and 07 on the date 2022-08-22 for instance. I'm also facing some issues here, a push in the right track will be great!
I want to add another WHERE but it fails, the current WHERE is a NOT EXISTS so it looks for all dates not equaling to what is given. My other WHERE basically means WHERE the place is open for booking or not.
To get closest free dates, you can ORDER BY your result by "distance" of particular alternative date to user's preferred date - the shortest intervals will be first:
ORDER BY alternative_date - TIMESTAMP '2022-08-20 05:00:00'
If you want to recommend time slots smaller than whole dates (hour range), you need to switch the whole thing from dates to hours, i.e. generate_series from 1 day to 1 hour (or whatever your smallest bookable unit is) and excluse invalid hours (nighttime I assume) in WHERE. From there, it is pretty much the same as with dates.
As for "second where", there can be only one WHERE, but it can be composed from multiple conditions - you can add more conditions using AND operator (and it can also be sub-query if needed):
WHERE NOT EXISTS(
SELECT * FROM events T
WHERE T.bookDate::DATE = G.alternative_date::DATE
) AND NOT EXISTS (
SELECT 1 FROM events WHERE "roomId" = '13b46460-162d-4d32-94c0-e27dd9246c79'
)
(warning: this second sub-query is probably dangerous in real world, since the room will be used more than one time, I assume, so you need to add some time condition to the subquery to check against date)

Count combination + frequency

I have such an assignment. I believe my guess is correct, however I didn't find anything confirming my assumption how frequency works with count function.
What was the most popular bike route (start/end station combination) in DC’s bike-share program over the first 3 months of 2012? How many times was the route taken?
• duration seconds: duration of the ride (in seconds)
• start time, end time: datetimes representing the beginning and end of the ride
• start station, end station: name of the start and end stations for the ride
This is the code I wrote, wanted to see if my guess regarding most popular route (i believe it is a frequency) is correct with COUNT combination.
If someone can confirm if my guess is right, I will appreciate.
SELECT start_station, end_station, count(*) AS ct_route_taken
FROM tutorial.dc_bikeshare_q1_2012
GROUP BY start_station, end_station
ORDER BY ct_route_taken DESC
LIMIT 1;
Just count(*).
The name of the table would indicate that we need no WHERE clause.
If that's misleading and it covers a greater time interval, add a (proper!) WHERE clause like this:
WHERE start_time >= '2012-01-01'
AND start_time < '2012-04-01'
Your query would eliminate most of '2012-03-31', since start_time is supposed to be a "datetime" type. Depending on which type exactly and where the date of "the first 3 months" is supposed to be located, we might need to adjust for time zone also.
See:
https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_BETWEEN_.28especially_with_timestamps.29
Ignoring time zones altogether in Rails and PostgreSQL
From description and the query look like ok if the start station and end station description are same for each and every station. however without looking into the table data it is little difficult to confirm.

Oracle date datatype vs sysdate

In my table sales there is expiry date field which is of date type.
Now, I have to write query which selects all records having expiry date greater than current system date.
select * from Sales where expiry_date > sysdate;
in output I am getting all records with expiry date 31/12/9999 00:00:00,
which is not desired.
" in output i am getting all records with expiry date 31/12/9999 00:00:00 which is not desired."
But that's what your query asks for: you must admit that the year 9999 is greater than the current year.
So either your query is correct and you have misunderstood the requirement, or you need to re-write the query to explicitly exclude records with the maximum date.
Presumably in this case EXPIRY_DATE was defined as NOT NULL and it was too late to change it when somebody raised the matter of records which never expire. So instead we have a magic value of 31-12-9999 is ,which means of "these records do not expire".
Anyhow, here is the query now:
select * from Sales
where expiry_date > sysdate
and expiry_date != date '9999-12-31';
This is a common problem with magic values: they offer a quick fix for an architectural problem but levy an ongoing tax on application logic.
Ahh the end of time, a date so far in the future the system storing it is unlikely to ever encounter it for real. The problem with that is that systems have a nasty habit of lasting longer than expected in one form or another.
Be that as it may, it looks like you want to exclude the end of time since the assumption is that those records don't truly expire, but gosh darn it you needed some kind of date in there for that index to work the way you wanted it to.
So you either need to directly exclude records where the expiration date equals your end of time value, or use a range condition that does the same:
where expiry_date between sysdate and date '9999-12-30 23:59:59'

SQL count open tasks each day

I've tried to use the search function and found a similar question. However, I can't get it to work properly.
I have a simple table that shows incoming and ended e-mails (tasks). There's an ingoing and ended-date.
For analysis I want to show (historical) the number of open/unanswered e-mails for a given day back in time. The output should answer the question: "What was the number of open tasks yyyy-mm-dd?".
The number of open tasks a given day (yyyy-mm-dd) should be
Count WHERE Date_IN < yyyy-mm-dd
AND Date_END >= yyyy-mm-dd
OR Date_IN < yyyy-mm-dd AND Date_END Is Null
Hope this makes sense. It's a pretty simple "inventory report" where the inventory is tasks.
I've tried to draw an output-example (When Date_END Is Null then task is still open).

SQL - state machine - reporting on historical data based on changeset

I want to record user states and then be able to report historically based on the record of changes we've kept. I'm trying to do this in SQL (using PostgreSQL) and I have a proposed structure for recording user changes like the following.
CREATE TABLE users (
userid SERIAL NOT NULL PRIMARY KEY,
name VARCHAR(40),
status CHAR NOT NULL
);
CREATE TABLE status_log (
logid SERIAL,
userid INTEGER NOT NULL REFERENCES users(userid),
status CHAR NOT NULL,
logcreated TIMESTAMP
);
That's my proposed table structure, based on the data.
For the status field 'a' represents an active user and 's' represents a suspended user,
INSERT INTO status_log (userid, status, logcreated) VALUES (1, 's', '2008-01-01');
INSERT INTO status_log (userid, status, logcreated) VALUES (1, 'a', '2008-02-01');
So this user was suspended on 1st Jan and active again on 1st of February.
If I wanted to get a suspended list of customers on 15th January 2008, then userid 1 should show up. If I get a suspended list of customers on 15th February 2008, then userid 1 should not show up.
1) Is this the best way to structure this data for this kind of query?
2) How do I query the data in either this structure or in your proposed modified structure so that I can simply have a date (say 15th January) and find a list of customers that had an active status on that date in SQL only? Is this a job for SQL?
This can be done, but would be a lot more efficient if you stored the end date of each log. With your model you have to do something like:
select l1.userid
from status_log l1
where l1.status='s'
and l1.logcreated = (select max(l2.logcreated)
from status_log l2
where l2.userid = l1.userid
and l2.logcreated <= date '2008-02-15'
);
With the additional column it woud be more like:
select userid
from status_log
where status='s'
and logcreated <= date '2008-02-15'
and logsuperseded >= date '2008-02-15';
(Apologies for any syntax errors, I don't know Postgresql.)
To address some further issues raised by Phil:
A user might get moved from active, to suspended, to cancelled, to active again. This is a simplified version, in reality, there are even more states and people can be moved directly from one state to another.
This would appear in the table like this:
userid from to status
FRED 2008-01-01 2008-01-31 s
FRED 2008-02-01 2008-02-07 c
FRED 2008-02-08 a
I used a null for the "to" date of the current record. I could have used a future date like 2999-12-31 but null is preferable in some ways.
Additionally, there would be no "end date" for the current status either, so I think this slightly breaks your query?
Yes, my query would have to be re-written as
select userid
from status_log
where status='s'
and logcreated <= date '2008-02-15'
and (logsuperseded is null or logsuperseded >= date '2008-02-15');
A downside of this design is that whenever the user's status changes you have to end date their current status_log as well as create a new one. However, that isn't difficult, and I think the query advantage probably outweighs this.
Does Postgres support analytic queries? This would give the active users on 2008-02-15
select userid
from
(
select logid,
userid,
status,
logcreated,
max(logcreated) over (partition by userid) max_logcreated_by_user
from status_log
where logcreated <= date '2008-02-15'
)
where logcreated = max_logcreated_by_user
and status = 'a'
/
#Tony the "end" date isn't necessarily applicable.
A user might get moved from active, to suspended, to cancelled, to active again. This is a simplified version, in reality, there are even more states and people can be moved directly from one state to another.
Additionally, there would be no "end date" for the current status either, so I think this slightly breaks your query?
#Phil
I like Tony's solution. It seems to most approriately model the situation described. Any particular user has a status for a given period of time (a minute, an hour, a day, etc.), but it is for a duration, not an instant in time. Since you want to know who was active during a certain period of time, modeling the information as a duration seems like the best approach.
I am not sure that additional statuses are a problem. If someone is active, then suspended, then cancelled, then active again, each of those statuses would be applicable for a given duration, would they not? It may be a vey short duration, such as a few seconds or a minute, but they would still be for a length of time.
Are you concerned that a person's status can change multiple times in a given day, but you want to know who was active for a given day? If so, then you just need to more specifically define what it means to be active on a given day. If it is enough that they were active for any part of that day, then Tony's answer works well as is. If they would have to be active for a certain amount of time in a given day, then Tony's solution could be modified to simply determine the length of time (in hours, or minutes, or days), and adding further restrictions in the WHERE clause to retrieve for the proper date, status, and length of time in that status.
As for there being no "end date" for the current status, that is no problem either as long as the end date were nullable. Simply use something like this "WHERE enddate <= '2008-08-15' or enddate is null".