If I want to make sure an operation can only be done once a week by a user, what is an efficient way to do it? If a user has carried out that operation on Friday, then he can still do it on Tuesday, because it's "next" week.
Your users do not have direct access to your database server, there is a UI through which this operation is performed... correct?
Therefore, the efficient way is to have an "OperationLastPerformed" (of type `datetime) column in your table and to populate that field when the operation is performed.
At that point, which ever programming language is used for your UI, it will be easy (and proper) to enforce that piece of business logic from your code...
If that is not acceptable and this must be done from the backend you could create a trigger that would check the "OperationLastPerformed" field before commiting the record and if the datetime is within current week rollback the commit...
Create a table that stores a history of user actions:
CREATE TABLE UserActions (
userId int,
weekOfYear int,
year int
)
To check if the user has performed an action during the same week, you can use the DATEPART function to determine the week:
IF EXISTS (SELECT * FROM UserActions WHERE userId = #userID and weekOfYear = DATEPART(isowk, GetDate() AND year = Year(GetDate())
If that returns NULL, the user hasn't performed any actions in the current week. Then, you could insert a row after the action is performed.
INSERT INTO UserActions (userId, weekOfYear int, year)
VALUES (#userId, DATEPART(isowk, GetDate(), Year(GetDate())
In a real general sense you can do this. It assumes a History Table with at least one column [Last_Date_Ran]
Begin process
check the history table for a date within current Sun-Sat
If a record extists in step 2 exit else perform process.
Related
I am developing a real-time auction site for a school project. We can't make any changes to the design of the database.
The 'Item' table has a column for the expiration date (the day the auction expires) and the expiration time (the exact time at which the auction expires). It also has a column that indicates whether the auction is opened or closed. This [AuctionClosed?] column needs to be updated when the expiration date and time are reached, which has to happen in real-time.
We're using an SQL Server database and the website runs on PHP7. The only possible solution I've found is to run a job every second, but this is too much overhead.
This is the function I want to use to check the column:
CREATE FUNCTION dbo.fn_isAuctionClosed (#Item BIGINT)
RETURNS BIT
AS
BEGIN
DECLARE #expirationDay DATE = (SELECT expirationDate FROM Item WHERE itemId = #Item)
DECLARE #expirationTime TIME = (SELECT expirationTime FROM Item WHERE itemId = #Item)
IF
DATE(GETDATE()) = #expirationDay AND TIME(GETDATE()) >= #expirationTime
OR
DATE(GETDATE()) > #expirationDay
RETURN 1
RETURN 0
END
And this is the procedure that updates the column:
CREATE PROCEDURE updateAuctionClosed #Item BIGINT
AS
UPDATE Item
SET [AuctionClosed?] = fn_isAuctionClosed(#Item)
WHERE itemId = #Item
To be more specific, what you really want here is a calculated column. Like I said in the comments, as the column will rely on the current date and time, the column won't be deterministic. This means it can't be PERSISTED but would be calculated every time the column is referenced (A PERSISTED column actually has it's value stored and is calculated when the row is effected in some way and restored). Even so, it can be calculated as follows:
ALTER TABLE Item DROP COLUMN [AuctionClosed?]; --You can't alter a column to a computed column, so we have to DROP it first
ALTER TABLE Item ADD [AuctionClosed?] AS CASE WHEN CONVERT(datetime,expirationDate) + CONVERT(datetime, expirationTime) > GETDATE() THEN 1 ELSE 0 END;
On a side note, I recommend against special characters in an object's name. Stick to alphanumerical characters only, and (if you must) underscores (_), as these don't force the object to be delimit identified.
I have a trigger on a table for insert, delete, update that on the first line gets the current date with GetDate() method.
The trigger will compare the deleted and inserted table to determine what field has been changed and stores in another table the id, datetime and the field changed. This combination must be unique
A stored procedure does an insert and an update sequentially on the table. Sometimes I get a violation of primary key and I suspect that the GetDate() returns the same value.
How can I make the GetDate() return different values in the trigger.
EDIT
Here is the code of the trigger
CREATE TRIGGER dbo.TR
ON table
FOR DELETE, INSERT, UPDATE
AS
BEGIN
SET NoCount ON
DECLARE #dt Datetime
SELECT #dt = GetDate()
insert tableLog (id, date, field, old, new)
select I.id, #dt, 'field', D.field, I.field
from INSERTED I LEFT JOIN DELETED D ON I.id=D.id
where IsNull(I.field, -1) <> IsNull(D.field, -1)
END
and the code of the calls
...
insert into table ( anotherfield)
values (#anotherfield)
if ##rowcount=1 SET #ID=##Identity
...
update table
set field = #field
where Id = #ID
...
Sometimes the GetDate() between the 2 calls (insert and update) takes 7 milliseconds and sometimes it has the same value.
That's not exactly full solution but try using SYSDATETIME instead and of course make sure that target table can store up datetime2 up to microseconds.
Note that you can't force different datetime regardless of precision (unless you will start counting up to ticks) as stuff can just happen at the same time wihthin given precision.
If stretching up to microseconds won't solve the issue on practical level, I think you will have to either redesign this logging schema (perhaps add identity column on top of what you have) or add some dirty trick - like make this insert in try catch block and add like microsecond (nanosecond?) in a loop until you insert successfully. Definitely not s.t. I would recommend.
Look at this answer: SQL Server: intrigued by GETDATE()
If you are inserting multiple ROWS, they will all use the same value of GetDate(), so you can try wrapping it in a UDF to get unique values. But as I said, this is just a guess unless you post the code of your trigger so we can see what you are actually doing?
It sounds like you're trying to create an audit trail - but now you want to forge some of the entries?
I'd suggest instead adding a rowversion column to the table and including that in your uniqueness criteria - either instead of or as well as the datetime value that is being recorded.
In this way, even if two rows are inserted with identical date/time data, you can still tell the actual insertion order.
I am trying to constrain a SQL Server Database by a Start Date and End Date such that I can never double book a resource (i.e. no overlapping or duplicate reservations).
Assume my resources are numbered such that the table looks like
ResourceId, StartDate, EndDate, Status
So lets say I have resource #1. I want to make sure that I cannot have have the a reservation for 1/8/2017 thru 1/16/2017 and a separate reservation for 1/10/2017 - 1/18/2017 for the same resource.
A couple of more complications, a StartDate for a resource can be the same as the EndDate for the resource. So 1/8/1027 thru 1/16/2017 and 1/16/2017 thru 1/20/2017 is ok (i.e., one person can check in on the same day another person checkouts).
Furthermore, the Status field indicates whether the booking of the resource is Active or Cancelled. So we can ignore all cancelled reservations.
We have protected against these overlapping or double booking reservations in Code (Stored Procs and C#) when saving but we are hoping to add an extra layer of protection by adding a DB Contraint.
Is this possible in SQL Server ?
Thanks in Advance
You can use a CHECK constraint to make sure startdate is on or before EndDate easily enough:
CONSTRAINT [CK_Tablename_ValidDates] CHECK ([EndDate] >= [StartDate])
A constraint won't help with preventing an overlapping date range. You can instead use a TRIGGER to enforce this by creating a FOR INSERT, UPDATE trigger that rolls back the transaction if it detects a duplicate:
CREATE TRIGGER [TR_Tablename_NoOverlappingDates] FOR INSERT, UPDATE AS
IF EXISTS(SELECT * from inserted INNER JOIN [MyTable] ON blah blah blah ...) BEGIN
ROLLBACK TRANSACTION;
RAISERROR('hey, no overlapping date ranges here, buddy', 16, 1);
RETURN;
END
Another option is to create a indexed view that finds duplicates and put a unique constraint on that view that will be violated if more than 1 record exists. This is usually accomplished with a dummy table that has 2 rows cartesian joined to an aggregate view that selects the duplicate id-- thus one record with a duplicate would return two rows in the view with the same fake id value that has a unique index.
I've done both, I like the trigger approach better.
Drawing from this answer here: Date range overlapping check constraint.
First, check to make sure there are not existing overlaps:
select *
from dbo.Reservation as r
where exists (
select 1
from dbo.Reservation i
where i.PersonId = r.PersonId
and i.ReservationId != r.ReservationId
and isnull(i.EndDate,'20990101') > r.StartDate
and isnull(r.EndDate,'20990101') > i.StartDate
);
go
If it is all clear, then create your function.
There are a couple of different ways to write the function, e.g. we could skip the StartDate and EndDate and use something based only on ReservationId like the query above, but I will use this as the example:
create function dbo.udf_chk_Overlapping_StartDate_EndDate (
#ResourceId int
, #StartDate date
, #EndDate date
) returns bit as
begin;
declare #r bit = 1;
if not exists (
select 1
from dbo.Reservation as r
where r.ResourceId = #ResourceId
and isnull(#EndDate ,'20991231') > r.StartDate
and isnull(r.EndDate,'20991231') > #StartDate
and r.[Status] = 'Active'
group by r.ResourceId
having count(*)>1
)
set #r = 0;
return #r;
end;
go
Then add your constraint:
alter table dbo.Reservation
add constraint chk_Overlapping_StartDate_EndDate
check (dbo.udf_chk_Overlapping_StartDate_EndDate(ResourceId,StartDate,EndDate)=0);
go
Last: Test it.
I'm writing an app that allows people to send SMS messages, and if we recognize the word they sent, we do something. The keywords we recognize (handle) change based on the date. For example, the church I work for always needs a lot of Easter volunteers, so for the two months before Easter, we want to specify 'easter' as a keyword, and after Easter, we want to disable it.
So the primary key needs to be <keyword, date-range>. I could set that up as <keyword, date-start, date-end>, but I want there to be a PK conflict (or at least some sort of constraint conflict) if I try to insert a new record with the same keyword and a date-start or date-end between another row's date-start and date-end.
What's my best course of action? Does SQL Server have this capability built in? Do I need to create some sort of custom type with .NET? Do I do this with a Primary Key or a secondary check constraint?
I always like optimizing for speed, but in truth, this app doesn't really need it.
Unless I'm missing something important I believe you can accomplish this using acheckconstraint paired with a function:
-- test table
CREATE TABLE keyword_ranges (Keyword varchar(10), Startdate date, Enddate date);
-- function that checks if ranges overlap
CREATE FUNCTION CheckKeywordRange
(#Keyword VARCHAR(10), #Startdate date, #Enddate date)
RETURNS int
AS
BEGIN
DECLARE #retval int
SELECT #retval = COUNT(*)
FROM keyword_ranges
WHERE keyword = #Keyword
AND
(#Startdate <= Enddate) and (#Enddate >= Startdate)
RETURN #retval
END;
GO
-- constraint that calls the function
ALTER TABLE keyword_ranges
ADD CONSTRAINT chkKeywordRange
CHECK (dbo.CheckKeywordRange(Keyword, Startdate, Enddate) = 1);
-- this insert will succeed
INSERT keyword_ranges VALUES ('Holiday', '2014-01-01', '2014-01-05')
-- this insert will conflict with the check and fail
INSERT keyword_ranges VALUES ('Holiday', '2014-01-03', '2014-01-07')
Sample SQL Fiddle
You'll probably need to enforce this at the application level, instead of the Sql Server level. Sql Server can enforce ranges, but only if each range evaluated to fixed canonical start times and lengths: (ie: always have 2 month ranges that always start on the first day of even months). If you want arbitrary start times or lengths, you're stuck doing it with application logic, or at best in a trigger.
SQL Server 2008: I've got a situation here in which I wish to read from a table and write a row under certain conditions. The problem is that I don't want another request coming it at exactly the same time and doing the same thing. I'll try to explain here:
Table Name: RequestQueue
Columns:
RequestID, StartDate, EndDate, RequestResult
Sample Data
1, 12/4/10 1:00pm, 12/4/10 1:02pm, Success
2, 12/4/10 1:04pm, 12/4/10 1:05pm, Success
3, 12/4/10 1:00pm, NULL, NULL
When a page loads in my app, I want it to look at this table and if there is a request still pending like (ID #3) it will not do anything. Otherwise, if there are no requests pending, it creates a new row with the ID and StartDate filled in.
The issue is that we could get into a situation where the page is loaded twice at almost exactly the same time. If they happen to both read from the table before the new row is produced, then I could get two new rows in there. I want to have some sort of query that reads from the table and if there are no requests pending, inserts the new row with the StartDate filled in. I want that query to run all the way before another page can even read from this table so I don't get the "double row" effect.
I might need "locking" or something, I googled that but haven't found something for my exact situation. I'm sure this could be a simple stored procedure I just need a push in the right direction here.
Thanks,
Robert
Assuming that you just want to block all concurrent access to the table you could just do.
BEGIN TRAN
DECLARE #StartDate datetime,
#EndDate datetime
SELECT TOP 1
#StartDate = StartDate,
#EndDate = EndDate
FROM RequestQueue WITH(TABLOCK,XLOCK)
ORDER BY RequestID DESC
IF #EndDate IS NULL
SELECT #StartDate AS 'StartDate'
ELSE
INSERT INTO RequestQueue (StartDate)
OUTPUT INSERTED.* /* Or use SCOPE_IDENTITY() instead*/
VALUES (GETDATE())
COMMIT
Alternatively you could just serialise access to the SELECT/INSERT code inside the specific procedure without taking an exclusive table lock by using sp_getapplock