replace select table by an update - sql

I have an intermediate table:
text_mining_molecule
|text_mining_id| molecule_id |
| -------------| ---------- |
| ID | ID |
and two other tables:
Table Molécules:
id | main_name | others …
--- | --------- | ------
1 | caféine | others …
Table jsonTextMining:
id | title | molecule_name | others …
---|------- |-------------------------------------|------
1 | title1 | colchicine, cellulose, acid, caféine| others …
text_mining_molecule need to be inserted when select a choice in a list with ID's from 2 others tables json_text_mining and molecule.
Actually there is a dropdown that already insert all rows from json_text_mining to text_mining when choose a score under 4.
INSERT INTO text_mining (id, solrid, originalpaper, annotatedfile, title, keyword, importantsentence, version, klimischscore, moleculename, synonymname, validation)
SELECT id, solrid, originalpaper, annotatedfile, title, keyword, importantsentence, version, klimischscore, molecule_name, synonym_name, validation
FROM json_text_mining WHERE klimischscore < 4
This works but i need text_mining_molecule to be filled also with related ID's so i have also this part of code :
SELECT s.id, m.id
FROM (SELECT id, regexp_split_to_table(molecule_name,', ') AS m_name
FROM json_text_mining) AS s, molecule m
WHERE m.main_name = s.m_name;
How can i update text_mining_molecule table directly with an insert instead a select ?

use CTE. eg if text_mining_molecule.molecule references molecule.id, would be smth like:
with c as (
SELECT s.id sid, m.id mid
FROM (SELECT id, regexp_split_to_table(molecule_name,', ') AS m_name
FROM json_text_mining) AS s, molecule m
WHERE m.main_name = s.m_name
)
update text_mining_molecule t
set sid = c.sid
from c
where t.molecule = c.mid

Related

Iterate through singly-linked list in PostgreSQL

