How to guarantee a table with exactly one row and one column? - sql

Using PostgreSQL 11.5. I need to allow a table to only allow one row and one column in it (not 0 or 2, exactly 1). I feel like ADD CONSTRAINT would be useful but I'm not sure what to do.
I'd like to be able to update this row when needed.
Something to make the following valid:
foo
-----
bar
But not the following:
foo | baz
-----+-----
bar | bin
-----+-----
| gar

You can force a table to have only one row setting a CHECK constraint on your primary key (or any other coulmn that is unique and not nullable) like this:
ALTER TABLE <tablename> ADD CONSTRAINT [<name>] CHECK ([pk/unique field] = [constant value]);
Anyways, since you want to have an updatable field you would need to use a table with 2 columns. If you really need to have only one row (and selecting only foo or creating a view for this purpose is not an option), you could also use triggers on insert and delete (and reject any operations inside them by raising an exception). To avoid having an empty table (not sure if you need to), you will also need this trigger on delete, the constraint will not be strong enough. For triggers see here: https://www.postgresql.org/docs/current/triggers.html
To avoid any further ALTER TABLE commands (such as adding a column) and DROP TABLE, use an event trigger. For more information, have a look into the Documentation: https://www.postgresql.org/docs/current/event-triggers.html
For using PL/pgSQL with triggers, also have a look at https://www.postgresql.org/docs/current/plpgsql-trigger.html

Here's an example that might help you:
CREATE TABLE singleton (x SMALLINT NOT NULL UNIQUE CHECK (x=1), foo VARCHAR(10) NOT NULL);
INSERT INTO singleton (x, foo) VALUES (1,'one row');

Create a VIEW as superuser or some trusted role:
CREATE VIEW onerow AS SELECT 'bar' AS foo; -- defaults to type text
GRANT SELECT ON onerow TO public;
The creator is the owner. Or:
ALTER VIEW onerow OWNER TO postgres; -- or to a trusted role
Everybody can SELECT from it like from any other table.
Nobody can add or remove rows from a VIEW - or even change anything at all with DDL commands.
Only DML commands can change it, and only the owner or superusers are allowed to do so:
CREATE OR REPLACE VIEW onerow AS
SELECT 'bar1' AS foo;
Remember that, ultimately, a motivated superuser can do anything.
On an extremely busy system, the exclusive lock taken by CREATE OR REPLACE VIEW might be a problem. That's the only possible problem with this solution I can think of.
Alternatively, consider this closely related answer to allow at most one row:
How to allow only one row for a table?
You might do that and (as superuser):
ALTER TABLE onerow OWNER TO postgres;
REVOKE ALL ON onerow FROM public;
GRANT SELECT ON onerow TO public;
Superusers can still DELETE or TRUNCATE. You can prevent that, too, with a TRIGGER or RULE ...
Or you could add a general TRIGGER with RETURN NULL or a RULE with DO INSTEAD NOTHING for INSERT and DELETE ...
One single value might alternatively be provided by a function or by a "global variable". See:
Passing user id to PostgreSQL triggers
Is there a way to define a named constant in a PostgreSQL query?

Related

Sybase ASE: Add NOT NULL column without a DEFAULT fails. Why?

Consider the following empty (as in without rows) table:
CREATE TABLE my_table(
my_column CHAR(10) NOT NULL
);
Trying to add a NOT NULL column without a DEFAULT will fail:
ALTER TABLE my_table ADD my_new_column CHAR(10) NOT NULL;
Error:
*[Code: 4997, SQL State: S1000]
ALTER TABLE my_table failed.
Default clause is required in order to add non-NULL column 'my_new_column'.
But adding the column as NULL and then change it to be NOT NULL will work:
ALTER TABLE my_table ADD my_new_column CHAR(10) NULL;
ALTER TABLE my_table MODIFY my_new_column CHAR(10) NOT NULL;
Setting a default and then removing the default will work too:
ALTER TABLE my_table ADD my_new_column CHAR(10) DEFAULT '' NOT NULL;
ALTER TABLE my_table REPLACE my_new_column DEFAULT NULL;
What's the justification for this behavior? What is the database trying to do internally that adding the column directly fails? I have a feeling that it might have something to do with internal versioning but I can't find anything in this regard.
This is speculation. I am guessing that Sybase is being overly conservative. In general, you cannot add a new not null column with no default value to a table that has rows. This is true in all databases, because there is no way to populate the existing rows for the new column.
I am guessing that Sybase simply doesn't check if the table has rows, only if it exists. Clearly it is not doing the check for the alter.
This is only speculation, but I suspect it has to do the combination of needing to both acquire a lock on the whole table to guarantee continued compliance with the schema, and re-allocate space for the records.
Allowing a direct add of a NOT NULL column would compromise any existing records if there's no default value. Yes, we know the table is empty. And the database can (eventually) know the table is empty at execution time... but it can't really know the table is empty at execution plan compile time, because a row could be added while the execution plan is determined.
This means the database would need to generate the worst-possible execution plan, involving a lock on the entire table, for the query to run in a transactionally-safe way. Additionally, adding (or removing) a column causes extra work for the database because it needs to re-allocate any pages and rebuild indexes in order to account for the changed size of individual records.
Put the two together, and it becomes difficult to just rollback a failed query, because you may have actual pages in different states. For whatever reason, the developers chose not to allow this.
The other options allow you to simply fail the query if a bad row gets in the way and would violate the schema, because you're not re-sizing records within pages. It might even allow you to get away with some page and row locks, rather than full table locks.

