sqlite3 trigger to auto-add new month record - triggers

I need to automatically insert a row in a stats table that is identified by the month number, if the new month does not exist as a row.
'cards' is a running count of individual IDs that stores a current value (gets reset at rollover time), a rollover count and a running total of all events on that ID
'stats keeps a running count of all IDs events, and how many rollovers occurred in a given month.
CREATE TABLE IDS (ID_Num VARCHAR(30), Curr_Count INT, Rollover_Count INT, Total_Count INT);
CREATE TABLE stats(Month char(10), HitCount int, RolloverCount int);
CREATE TRIGGER update_Tstats BEFORE UPDATE OF Total_Count ON IDS
WHEN 0=(SELECT HitCount from stats WHERE Month = strftime('%m','now'))
(Also tried a "IS NULL" at the other end of the WHEN clause...still no joy)
BEGIN
INSERT INTO stats (Month, HitCount, RolloverCount) VALUES (strftime('%m', 'now'),0,0);
END;
I did have it working to a point, but as rollover was updated twice per cycle (value changed up and down via SQL query I have in a python script), it gave me doubleups in the stats rollover count. So now I'm running a double query in my script. However, this all fall over if the current month number does not exist in the stats table.
All I need to do is check if a blank record exists for the current month for the python script UPDATE queries to run against, and if not, INSERT one. The script itself can't do a 'run once' type of query on initial runup, because it may run for days, including spanning a new month changeover.
Any assistance would be hugely appreciated.

To check whether a record exists, use EXISTS:
CREATE TRIGGER ...
WHEN NOT EXISTS (SELECT 1 FROM stats WHERE Month = ...)
BEGIN
INSERT INTO stats ...
END;

Related

Redshift insert a date value into a table

insert into table1 (ID,date)
select
ID,sysdate
from table2
assume i insert a record into table2 with value ID:1,date:2023-1-1
the expected result is update the ID of table1 base on the ID from table2 and update the value of date of table1 base on the sysdate from table2.
select *
from table1;
the expected result after running the insert statement will be
ID
date
1
2023-1-6
but what i get is:
ID
date
1
2023-1-1
I see a few possibilities based on the information given:
You say "the expected result is update the ID of table1 base on the ID from table2" and this begs the question - did ID = 1 exist in table1 BEFORE you ran the INSERT statement? If so are you expecting that the INSERT will update the value for ID #1? Redshift doesn't enforce or check uniqueness of primary keys and you would get 2 rows in the table1 in this case. Is this what is happening?
SYSDATE on Redshift provides the start timestamp of the current transaction, NOT the current statement. Have you had the current transaction open since the 1st?
You didn't COMMIT the results (or the statement failed) and are checking from a different session. It could also be that the transaction started before in the second session before the COMMIT completed. Working with MVCC across multiple sessions can trip anyone up.
There are likely other possible explanations. If you could provide DDL, sample data, and a simple test case so that others can recreate what you are seeing it would greatly narrow down the possibilities.

How to add unique constraint over a time range?

