Unique Index on two columns and vice versa - constraints

I have the following table in postgres 9.5:
CREATE TABLE public.test
(
id bigint NOT NULL DEFAULT nextval('test_id_seq'::regclass),
id1 integer,
id2 integer,
CONSTRAINT test_pkey PRIMARY KEY (id)
);
I want to add restrictions on both columns which only allows recordsets where
the new recordset (id1,id2) is not present and
the new recordset (id1,id2) is not present as (id2,id1) and
id1 and id2 of new recordset are not equal
It should lool like this:
id | id1 | id2
---------------
1 | 1 | 1 <-- invalid (violates restriction 3.)
2 | 1 | 2 <-- valid
3 | 1 | 2 <-- invalid (violates restriction 1.)
4 | 2 | 1 <-- invalid (violates restriction 2.)
5 | 3 | 1 <-- valid
6 | 3 | 2 <-- valid
For restriction 1, I have added:
ALTER TABLE test ADD CONSTRAINT test_id1_id2_unique UNIQUE(id1, id2);
But how to add constraints for 2. and 3.?
Final solution with help of a_horse_with_no_name:
CREATE TABLE public.test(
id bigint NOT NULL DEFAULT nextval('test_id_seq'::regclass),
id1 integer NOT NULL,
id2 integer NOT NULL,
CONSTRAINT test_pkey PRIMARY KEY (id),
CONSTRAINT test_check CHECK (id1 <> id2)
);
CREATE UNIQUE INDEX test_id1_id2_unique
ON public.test
USING btree
((LEAST(id1, id2)), (GREATEST(id1, id2)));

You can create a unique index to cover both 1. and 2.
create unique index on test ( least(id1,id2), greatest(id1,id2) );
For 3. you need a check constraint:
CREATE TABLE public.test
(
id bigint NOT NULL DEFAULT nextval('test_id_seq'::regclass),
id1 integer,
id2 integer,
constraint check_not_equal check (id1 is distinct from id2),
CONSTRAINT test_pkey PRIMARY KEY (id)
);
You probably want both ids to be NOT NULL as well. In that case you could also use check (id1 <> id2)

Related

Update the missing incremental number in a data set

In one of my tables I have a column called 'Priority' which is a number starting from 1. With new data the next number will be added to the new record.
When I deleted a data from the middle there will be a gap in priority. I want to run a plsql update statement so that the mising numbers will be replaced according the priority order that I had before.
original data
Priority | user
1 | A
2 | B
3 | C
4 | D
Then I delete the record B
Priority | user
1 | A
3 | C
4 | D
After Update it should be like
Priority | user
1 | A
2 | C
3 | D
You can do this without PL/SQL:
create table demo
( priority number, username varchar2(20) );
insert all
into demo values (2, 'A')
into demo values (4, 'B')
into demo values (9, 'C')
into demo values (10, 'D')
select * from dual;
merge into demo o
using ( select row_number() over (order by priority) as new_priority
, rowid as row_id
from demo ) n
on (n.row_id = o.rowid)
when matched then update set o.priority = n.new_priority;
select * from demo;
PRIORITY USERNAME
---------- --------------------
1 A
2 B
3 C
4 D

SQLite UPDATE now working or I have done wrong