How do I insert data from parameters into a table with a trigger after Update

I am new to triggers, I am trying one that would let me insert the reasons for the update into a log table after another table was updated updated. The problem is that I am trying to make the user type an input as it would do in a stored procedure but that doesn't work with triggers apparently.
Let's say we have the table, Users:
User ID | User
--------+-------
1 John
The result I want is the following:
Log_ID | Reason_Change
-------+------------------------------------------
1 John wanted to change his name to John25
Is that possible?
I am trying:
#Reason_Change VARCHAR(500)
In SQL Server, you can do this with an instead of trigger on a view. The idea is the following:
Create a view that contains the columns from users along with the change reason.
Define an instead of trigger on the view. This will allow an insert to include the change reason.
Only use the view for inserts.
Then, you can include the reason in the insert. The trigger would put all the other columns in users and update the logs table appropriately.
Such triggers are explained in the documentation.

How do I replace a table in Postgres?

Basically I want to do this:
begin;
lock table a;
alter table a rename to b;
alter table a1 rename to a;
drop table b;
commit;
i.e. gain control and replace my old table while no one has access to it.
Simpler:
BEGIN;
DROP TABLE a;
ALTER TABLE a1 RENAME TO a;
COMMIT;
DROP TABLE acquires an ACCESS EXCLUSIVE lock on the table anyway. An explicit LOCK command is no better. And renaming a dead guy is just a waste of time.
You may want to write-lock the old table while preparing the new, to prevent writes in between. Then you'd issue a lock like this earlier in the process:
LOCK TABLE a IN SHARE MODE;
What happens to concurrent transactions trying to access the table? It's not that simple, read this:
Best way to populate a new column in a large table?
Explains why you may have seen error messages like this:
ERROR: could not open relation with OID 123456
Create SQL-backup, make changes you need directly at the backup.sql file and restore database. I used this trick when have added INHERIT for group of tables (Postgres dbms) to remove inherited fields from subtable.
I would use answer#13, but I agree, it will not inherit the constraints, and drop table might fail
line up the relevant constraints first (like from pg_dump --schema-only,
drop the constraints
do the swap per answer#13
apply the constraints (sql snippets from the schema dump)

Allow insertion only from within a trigger

I'm new to SQL programming, and I couldn't find an answer to this question online.
I'm working with pl/pgsql and I wish to achieve the following result:
I have a table A with certain attributes.
I am supposed to keep this table updated at any time - thus whenever a change was made that can affect A's values (in other tables B or C which are related to A) - a trigger is fired which updates the values (in the process - new values can be inserted into A, as well as old values can be deleted).
At the same time, I want to prevent from someone insert values into A.
What I want to do is to create a trigger which will prevent insertion into A (by returning NULL) - but I don't want this trigger to be called when I'm doing the insertion from another Trigger - so eventually - insertion to A will only be allowed from within a specific trigger.
As I said before, I'm new to SQL, and I don't know if this is even possible.
Yes, totally possible.
1. Generally disallow UPDATE to A
I would operate with privileges:
REVOKE ALL ON TABLE A FROM public; -- and from anybody else who might have it
That leaves superusers such as postgres who ignore these lowly restrictions. Catch those inside your trigger-function on A with pg_has_role():
IF pg_has_role('postgres', 'member') THEN
RETURN NULL;
END IF;
Where postgres is an actual superuser. Note: this catches other superusers as well, since they are member of every role, even other superusers.
You could catch non-superusers in a similar fashion (alternative to the REVOKE approach).
2. Allow UPDATE for daemon role
Create a non-login role, which is allowed to update A:
CREATE ROLE a_update NOLOGIN;
-- GRANT USAGE ON SCHEMA xyz TO a_update; -- may be needed, too
GRANT UPDATE ON TABLE A TO a_update;
Create trigger functions on tables B and C, owned by this daemon role and with SECURITY DEFINER. Details:
Is there a way to disable updates/deletes but still allow triggers to perform them?
Add to the trigger function on A:
IF pg_has_role('postgres', 'member') THEN
RETURN NULL;
ELSIF pg_has_role('a_update', 'member') THEN
RETURN NEW;
END IF;
For simple 1:1 dependencies, you can also work with foreign key constraints (additionally) using ON UPDATE CASCADE.

Define FK where target table does not exists

When I create table that has definition of FK's directly in CREATE command and target table does not exists yet, results in error.
Can checking, if target table exists, be somehow suspended?
my DBMS is Postgres.
Example (pseudocode):
create table "Bar"
foo_id integer FK of "Foo"."id",
someattr text;
create table "Foo"
id integer;
Example is in wrong order, thats why it wont run.
I'm trying to recreate databse in batch, based on definitions in many sql files.
When I create table that has definition of FK's directly in CREATE command and target table does not exists yet, results in error.
Can checking, if target table exists, be somehow suspended?
The best ways to deal with this are likely:
Create your tables in the correct order, or
Create the constraints
outside the table creation, after all tables are created.
Brute force is always an option.
Keep on running your DDL scripts in until you get a run with no errors.
More elegance requires a sequential structuring of your scripts.
Adding existence checks is possible, but I am not too familiar with the postgres metadata.