How to insert data from one table into another as PostgreSQL array? - sql

I have the following tables:
CREATE TABLE "User" (
id integer DEFAULT nextval('"User_id_seq"'::regclass) PRIMARY KEY,
name text NOT NULL DEFAULT ''::text,
coinflips boolean[]
);
CREATE TABLE "User_coinflips_COPY" (
"nodeId" integer,
position integer,
value boolean,
id integer DEFAULT nextval('"User_coinflips_COPY_id_seq"'::regclass) PRIMARY KEY
);
I'm no looking for the SQL statement that grabs the value entry from each row in User_coinflips and inserts it as an array into the coinflips column on User.
Any help would be appreciated!
Update
Not sure if it's important but I just realized a minor mistake in my table definitions above, I replace User_coinflips with User_coinflips_COPY since that accurately describes my schema. Just for context, before it looked like this:
CREATE TABLE "User_coinflips" (
"nodeId" integer REFERENCES "User"(id) ON DELETE CASCADE,
position integer,
value boolean NOT NULL,
CONSTRAINT "User_coinflips_pkey" PRIMARY KEY ("nodeId", position)
);

You are looking for an UPDATE, rather then insert.
Use a derived table with the aggregated values to join against in the UPDATE statement:
update "User"
set conflips = t.flips
from (
select "nodeId", array_agg(value order by position) as flips
from "User_coinflips"
group by "nodeId"
) t
where t."nodeId" = "User"."nodeId";

Related

How do I check the value of a foreign key on insert?