I have a table slot which has a start_time and end_time. I want no other slot to be created having the same start and end time. A unique constraint as shown in the schema below
CREATE TABLE slot(
id SERIAL PRIMARY KEY,
start_time TIMETZ NOT NULL,
end_time TIMETZ NOT NULL,
CONSTRAINT slot_start_end_unique UNIQUE (start_time,end_time)
);
can be easily bypassed by picking up one minute + or - time. I want to add a constraint so that no equivalent time slot can be created or a subset time slot cannot be created.
I am thinking of using check to prevent any practically same slot from being created.
Can anyone please point towards the right direction?
Your idea of using a check constraint as unique enforcement is can probable be made to work but there could be issues and should probably be avoided. Your requirement necessitates comparing with other rows in the table but
PostgreSQL does not support CHECK constraints that reference table
data other than the new or updated row being checked. ...
It goes on to indicate a custom trigger is best employed. So, that is the approach here. See Section 5.4.1. Check Constraints.
Beyond that you have a couple issues: First off the data type TIME WITH TIMEZONE (TIMETZ) is a poor choice for data type and is somewhat misleading as it not actually used as indicated as the. As Section 8.5.3. Time Zones puts it:
Although the date type cannot have an associated time zone, the time
type can. Time zones in the real world have little meaning unless
associated with a date as well as a time, ... PostgreSQL assumes
your local time zone for any type containing only date or time.
(emphases mine)
Secondly, by using time only you may have problems specifying some ranges. How, for example, do you code the range from 22:00 to 06:00 or 23:45 to 00:15. But now back to the process.
The following trigger assumes data type TIME rather than TIMETZ and adjusts for the over midnight issue by assuming 'the next day' whenever start_time is greater than end_time.
create or replace
function is_valid_irange()
returns trigger
language plpgsql
strict
as $$
declare
k_existing_message constant text =
'Range Requested (%s,%s). Overlaps existing range (%s,%s).';
l_existing_range tsrange;
l_parm_range tsrange;
begin
with p_times(new_start_time, new_end_time) as
( values ('1970-01-01'::timestamp + new.start_time
,'1970-01-01'::timestamp + new.end_time
)
)
select tsrange(new_start_time,end_time,'[)')
into l_parm_range
from (select new_start_time
, case when new_start_time>new_end_time
then new_end_time + interval '1 day'
else new_end_time
end end_time
from p_times
) pr;
with db_range (id, existing_range) as
( select id, tsrange(start_time, end_time, '[)')
from ( select id, '1970-01-01'::timestamp + start_time start_time
, case when start_time>end_time
then '1970-01-02'::timestamp + end_time
else '1970-01-01'::timestamp + end_time
end end_time
from irange
) dr
)
select d.existing_range
into l_existing_range
from db_range d
where l_parm_range && existing_range
and d.id != new.id
limit 1;
if l_existing_range is not null
then
raise exception 'Invalid Range Requested:'
using detail= format( k_existing_message
, lower(l_parm_range)
, upper(l_parm_range)
, lower(l_existing_range)::time
, upper(l_existing_range)::time
);
end if;
return new;
end ;
$$;
How it works:
Postgres provides a set of built in data range types and a set of range operator functions.
The trigger coheres the start and end times,both new row and existing table rows, into timestamps with a fixed date ( the beginning of time 1970-01-01 according to unix).
Then employs the Overlaps (&&) operator. If any overlaps are found the trigger raises and exception. Instead of an exception it could return null to suppress
the insert or update but otherwise continue processing. For that it needs to become a BEFORE trigger. It is currently an AFTER trigger.
For full example see here. Do not worry about the date, pick any you want, just used a a generator for calculating times and to provide a common base for testing.
Create the table as normal then before you INSERT data into the table perform a SELECT query to search whether or not the time you are looking to insert already exists. For example you want to enter start 1pm and end 2pm as such:
DECLARE #start_value INT = 1
#end_value INT = 2;
Select COUNT(ID) as UseCheck FROM slot WHERE start_time = #start_value or end_time = #end_value
Then apply logic to say; IF UseCheck > 0 Then do stuff

PostgreSQL auto-increment increases on each update

Every time I do an INSERT or UPSERT (ON CONFLICT UPDATE), the increments column on each table increments by the number of updates that came before it.
For instance, if I have this table:
id int4
title text
description text
updated_at timestamp
created_at timestamp
And then run these queries:
INSERT INTO notifications (title, description) VALUES ('something', 'whatever'); // Generates increments ID=1
UPDATE notifications title='something else' WHERE id = 1; // Repeat this query 20 times with different values.
INSERT INTO notifications (title, description) VALUES ('something more', 'whatever again'); // Generates increments ID=22
This is a pretty big issue. The script we are running processes 100,000+ notifications every day. This can create gaps between each insert on the order of 10,000, so we might start off with 100 rows but by the time we reach 1,000 rows we have an auto-incremented primary key ID value over 100000 for that last row.
We will quickly run out of auto-increment values on our tables if this continues.
Is our PostgreSQL server misconfigured? Using Postgres 9.5.3.
I'm using Eloquent Schema Builder (e.g. $table->increments('id')) to create the table and I don't know if that has something to do with it.
A sequence will be incremented whenever an insertion is attempted regardless of its success. A simple update (as in your example) will not increment it but an insert on conflict update will since the insert is tried before the update.
One solution is to change the id to bigint. Another is not to use a sequence and manage it yourself. And another is to do a manual upsert:
with s as (
select id
from notifications
where title = 'something'
), i as (
insert into notifications (title, description)
select 'something', 'whatever'
where not exists (select 1 from s)
)
update notifications
set title = 'something else'
where id = (select id from s)
This supposes title is unique.
You can reset auto increment column to max inserted value by run this command before insert command:
SELECT setval('notifications_id_seq', MAX(id)) FROM notifications;

