How to efficiently insert ENUM value into table? - sql

Consider the following schema:
CREATE TABLE IF NOT EXISTS snippet_types (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS snippets (
id INTEGER NOT NULL PRIMARY KEY,
title TEXT,
content TEXT,
type INTEGER NOT NULL,
FOREIGN KEY(type) REFERENCES snippet_types(id)
);
This schema assumes a one-to-many relationship between tables and allows efficiently maintaining a set of ENUMs in the snippet_types table. Efficiency comes from the fact that we don't need to store the whole string describing snippet type in the snippets table, but this decision also leads us to some inconvenience: upon inserting we need to retrieve snippet id from snippet_types and this leads to one more select and check before inserting:
SELECT id FROM snippet_types WHERE name = "foo";
-- ...check that > 0 rows returned...
INSERT INTO snippets (title, content, type) values ("bar", "buz", id);
We could also combine this insert and select into one select like that:
INSERT INTO snippets (title, content, type)
SELECT ("bar", "buz", id) FROM snippet_types WHERE name = "foo"
However, if "foo" type is missing in snippet_types then 0 rows would have been inserted and no error returned and I don't see a possibility to get a number of rows sqlite actually inserted.
How can I insert ENUM-containing tuple in one query?

Related

PostgreSQL- insert result of query into exisiting table, auto-increment id

I have created an empty table with the following SQL statement. My understanding (based on this tutorial: https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-serial/) was that SERIAL PRIMARY KEY will automatically provide an auto-incremented id for every new row:
CREATE TABLE "shema".my_table
(
id SERIAL PRIMARY KEY,
transaction text NOT NULL,
service_privider text NOT NULL,
customer_id text NOT NULL,
value numeric NOT NULL
)
WITH (
OIDS = FALSE
);
ALTER TABLE "shema".my_table
OWNER to admin;
Now I am querying another tables and would like to save the result of that query into my_table. The result of the query outputs following schema:
transaction
service_provider
customer_id
value
meaning the schema of my_table minus id. when I try to execute:
INSERT into my table
Select {here is the query}
Then I am getting an error that column "id" is of type integer but expression is of type text. I interpret it that the sql query is looking for id column and cannot find it. How can I insert data into my_table without explicitly stating id number but have this id auto-generated for every row?
Always mention the columns you want to INSERT:
INSERT INTO schemaname.my_table("transaction", service_privider, customer_id, value)
SELECT ?, ?, ?, ?;
If you don't, your code will break now or somewhere in the future.
By the way, transaction is a reserved word, try to use a better column name.

Populate virtual SQLite FTS5 (full text search) table from content table

I've followed https://kimsereylam.com/sqlite/2020/03/06/full-text-search-with-sqlite.html to set up SQLite's virtual table extension FTS5 for full text search on an external content table.
While the blog shows how to set up triggers to keep the virtual FTS table updated with the data:
CREATE TABLE user (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
short_description TEXT
)
CREATE VIRTUAL TABLE user_fts USING fts5(
username,
short_description,
email UNINDEXED,
content='user',
content_rowid='id'
)
CREATE TRIGGER user_ai AFTER INSERT ON user
BEGIN
INSERT INTO user_fts (rowid, username, short_description)
VALUES (new.id, new.username, new.short_description);
END;
...
I am failing to populate the FTS table from all previous data in an analogous fashion.
I'll stick to the example from the blog:
INSERT INTO user_fts (rowid, username, short_description) SELECT (id, username, short_description) FROM user;
However, sqlite (3.37.2) fails with row value misused.
Please explain how id, content_rowid, rowid and new.id are related and how to modify the query to update the FTS table properly.
INSERT INTO user_fts (rowid, username, short_description) SELECT id, username, short_description FROM user;
(no parentheses) works.
rowid is a unique 64 bit unsigned integer row id.
If the table contains an integer primary key (as id in user), they are the same (alias). I.e. user.rowid == user.id = user_fts.rowid.
Doc: https://www.sqlite.org/lang_createtable.html#rowid
The new refers to the element being inserted.
Doc: https://www.sqlite.org/lang_createtrigger.html
content_rowid links the virtual FTS table to the external data table row id column (it defaults to rowid).
Doc: https://www.sqlite.org/fts5.html#external_content_tables

Postgresql Upsert based on conditition

I have the following tables
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
...
CREATE TABLE tags (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4 (),
user_id UUID NOT NULL references users (id),
tag VARCHAR(200) NOT NULL,
...
}
I would like to form a query that inserts a tag based on the following constraints:
For a given user_id in the tags table, all entries must have unique tags
Different user_ids can have the same tag. For example:
The following should be valid in the tag table
id
user_id
tag
some-tag-uuid-1
some-user-uuid-1
foo
some-tag-uuid-2
some-user-uuid-1
bar
some-tag-uuid-3
some-user-uuid-2
foo
Note the differences in user_id .
The following should NOT be valid in the tag table
id
user_id
tag
some-tag-uuid-1
some-user-uuid-1
foo
some-tag-uuid-2
some-user-uuid-1
foo
If an entry exists, I should return the existing tag id. If not, we insert the new tag
and return the new tag's id.
What I currently have
As of now, the only query I can come up with is split into two parts and the app handles the intermediate logic.
For a given tag to insert e.g.
{id: 'some-tag-uuid-1', user_id: 'some-user-uuid-1', tag: 'busy'};
SELECT id FROM tag WHERE user_id = 'some-user-uuid-1' AND tag = 'busy'
From the resulting rows, I then check if it exists, if so, I return the existing id, if not I insert the new id in the tag table returning the new id.
I'm not sure if this approach is the best approach, and would like a single more performant query (if possible)
As stated by #SebDieBln :
You add a unique constraint in the tags table definition : CONSTRAINT unique_constraint UNIQUE (user_id, tag)
You add ON CONFLICT DO NOTHING in the INSERT statement
You add the RETURNING clause in the INSERT statement in order to get the new tag when inserted
But when the tag value already exists for the user_id, the returned value is NULL, so you need to catch the tag input value instead.
Finaly you can do everything within a sql function :
CREATE OR REPLACE FUNCTION test (IN _user_id UUID , INOUT _tag VARCHAR(200), OUT _rank INTEGER)
RETURNS record LANGUAGE sql AS
$$
WITH cte AS (INSERT INTO tags (user_id, tag) VALUES (_user_id, _tag) ON CONFLICT DO NOTHING RETURNING tag)
SELECT tag, 1 FROM cte
UNION
SELECT _tag, 2
ORDER BY 2
LIMIT 1 ;
$$
And you call the sql function to get the expected behavior :
SELECT _tag FROM test('some-user-uuid-1', 'busy')
see the test result in dbfiddle.

Populate snowflake table with default values without selecting default column values from the file data

I am trying to load a table (drop table and load the data - similar to truncate and load) dynamically. Let us assume that table needs to have 4 fields, ID, Name, SeqNo, and DtTimeStamp.
The data is being selected from an externally staged csv\text file that has only two fields (ID and Name). The below query gives an error for the nonmatching of a number of columns. How to resolve that issue?
CREATE OR REPLACE TABLE SOMETABLENAME(ID NUMBER(38,0), Name
VARCHAR(255), SeqNo NUMBER(38,0) NOT NULL AUTOINCREMENT, DtTimeStamp
TIMESTAMP_NTZ(9) NOT NULL DEFAULT CURRENT_TIMESTAMP()) AS SELECT A.$1
AS ID, A.$2 AS Name FROM #EXTERNALSTAGE/SOME_FILE.CSV A;
If you carefully look at the above SQL statement, my table has two extra fields that need to be auto-populated for every row it loads. But I am unable to make it work?
Any suggestions are highly appreciated.
Thanks in Advance!
Sathya
CREATE TABLE … AS SELECT (CTAS)
CREATE TABLE <table_name> ( <col1_name> , <col2_name> , ... ) AS SELECT ...
The number of column names specified must match the number of SELECT list items in the query; the types of the columns are inferred from the types produced by the query.
To resolve it, CTAS and INSERT INTO could be two separate steps:
CREATE OR REPLACE TABLE SOMETABLENAME(
ID NUMBER(38,0),
Name VARCHAR(255),
SeqNo NUMBER(38,0) NOT NULL AUTOINCREMENT,
DtTimeStamp TIMESTAMP_NTZ(9) NOT NULL DEFAULT CURRENT_TIMESTAMP()
);
-- here INSERT/SELECT have matching column list
INSERT INTO SOMETABLENAME(ID, Name)
SELECT A.$1 AS ID, A.$2 AS Name FROM #EXTERNALSTAGE/SOME_FILE.CSV A;

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.