Current table questions:
rowid: 1 | 2 | 3 | 4 | 5
id: 3 | 4 | 7 | 9 | 10
Trying to achieve the following:
rowid: 1 | 2 | 3 | 4 | 5
id: 1 | 2 | 3 | 4 | 5
I have tried many different variations of SQL without success, this is the latest I am testing:
UPDATE questions SET id = rowid;
Can someone please suggest how I solve this as I have googled and cannot find the solution?
I don't believe that your question completely encompasses all aspects of the issue.
In theory to have a table (the before table) where SELECT rowid, id results in
rowid: 1 | 2 | 3 | 4 | 5
id: 3 | 4 | 7 | 9 | 10
The id column must not be an alias of the rowid column (otherwise the values would be identical)
However, if the id column is an alias of the rowid column, the both columns would be the same so the before table above would not be as above.
As an example using :-
--<<<<<<<<<< WORKS >>>>>>>>>>
-- as ID is not an alias of the rowid column update changes id column
DROP TABLE IF EXISTS questionsv3;
CREATE TABLE IF NOT EXISTS questionsv3 (ID INTEGER);
INSERT INTO questionsv3 VALUES (3),(4),(7),(9),(10);
SELECT rowid, id FROM questionsv3;
UPDATE questionsv3 SET id = rowid;
SELECT rowid, id FROM questionsv3;
results in the expected result as per :-
First Select (before update) :-
Second Select (after update)
Other potential causes
rowid is not in fact the rowid as per SQLITE, but a conceptual idea that it should be 1,2,3 ...... (in which case using VACUUM, if there is no alias to the rowid, may result in the desired re-numbering of the rowid column, which if followed by the update may then result in the id being re-sequenced).
That the update is done within a transaction that hasn't been committed and is rolled back.
You may wish to consider the following permutations of different table creations (see comments) :-
-- as ID is an alias of rowid, then rowid is set according to ID so update does nothing
DROP TABLE IF EXISTS questionsv1;
CREATE TABLE IF NOT EXISTS questionsv1 (ID INTEGER PRIMARY KEY);
INSERT INTO questionsv1 VALUES (3),(4),(7),(9),(10);
SELECT rowid, id FROM questionsv1;
UPDATE questionsv1 SET id = rowid;
SELECT rowid, id FROM questionsv1;
-- as ID is an alias of the rowid column, then rowid is set according to the ID so update does nothing
DROP TABLE IF EXISTS questionsv2;
CREATE TABLE IF NOT EXISTS questionsv2 (ID INTEGER PRIMARY KEY AUTOINCREMENT);
INSERT INTO questionsv2 VALUES (3),(4),(7),(9),(10);
SELECT rowid, id FROM questionsv2;
UPDATE questionsv2 SET id = rowid;
SELECT rowid, id FROM questionsv2;
--<<<<<<<<<< WORKS >>>>>>>>>>
-- as ID is not an alias of the rowid column update changes id column
DROP TABLE IF EXISTS questionsv3;
CREATE TABLE IF NOT EXISTS questionsv3 (ID INTEGER);
INSERT INTO questionsv3 VALUES (3),(4),(7),(9),(10);
SELECT rowid, id FROM questionsv3;
UPDATE questionsv3 SET id = rowid;
SELECT rowid, id FROM questionsv3;
--<<<<<<<<<< WORKS >>>>>>>>>>
-- as ID is not an alias of rowid the ID column is updated accordingly
DROP TABLE IF EXISTS questionsv4;
CREATE TABLE IF NOT EXISTS questionsv4 (ID TEXT PRIMARY KEY); -- not an alias of rowid
INSERT INTO questionsv4 VALUES (3),(4),(7),(9),(10);
SELECT rowid, id FROM questionsv4;
UPDATE questionsv4 SET id = rowid;
SELECT rowid, id FROM questionsv4;
--<<<<<<<<<< FAILS >>>>>>>>>>
DROP TABLE IF EXISTS questionsv13;
CREATE TABLE IF NOT EXISTS questionsv13 (ID INTEGER PRIMARY KEY) WITHOUT ROWID;
INSERT INTO questionsv13 VALUES (3),(4),(7),(9),(10);
SELECT id FROM questionsv13;
UPDATE questionsv13 SET id = rowid; -- would fail no such column
SELECT id FROM questionsv13;

SQLite: How to insert with a new rowid after having deleted one?

Suppose my table is,
CREATE TABLE bookmark (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name STRING(64)
);
I have inserted three rows in this table, like this:
Id | Name
1 | Bookmark 1
2 | Bookmark 2
3 | Bookmark 3
Now If I delete the row 3, so I get this:
Id | Name
1 | Bookmark 1
2 | Bookmark 2
And now I insert a new row with name Bookmark 4, I get this:
Id | Name
1 | Bookmark 1
2 | Bookmark 2
3 | Bookmark 4
Should not I be expecting the following?
Id | Name
1 | Bookmark 1
2 | Bookmark 2
4 | Bookmark 4
Anyone knows how to do this?
Notice: It is not a SQLite behavior but more of a SQLAlchemy problem. As #CL. points out here, it REALLY DOES what I wanted.
Sorry folks, finally I've found out it is caused by SQLAlchemy 1.1.13, it is not setting AUTOINCREMENT field for SQLite even we have specified like following,
db = SQLAlchemy()
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
As I use Flask, when specify,
app.config['SQLALCHEMY_ECHO'] = True
The log shows that AUTOINCREMENT is actually not set.
After some digging, to do it for SQLite, in SQLAlchemy 1.1.13 (source),
class Bookmark(db.Model):
__tablename__ = "bookmark"
__table_args__ = {'sqlite_autoincrement': True}
For SQLAlchemy 1.2, the following may work (not tested),
id = db.Column(db.Integer, primary_key=True, sqlite_autoincrement=True)