How do I achieve exclusive OR in T-SQL?

I have a data table where there's a list of columns (boiled down to the pertinent ones for this example):
users(
usr_pkey int identity(1, 1) primary key,
usr_name nvarchar(64),
...,
)
accounts(
acc_pkey int identity(1, 1) primary key,
usr_key int foreign_key references users(usr_pkey),
acc_effective datetime,
acc_expires datetime,
acc_active bit,
...,
)
From this table I'm looking to grab all records where:
The account belongs to the specified user and
In the first instance:
the account is active and today's date falls between the account's effective and expiry date or
In the second instance:
if no records were identified by the first instance, the record with the most recent expiry date.
So - if an active record exists where today's date falls between the account's effective and expiry dates, I want that record. Only if no match was found do I want any account for this user having the most recent expiry date.
Unless something has radically changed in TSQL 2008, it's brute force.
select *
from table
where ( ( condition 1 OR condition 2)
AND NOT ( condition 1 AND condition 2) )
Here's one solution I've found:
select top 1 *
from accounts
where usr_key = #specified_user
order by
acc_active desc,
case
when getdate() between acc_effective and acc_expires then 0
else 1
end,
acc_expires desc
This would effectively order the records in the right priority sequence allowing me to pick the top one off the list
Strictly speaking, it doesn't achieve exclusive or, but it could be applied to this data set to achieve the same end.

Where/How to Create Reference Number

I'm using Entity Framework and MSSQL...
I need to insert a custom reference number when a record is inserted. The format is YYYY-01, YYYY-02, etc but the sequential number needs to be reset when a new year begins.
For example 2011-01, 2011-02, 2012-01
I'm curious if I should just go with a trigger or manage this with EF or ?
Having the sequential numbering reset each year has me a little confused...
Thanks for any advice!
Update:
Sorry, couldn't get the Code tag to work well with the markup
--Variables
DECLARE #year INT,
#seqNum INT;
--Try to find if the [ComplaintCount] table already contains the current year
SET #year = (SELECT [Count_Year]
FROM [ComplaintCount]
WHERE [Count_Year] = YEAR(Getdate()))
--If the current year cannot be found in the [ComplaintCount] table, a new record for the current year needs to be made
IF #year IS NULL
BEGIN
--Get the Current Year and set the initial sequence number to start counting for the new year
SET #year = YEAR(Getdate());
SET #seqNum = 1;
--Insert the new default values into the [ComplaintCount] table
INSERT INTO [ComplaintCount]
(count_year,
count_current)
VALUES (#year,
#seqNum);
END
ELSE
BEGIN
--We found a record already in the [ComplaintCount] table for the current year
--Get the sequence number and increase it by one
SET #seqNum = (SELECT [Count_Current]
FROM [ComplaintCount]
WHERE [Count_Year] = #year) + 1
--Insert the new values into the [ComplaintCount] table
UPDATE [ComplaintCount]
SET [Count_Current] = #seqNum
WHERE [Count_Year] = #year;
END
--Its now safe to insert the correct reference number into the [Complaint] table
UPDATE
UPDATE [Complaint]
SET [Complaint_Reference] = CAST(#year AS VARCHAR) + '-' + CAST(
#seqNum AS VARCHAR)
FROM [Complaint]
INNER JOIN inserted
ON [Complaint].[PK_Complaint_Id] = inserted.[PK_Complaint_Id]
I'd say a trigger. Create a two column table that stores the year and the current record number and then uses a trigger to look up the current year, increment the count column by one, then return that count to the trigger. Build logic into the trigger that if the new year doesn't exist, insert the new year record. I know most people like to avoid triggers if possible but that's a pretty legit use of a trigger and way less processing than trying to count records on every insert.
Having a single row for every year and it's related count may also prove useful in the future when you're trying to audit a past year or answer BI questions.