I'm teaching myself SQL using Sqlite3, well suited for my forever-game project (Don't we all have one?) and have the following tables:
CREATE TABLE equipment_types (
row_id INTEGER PRIMARY KEY,
type TEXT NOT NULL UNIQUE);
INSERT INTO equipment_types (type) VALUES ('gear'), ('weapon');
CREATE TABLE equipment_names (
row_id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE);
INSERT INTO equipment_names (name) VALUES ('club'), ('band aids');
CREATE TABLE equipment (
row_id INTEGER PRIMARY KEY,
name INTEGER NOT NULL UNIQUE REFERENCES equipment_names,
type INTEGER NOT NULL REFERENCES equipment_types);
INSERT INTO equipment (name, type) VALUES (1, 2), (2, 1);
So now we have a 'club' that is a 'weapon', and 'band aids' that are 'gear'. I now want to make a weapons table; it will have an equipment_id that references the equipment table and weapon properties like damage and range, etc. I want to constrain it to equipment that is a 'weapon' type.
But for the life of me I can't figure it out. CHECK, apparently, only allows expressions, not subqueries, and I've been trying to craft a TRIGGER that might do the job, but in short, I can't quite figure out the query and syntax, or how to check the result that as I understand it will be in the form of a table, or null.
Also, are there good online resources for learning SQL more advanced than W3School? Add them as a comment, please.
Just write a query that looks up the type belonging to the new record:
CREATE TRIGGER only_weapons
BEFORE INSERT ON weapons
FOR EACH ROW
WHEN (SELECT et.type
FROM euqipment_types AS et
JOIN equipment AS e ON e.type = et.equipment_type_id
WHERE e.row_id = NEW.equipment_id
) != 'weapon'
BEGIN
SELECT RAISE(FAIL, "not a weapon");
END;
The foreign key references should be to the primary key and to the same time. I would phrase this as:
CREATE TABLE equipment_types (
equipment_type_id INTEGER PRIMARY KEY,
type TEXT NOT NULL UNIQUE
);
INSERT INTO equipment_types (type) VALUES ('gear'), ('weapon');
CREATE TABLE equipment_names (
equipment_name_id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
INSERT INTO equipment_names (name) VALUES ('club'), ('band aids');
CREATE TABLE equipment (
equipment_id INTEGER PRIMARY KEY,
equipment_name_id INTEGER NOT NULL UNIQUE REFERENCES equipment_names(equipment_name_id),
equipement_type_id INTEGER NOT NULL REFERENCES equipment_types(equipement_type_id)
);
I would not use the name row_id for the primary key. That is the built-inn default, so the name is not very good. In SQLite, an integer primary key is automatically auto-incremented (see here).

Converting PL/SQL code to SQLite

I would like to know how can I convert the following block of Oracle PL/SQL code into SQLite so that it can be used in an Objective C program:
SELECT CUSTOMERS_ID_SEQ.NEXTVAL
INTO V_CUSTOMER_ID
FROM DUAL;
where V_CUSTOMER_ID is CUSTOMER_ID%TYPE NOT NULL, and
CUSTOMER_ID is integer type in table.
SQLite does not have sequences.
To get an autoincrementing ID, you have to use an INTEGER PRIMARY KEY column.
CL is right.
Short answer: A column declared INTEGER PRIMARY KEY will autoincrement.
Long answer: If you declare a column of a table to be INTEGER PRIMARY KEY, then whenever you insert a NULL into that column of the table, the NULL is automatically converted into an integer which is one greater than the largest value of that column over all other rows in the table, or 1 if the table is empty. (If the largest possible integer key, 9223372036854775807, then an unused key value is chosen at random.) For example, suppose you have a table like this:
CREATE TABLE t1(
a INTEGER PRIMARY KEY,
b INTEGER
);
With this table, the statement
INSERT INTO t1 VALUES(NULL,123);
is logically equivalent to saying:
INSERT INTO t1 VALUES((SELECT max(a) FROM t1)+1,123);
There is a function named sqlite3_last_insert_rowid() which will return the integer key for the most recent insert operation.
Check the following for FAQ help on SQLite: http://sqlite.org/faq.html#q1

What options are available for applying a set level constraint in PostgreSQL?

I have a situation where I need to ensure that there is only one active record with the same object_id and user_id at any time. Here is a representative table:
CREATE TABLE actions (
id SERIAL PRIMARY KEY,
object_id integer,
user_id integer,
active boolean default true,
created_at timestamptz default now()
);
By only one active record at a time, I mean you could have a sequence of inserts like the following:
insert into actions (object_id, user_id, active) values (1, 1, true);
insert into actions (object_id, user_id, active) values (1, 1, false);
but doing a subsequent
insert into actions (object_id, user_id, active) values (1, 1, true);
should fail because at this point in time, there already exists 1 active tuple with object_id = 1 and user_id = 1.
I'm using PostgreSQL 8.4.
I saw this post which looks interesting, but its Oracle specific.
I also saw this post but it requires more care regarding the transaction isolation level. I don't think it would work as-is in read committed mode.
My question is what other options are available to unsure this kind of constraint?
Edit: Removed the third insert in the first set. I think it was confusing the example. I also added the created_at time stamp to help with the context. To reiterate, there can be multiple (object_id, user_id, false) tuples, but only one (object_id, user_id, true) tuple.
Update: I accepted Craig's answer, but for others who may stumble upon something similar, here is another possible (though suboptimal) solution.
CREATE TABLE action_consistency (
object_id integer,
user_id integer,
count integer default 0,
primary key (object_id, user_id),
check (count >= 0 AND count <= 1)
);
CREATE OR REPLACE FUNCTION keep_action_consistency()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF NEW.active THEN
UPDATE action_consistency
SET count = count + 1
WHERE object_id = NEW.object_id AND
user_id = NEW.user_id;
INSERT INTO action_consistency (object_id, user_id, count)
SELECT NEW.object_id, NEW.user_id, 1
WHERE NOT EXISTS (SELECT 1
FROM action_consistency
WHERE object_id = NEW.object_id AND
user_id = NEW.user_id);
ELSE
-- assuming insert will be active for simplicity
UPDATE action_consistency
SET count = count - 1
WHERE object_id = NEW.object_id AND
user_id = NEW.user_id;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER ensure_action_consistency AFTER INSERT OR UPDATE ON actions
FOR EACH ROW EXECUTE PROCEDURE keep_action_consistency();
It requires the use of a tracking table. For what I hope are obvious reasons, this is not at all desirable. It means that you have an additional row each distinct (object_id, user_id) in actions.
Another reason why I accepted #Craig Ringer's answer is that there are foreign key references to actions.id in other tables that are also rendered inactive when a given action tuple changes state. This why the history table is less ideal in this scenario. Thank you for the comments and answers.
Given your specification that you want to limit only one entry to being active at a time, try:
CREATE TABLE actions (
id SERIAL PRIMARY KEY,
object_id integer,
user_id integer,
active boolean default true,
created_at timestamptz default now()
);
CREATE UNIQUE INDEX actions_unique_active_y ON actions(object_id,user_id) WHERE (active = 't');
This is a partial unique index, a PostgreSQL specific feature - see partial indexes. It constrains the set such that only one (object_id,user_id) tuple may exist where active is true.
While that strictly answers your question as you explained further in comments, I think wildplasser's answer describes the more correct choice and best approach.
You can use UNIQUE constraint to ensure that the column contains the unique value...
Here, set of object_id and user_id have been made unique....
CREATE TABLE actions (
id SERIAL PRIMARY KEY,
object_id integer,
user_id integer,
active boolean default true,
UNIQUE (object_id , user_id )
);
Check Out SQLFIDDLE
Similary, if you want to make set of object_id,user_id and active as UNIQUE, you can simply add the column name in the list of UNIQUE.
CREATE TABLE actions (
id SERIAL PRIMARY KEY,
object_id integer,
user_id integer,
active boolean default true,
UNIQUE (object_id , user_id,active )
);
Check Out SQLFIDDLE
Original:
CREATE TABLE actions (
id SERIAL PRIMARY KEY,
object_id integer,
user_id integer,
active boolean default true
);
my version:
CREATE TABLE actions (
object_id integer NOT NULL REFERENCES objects (id),
user_id integer NOT NULL REFERENCES users(id),
PRIMARY KEY (user_id, object_id)
);
What are the differences:
omitted the surrogate key. It is useless, it enforces no constraint, and nobody will ever reference it
added a (composite) primary key, which happens to be the logical key
changed the two fields to NOT NULL, and made them into foreign keys (what would be the meaning of a row that would not exist in the users or objects table?
removed the boolean flag. What is the semantic difference between a {user_id,object_id} tuple that does not exist versus one that does exist but has it's "active" flag set to false? Why create three states when you only need two?

How to combine particular rows in a pl/pgsql function that returns set of a view row type?

I have a view, and I have a function that returns records from this view.
Here is the view definition:
CREATE VIEW ctags(id, name, descr, freq) AS
SELECT tags.conc_id, expressions.name, concepts.descr, tags.freq
FROM tags, concepts, expressions
WHERE concepts.id = tags.conc_id
AND expressions.id = concepts.expr_id;
The column id references to the table tags, that, references to another table concepts, which, in turn, references to the table expressions.
Here are the table definitions:
CREATE TABLE expressions(
id serial PRIMARY KEY,
name text,
is_dropped bool DEFAULT FALSE,
rank float(53) DEFAULT 0,
state text DEFAULT 'never edited',
UNIQUE(name)
);
CREATE TABLE concepts(
id serial PRIMARY KEY,
expr_id int NOT NULL,
descr text NOT NULL,
source_id int,
equiv_p_id int,
equiv_r_id int,
equiv_len int,
weight int,
is_dropped bool DEFAULT FALSE,
FOREIGN KEY(expr_id) REFERENCES expressions,
FOREIGN KEY(source_id),
FOREIGN KEY(equiv_p_id) REFERENCES concepts,
FOREIGN KEY(equiv_r_id) REFERENCES concepts,
UNIQUE(id,equiv_p_id),
UNIQUE(id,equiv_r_id)
);
CREATE TABLE tags(
conc_id int NOT NULL,
freq int NOT NULL default 0,
UNIQUE(conc_id, freq)
);
The table expressions is also referenced from my view (ctags).
I want my function to combine rows of my view, that have equal values in the column name and that refer to rows of the table concepts with equal values of the column equiv_r_id so that these rows are combined only once, the combined row has one (doesn't matter which) of the ids, the value of the column descr is concatenated from the values of the rows being combined, and the row freq contains the sum of the values from the rows being combined. I have no idea how to do it, any help would be appreciated.
Basically, what you describe looks like this:
CREATE FUNCTION f_test()
RETURNS TABLE(min_id int, name text, all_descr text, sum_freq int) AS
$x$
SELECT min(t.conc_id) -- AS min_id
,e.name
,string_agg(c.descr, ', ') -- AS all_descr
,sum(t.freq) -- AS sum_freq
FROM tags t
JOIN concepts c USING (id)
JOIN expressions e ON e.id = c.expr_id;
-- WHERE e.name IS DISTINCT FROM
$x$
LANGUAGE sql;
Major points:
I ignored the view ctags altogether as it is not needed.
You could also write this as View so far, the function wrapper is not necessary.
You need PostgreSQL 9.0+ for string_agg(). Else you have to substitute with
array_to_string(array_agg(c.descr), ', ')
The only unclear part is this:
and that refer to rows of the table concepts with equal values of the column equiv_r_id so that these rows are combined only once
Waht column exactly refers to what column in table concepts?
concepts.equiv_r_id equals what exactly?
If you can clarify that part, I might be able to incorporate it into the solution.

Can these three SQLITE INSERTS be combinded or improved?

I have three tables:
CREATE TABLE "local" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , "serialNumber" TEXT, "location" TEXT)
CREATE TABLE "setups" ("id" INTEGER PRIMARY KEY NOT NULL ,"hold" TEXT,"mode" INTEGER,"setTemp" REAL,"maxSTemp" REAL,"minSTemp" REAL,"units" TEXT,"heat" INTEGER,"heatMode" INTEGER,"fanMode" INTEGER,"fan" INTEGER,"cool" INTEGER)
CREATE TABLE "data" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,"humidity" REAL,"time" INTEGER,"filtChng" INTEGER,"indoorTemp" REAL,"outdoorTemp" REAL, "setups_id" INTEGER, "local_id" INTEGER)
Everytime I get a new entry I execute:
INSERT INTO local ('serialNumber') SELECT 'XXXX' WHERE NOT EXISTS (SELECT * FROM local WHERE serialNumber='XXXX')"
INSERT INTO setups ('hold','mode','setTemp','maxSTemp','minSTemp','units','heat','heatMode','fanMode','fan','cool') SELECT '00',1,74.0,74.0,74.0,'F',1,1,1,1,1 WHERE NOT EXISTS (SELECT * FROM setups WHERE hold='00' AND mode=1 AND setTemp=74.0 AND maxSTemp=74.0 AND minSTemp=74.0 AND units='F' AND heat=1 AND heatMode=1 AND fanMode=1 AND fan=1 AND cool=1)
INSERT INTO data ('humidity','filtChng','time','indoorTemp','outdoorTemp',local_id,setups_id) SELECT 74.0,111111111,100,74.0,74.0,local.id,setups.id FROM local CROSS JOIN setups WHERE local.serialNumber='XXXX' AND setups.hold='00' AND setups.mode=1 AND setups.setTemp=74.0 AND setups.maxSTemp=74.0 AND setups.minSTemp=74.0 AND setups.units='F' AND setups.heat=1 AND setups.heatMode=1 AND setups.fanMode=1 AND setups.fan=1 AND setups.cool=1
What I am doing works, but seems slow and redundant/inefficient...
Well, you can remove the "where not exists" part from the "local" insert if you use a unique constraint on the "serialNumber" field. Be careful, this will throw a constraint violation instead of just not inserting the row. So be sure to handle that in the application.
And though I assume it is, be sure that checking for duplicates is really necessary in your app.