I have a table with a primary (unique) key id and a foreign key next_id which points to another (next) entry in this same table (or to none if it's the last entry).
So the example data may look like this:
| id | next_id |
| -------- | ---------- |
| 1 | 3 |
| 2 | null |
| 3 | 2 |
What's the most effective SQL-query to iterate through such linked list in Posgres and return list of linked id's (would be [1, 3, 2] for the provided example table)?
I figured it out using the recursive query as suggested in comments by #Laurenz Albe
WITH recursive tmp_table as (
SELECT id
FROM main_table
WHERE id = 1
UNION
SELECT m.id
FROM main_table m
INNER JOIN tmp_table t ON t.next_id = m.id
)
SELECT * FROM tmp_table;

Recursive delete of multiple rows in one table

I have a problem with my postgres database. I have a table Tasks with 3 columns: ID, Name and Parent_ID (which refers to another task id in this table):
id | name | parent_id
---+------+-----------
1 | A | 0
2 | B | 1
3 | C | 2
4 | D | 1
5 | E | 0
6 | F | 0
So basically it's like this:
1. A
2. B
3. C
4. D
5. E
6. F
What I'm trying to do is to delete task A, and delete all of its children and all children of children etc etc..(in this case B and D, along with C as its children of B which is deleted) something like cascade delete, but i cant do this. Maybe any function will work?
The result after delete should be
id | name | parent_id
---+------+-----------
5 | E | 0
6 | F | 0
Hope you guys can help me.
You only need a cascading FK-constraint:
\i tmp.sql
CREATE TABLE employees (
id serial PRIMARY KEY
, name VARCHAR NOT NULL
, parent_id INT REFERENCES employees(id) ON DELETE CASCADE
);
INSERT INTO employees
VALUES (1,'A', NULL),(2,'B', 1),(3,'C', 2),
(4,'D', 1),(5,'E', NULL),(6,'F', NULL);
DELETE FROM employees
WHERE name = 'A'
;
SELECT * FROM employees
;
----------
Result:
----------
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 6
DELETE 1
id | name | parent_id
----+------+-----------
5 | E |
6 | F |
(2 rows)
You can build the list of all descendent rows from the starting name with a recursive query, and then use it as a filter for the deletetions.
with recursive cte(id, parent_id) as (
select id, parent_id from mytable where name = 'A'
union all
select t.id, t.parent_id from mytable t inner join cte c on c.id = t.parent_id
)
delete from mytable where id in (select id from cte)
Demo on DB Fiddle - table content after executing the query:
id | name | parent_id
-: | :--- | --------:
5 | E | 0
6 | F | 0
Use recursive cte to get all rows with Name = 'A' and it's subordinates.
Then delete it from table employees.
Here is the step to create the table:
Sample table: CREATE TABLE employees (
id serial PRIMARY KEY,
name VARCHAR NOT NULL,
parent_id INT
);
INSERT INTO employees
VALUES (1,'A', 0),(2,'B', 1),(3,'C', 2),
(4,'D', 1),(5,'E', 0),(6,'F', 0);
Query:
WITH RECURSIVE subordinates AS (
SELECT
id,
parent_id,
name
FROM
employees
WHERE
name = 'A'
UNION
SELECT
e.id,
e.parent_id,
e.name
FROM
employees e
INNER JOIN subordinates s ON s.id = e.parent_id
)
DELETE
FROM employees
WHERE id in (
SELECT
id
FROM
subordinates);
SELECT * FROM employees;

Count how many times a value appears in tables SQL

Here's the situation:
So, in my database, a person is "responsible" for job X and "linked" to job Y. What I want is a query that returns: name of person, his ID and he number of jobs it's linked/responsible. So far I got this:
select id_job, count(id_job) number_jobs
from
(
select responsible.id
from responsible
union all
select linked.id
from linked
GROUP BY id
) id_job
GROUP BY id_job
And it returns a table with id in the first column and number of occurrences in the second. Now, what I can't do is associate the name of person to the table. When i put that in the "select" from beginning it gives me all the possible combinations... How can I solve this? Thanks in advance!
Example data and desirable output:
| Person |
id | name
1 | John
2 | Francis
3 | Chuck
4 | Anthony
| Responsible |
process_no | id
100 | 2
200 | 2
300 | 1
400 | 4
| Linked |
process_no | id
101 | 4
201 | 1
301 | 1
401 | 2
OUTPUT:
| OUTPUT |
id | name | number_jobs
1 | John | 3
2 | Francis | 3
3 | Chuck | 0
4 | Anthony | 2
Try this way
select prs.id, prs.name, count(*) from Person prs
join(select process_no, id
from Responsible res
Union all
select process_no, id
from Linked lin ) a on a.id=prs.id
group by prs.id, prs.name
I would recommend aggregating each of the tables by the person and then joining the results back to the person table:
select p.*, coalesce(r.cnt, 0) + coalesce(l.cnt, 0) as numjobs
from person p left join
(select id, count(*) as cnt
from responsible
group by id
) r
on r.id = p.id left join
(select id, count(*) as cnt
from linked
group by id
) l
on l.id = p.id;
select id, name, count(process_no) FROM (
select pr.id, pr.name, res.process_no from Person pr
LEFT JOIN Responsible res on pr.id = res.id
UNION
select pr.id, pr.name, lin.process_no from Person pr
LEFT JOIN Linked lin on pr.id = lin.id) src
group by id, name
order by id
Query ain't tested, give it a shot, but this is the way you want to go

Sql select query based on a column value

I have a Table1 like this:
ApplicableTo IdApplicable
---------------------------
Dept 1
Grade 3
section 1
Designation 2
There other tables like:
tblDept:
ID Name
1 dept1
2 baking
3 other
tblGrade:
ID Name
1 Grd1
2 Manager
3 gr3
tblSection:
id Name
1 Sec1
2 sec2
3 sec3
tblDesignation:
id Name
1 Executive
2 Developer
3 desig3
What I need is a query for table1 in such a way that gives me
ApplicableTo (table1)
Name (from the relevant table based on the value in `ApplicableTo` column)
Is this possible?
Desired Result:
eg: ApplicableTo IdApplicable Name
Dept 1 dept1
grade 3 gr3
Section 1 sec1
Designation 2 Developer.
This is the result I desire.
You could do something like the following so the applicable to becomes part of the JOIN predicate:
SELECT t1.ApplicableTo, t1.IdApplicable, n.Name
FROM Table1 AS t1
INNER JOIN
( SELECT ID, Name, 'Dept' AS ApplicableTo
FROM tblDept
UNION ALL
SELECT ID, Name, 'Grade' AS ApplicableTo
FROM tblGrade
UNION ALL
SELECT ID, Name, 'section' AS ApplicableTo
FROM tblSection
UNION ALL
SELECT ID, Name, 'Designation' AS ApplicableTo
FROM tblDesignation
) AS n
ON n.ID = t1.IdApplicable
AND n.ApplicableTo = t1.ApplicableTo
I would generally advise against this approach, although it may seem like a more consice approach, you would be better having 4 separate nullable columns in your table:
ApplicableTo | IdDept | IdGrade | IdSection | IdDesignation
-------------+--------+---------+-----------+---------------
Dept | 1 | NULL | NULL | NULL
Grade | NULL | 3 | NULL | NULL
section | NULL | NULL | 1 | NULL
Designation | NULL | NULL | NULL | 2
This allows you to use foreign keys to manage your referential integrity properly.
You can use CASE here,
SELECT ApplicableTo,
IdApplicable,
CASE
WHEN ApplicableTo = 'Dept' THEN (SELECT Name FROM tblDept WHERE tblDept.ID = IdApplicable)
WHEN ApplicableTo = 'Grade' THEN (SELECT Name FROM tblGrade WHERE tblGrade.ID = IdApplicable)
WHEN ApplicableTo = 'Section' THEN (SELECT Name FROM tblSection WHERE tblSection.ID = IdApplicable)
WHEN ApplicableTo = 'Designation' THEN (SELECT Name FROM tblDesignation WHERE tblDesignation.ID = IdApplicable)
END AS 'Name'
FROM Table1
The easiest way to achieve that is to add an extra column in table1 to keep the table where id is refferred to. Otherwise you can't know in which table the applicable id is reffered to.
Or you can create the applicable id in a way that you can extract the table afterwords from it for example a1 for id 1 in tblDept. And then use [case] (http://dev.mysql.com/doc/refman/5.0/en/case.html) (for mysql) in order to make the correct Join.

Selecting unique rows in a set of two possibilities

The problem itself is simple, but I can't figure out a solution that does it in one query, and here's my "abstraction" of the problem to allow for a simpler explanation:
I will let my original explenation stand, but here's a set of sample data and the result i expect:
Ok, so here's some sample data, i separated pairs by a blank line
-------------
| Key | Col | (Together they from a Unique Pair)
--------------
| 1 Foo |
| 1 Bar |
| |
| 2 Foo |
| |
| 3 Bar |
| |
| 4 Foo |
| 4 Bar |
--------------
And the result I would expect, after running the query once, it need to be able to select this result set in one query:
1 - Foo
2 - Foo
3 - Bar
4 - Foo
Original explenation:
I have a table, call it TABLE where I have a two columns say ID and NAME which together form the primary key of the table. Now I want to select something where ID=1 and then first checks if it can find a row where NAME has the value "John", if "John" does not exist it should look for a row where NAME is "Bruce" - but only return "John" if both "Bruce" and "John" exists or only "John" exists of course.
Also note that it should be able to return several rows per query that match the above criteria but with different ID/Name-combinations of course, and that the above explanation is just a simplification of the real problem.
I could be completely blinded by my own code and line of thought but I just can't figure this out.
This is fairly similar to what you wrote, but should be fairly speedy as NOT EXISTS is more efficient, in this case, than NOT IN...
mysql> select * from foo;
+----+-----+
| id | col |
+----+-----+
| 1 | Bar |
| 1 | Foo |
| 2 | Foo |
| 3 | Bar |
| 4 | Bar |
| 4 | Foo |
+----+-----+
SELECT id
, col
FROM foo f1
WHERE col = 'Foo'
OR ( col = 'Bar' AND NOT EXISTS( SELECT *
FROM foo f2
WHERE f1.id = f2.id
AND f2.col = 'Foo'
)
);
+----+-----+
| id | col |
+----+-----+
| 1 | Foo |
| 2 | Foo |
| 3 | Bar |
| 4 | Foo |
+----+-----+
You can join the initial table to itself with an OUTER JOIN like this:
create table #mytest
(
id int,
Name varchar(20)
);
go
insert into #mytest values (1,'Foo');
insert into #mytest values (1,'Bar');
insert into #mytest values (2,'Foo');
insert into #mytest values (3,'Bar');
insert into #mytest values (4,'Foo');
insert into #mytest values (4,'Bar');
go
select distinct
sc.id,
isnull(fc.Name, sc.Name) sel_name
from
#mytest sc
LEFT OUTER JOIN #mytest fc
on (fc.id = sc.id
and fc.Name = 'Foo')
like that.
No need to make this overly complex, you can just use MAX() and group by ...
select id, max(col) from foo group by id
try this:
select top 1 * from (
SELECT 1 as num, * FROM TABLE WHERE ID = 1 AND NAME = 'John'
union
SELECT 2 as num, * FROM TABLE WHERE ID = 1 AND NAME = 'Bruce'
) t
order by num
I came up with a solution myself, but it's kind of complex and slow - nor does it expand well to more advanced queries:
SELECT *
FROM users
WHERE name = "bruce"
OR (
name = "john"
AND NOT id
IN (
SELECT id
FROM posts
WHERE name = "bruce"
)
)
No alternatives without heavy joins, etc. ?
Ok, so here's some sample data, i separated pairs by a blank line
-------------
| Key | Col | (Together they from a Unique Pair)
--------------
| 1 Foo |
| 1 Bar |
| |
| 2 Foo |
| |
| 3 Bar |
| |
| 4 Foo |
| 4 Bar |
--------------
And the result I would expect:
1 - Foo
2 - Foo
3 - Bar
4 - Foo
I did solve it above, but that query is horribly inefficient for lager tables, any other way?
Here's an example that works in SQL Server 2005 and later. It's a useful pattern where you want to choose the top row (or top n rows) based on a custom ordering. This will let you not just choose among two values with custom priorities, but any number. You can use the ROW_NUMBER() function and a CASE expression:
CREATE TABLE T (id int, col varchar(10));
INSERT T VALUES (1, 'Foo')
INSERT T VALUES (1, 'Bar')
INSERT T VALUES (2, 'Foo')
INSERT T VALUES (3, 'Bar')
INSERT T VALUES (4, 'Foo')
INSERT T VALUES (4, 'Bar')
SELECT id,col
FROM
(SELECT id, col,
ROW_NUMBER() OVER (
PARTITION BY id
ORDER BY
CASE col
WHEN 'Foo' THEN 1
WHEN 'Bar' THEN 2
ELSE 3 END
) AS RowNum
FROM T
) AS X
WHERE RowNum = 1
ORDER BY id
In PostgreSQL, I believe it would be this:
SELECT DISTINCT ON (id) id, name
FROM mytable
ORDER BY id, name = 'John' DESC;
Update - false sorts before true - I had it backwards originally. Note that DISTINCT ON is a PostgreSQL feature and not part of standard SQL. What happens here is that it only shows you the first row for any given id that it comes across. Since we order by weather the name is John, rows named John will be selected over all other names.
With your second example, it would be:
SELECT DISTINCT ON (key) key, col
FROM mytable
ORDER BY key, col = 'Foo' DESC;
This will give you:
1 - Foo
2 - Foo
3 - Bar
4 - Foo
You can use joins instead of the exists and this may improve the query plan in cases where the optimizer is not smart enough:
SELECT f1.id
,f1.col
FROM foo f1
LEFT JOIN foo f2
ON f1.id = f2.id
AND f2.col = 'Foo'
WHERE f1.col = 'Foo'
OR ( f1.col = 'Bar' AND f2.id IS NULL )