How to get ID from table A to table B in trigger?

I have two tables, A & B:
TABLE A
id | name
TABLE B
id | name | fk_idA
I want to create trigger AFTER INSERT in TABLE B which updates fk_idA appropriate with the newest id from table A.
An example:
TABLE A
id | name
1 | Andrew
2 | David
TABLE B
id | name | fk_idA
1 | Photo1 | 2
If the ID column is autoincrementing, the latest is the largest one, i.e., the one returned by MAX:
CREATE TRIGGER DefaultAIsLatest
AFTER INSERT ON TableB
FOR EACH ROW
WHEN NEW.fk_idA IS NULL
BEGIN
UPDATE TableB
SET fk_idA = (SELECT MAX(id)
FROM TableA)
WHERE id = NEW.id;
END;

SQLite Compare two columns

I am creating a database for my Psych class and I am scoring a personality profile. I need to compare two test items and, if they match a condition, then copy into a separate table.
Example (pseudocode is between \)Sqlite3
INSERT INTO Scale
SELECT* FROM Questions
WHERE \\if Question 1 IS 'TRUE' AND Question 3 IS 'FALSE' THEN Copy this Question
and its response into the Scale table\\;
I have about 100 other questions that work like this. Sample format goes like this:
IF FirstQuestion IS value AND SecondQuestion IS value THEN
Copy both questions into the Scale TABLE.
---------- EDITED AFTER FIRST RESPONSE! EDITS FOLLOW-------------
Here is my TestItems table:
ItemID | ItemQuestion | ItemResponse
```````````````````````````````````````````````````
1 | Is the sky blue? | TRUE
2 | Are you a person? | TRUE
3 | 2 Plus 2 Equals Five | FALSE
What I want to do: If Question 1 is TRUE AND Question 3 is FALSE, then insert BOTH questions into the table 'Scale' (which is setup like TestItems). I tried this:
INSERT INTO Scale
SELECT * FROM TestItems
WHERE ((ItemID=1) AND (ItemResponse='TRUE'))
AND ((ItemID=3) AND (ItemResponse='FALSE'));
HOWEVER: The above INSERT copies neither.
The Resulting 'Scale' table should look like this:
ItemID | ItemQuestion | ItemResponse
```````````````````````````````````````````````````
1 | Is the sky blue? | TRUE
3 | 2 Plus 2 Equals Five | FALSE
There is nothing wrong with your query. You're just there:
INSERT INTO Scale
SELECT * FROM Questions
WHERE `Question 1` = 1 AND `Question 3` = 0;
Here 1 and 0 are values (in your first case, true and false). First of all you should ensure there are fields Question 1 and Question 3 in your Questions table. Secondly the column count as well as data types of Scale table should match Questions table. Otherwise you will have to do selectively choose the fields in your SELECT query.
Edit: To respond to your edit, I am not seeing an elegant solution. You could do this:
INSERT INTO Scale
SELECT * FROM TestItems WHERE ItemID = 1 AND ItemResponse = 'TRUE'
UNION
SELECT * FROM TestItems WHERE ItemID = 3 AND ItemResponse = 'FALSE'
WHERE (SELECT COUNT(*) FROM (
SELECT 1 FROM TestItems WHERE ItemID = 1 AND ItemResponse = 'TRUE'
UNION
SELECT * FROM TestItems WHERE ItemID = 3 AND ItemResponse = 'FALSE'
) AS t) >= 2
Your insert did not work because ItemID cant be both 1 and 3 at the same time. My solution gets the required records to be inserted into Scale table, but verifies both the record exists by checking the count. Additionally you could (should) do as below since this can be marginally more efficient (the above SQL was to clearly show the logic being used):
INSERT INTO Scale
SELECT * FROM TestItems WHERE ItemID = 1 AND ItemResponse = 'TRUE'
UNION
SELECT * FROM TestItems WHERE ItemID = 3 AND ItemResponse = 'FALSE'
WHERE (
SELECT COUNT(*)
FROM TestItems
WHERE ItemID = 1 AND ItemResponse = 'TRUE'
OR ItemID = 3 AND ItemResponse = 'FALSE'
) >= 2

Resources