Dynamic conditional insert in postgresql - sql

I am attempting to define a conditional insert in postgresql, on an index over 3 columns (which gives uniqueness). I'm trying to follow the following example from the postgresql documentation:
INSERT INTO example_table
(id, name)
SELECT 1, 'John'
WHERE
NOT EXISTS (
SELECT id FROM example_table WHERE id = 1
);
For the basic SELECT WHERE NOT EXISTS structure. But if the index varies, i.e. you want to prevent insert if there exists a selection in the table with id=index value of current pre-insert row, how do you implement this? Here is my current (wrong) code:
insert = (
"INSERT INTO table (index,a,b,c,d,e)"
"SELECT * FROM table WHERE NOT EXISTS (SELECT * FROM table WHERE index=index)");
cur.execute(insert,data)
For clarity, the index is defined on data columns (a,b,c), data is a row of (index,a,b,c,d,e), and I'm wrapping this in psycopg2. I have searched for an answer for a while, but haven't been able to successfully adapt anything to this problem yet.

insert into t1 (a, b, c, d, e)
select a, b, c, d, e
from t2
where not exists (
select 1
from t1
where a = t2.a and b = t2.b and c = t2.c
);
In Python it is easier and cleaner to use the triple quote raw string
insert = """
insert into t1 (a, b, c, d, e)
select a, b, c, d, e
from t2
where not exists (
select 1
from t1
where a = t2.a and b = t2.b and c = t2.c
);
"""

Related

Can i run more then one main select statments on the 2 with tables?

Hay All,
is it possible to run more than 1 select statement after using with?
first select statement works fine, as soon as i add another select statement i got a error.
with
a as (select a,b,c from Table1 with(readuncommitted)),
b as (select d,e,f from Table2 with(readuncommitted))
select * from a
select * from b
expected output:
Table 1
a
Table 2
b
Well the way CTEs will behave is that they will only be in scope for the first query, but not the second. You could perhaps do a union query here:
SELECT a, b, c, 'Table1' AS src FROM a
UNION ALL
SELECT d, e, f, 'Table2' FROM b;
Or, you could move the b CTE to before the second query:
WITH a AS (
SELECT a, b, c
FROM Table1
WITH(readuncommitted)
)
SELECT * FROM a;
WITH b AS (
SELECT d, e, f
FROM Table2
WITH(readuncommitted)
)
SELECT * FROM b;
hay DasD,
You can not use multiple select for cte, but you can use more than one CTE like this.
with
a as (select a,b,c from Table1 with(readuncommitted)),
b as (select d,e,f from Table2 with(readuncommitted))
select * from a,b
You have to explain to the database, what you wantfrom bith tables.
as both have the same structure you can use UNION to join them vertically
with
a as (select a,b,c from Table1 with(readuncommitted)),
b as (select d,e,f from Table2 with(readuncommitted))
select * from a
UNION
select * from b
From the docs:
"A CTE must be followed by a single SELECT, INSERT, UPDATE, or DELETE statement that references some or all the CTE columns."
Source

Using multiple CTE with multiple temp tables

In Windows Server, I am trying to gather data using multiple CTEs insert them into a few temp tables to later on perform a join. Below is what I got. :
------TEMP TABLE SET UP------
IF EXISTS (
SELECT *
FROM tempdb.dbo.sysobjects
WHERE id = Object_id(N'tempdb..#LEFT')
)
BEGIN
DROP TABLE #LEFT
END
IF EXISTS (
SELECT *
FROM tempdb.dbo.sysobjects
WHERE id = Object_id(N'tempdb..#RIGHT')
)
BEGIN
DROP TABLE #RIGHT
END
------TEMP TABLE SET UP END------
------CTE SET UP------
; with
CTEfirst (1a, b, c, d) as
(select 1a, b, c, d from tableA)
, CTEone (a, b, c) as
(select a, b, c from table1)
),
CTEtwo (a, b, c) as (
(select a, b, c from table2)
),
CTEthree (a, b, c) as (
(select a, b, c from table3)
------CTE SET UP END------
select * into #LEFT from CTEone
union
select * from CTEtwo
union
select * from CTEthree
-----------------------------
/*At this point I am getting the issue to recognize CTEfirst when attempting to insert data into #RIGHT temp table unless I move the below portion below the previous section (prior to the unions) but then would encounter the issue of the overall query not recognizing the next CTE, CTEone.*/
select * into #RIGHT from CTEfirst
Thank you
you have declared cte but it is empty
; with
CTEfirst (a, b, c, d)
as ( select ...) --<-- missing cte definiation here
, CTEone (a, b, c) as
(select a, b, c from table1)
),
You cannot refer to the same CTE for more than one unattached select statement.
Your first query ends when you insert into #left.
After that you cannot run a new select statement referring to the same (unattached) CTEs.
Think of CTEs as reformatted sub-queries. If you want data loaded into multiple temp tables, I wouldn't use CTEs in the first place. Just insert into the temp tables directly.

SQL - Querying top value for multiple columns

Table1:
Name, Value A, Value B, Value C
I would like to find the largest Name by Value A, the largest Name by Value B and the largest Name by Value C. Does anyone have a quick way to do this? The table itself is rather large and I would really want to avoid running through it multiple times for each value.
Thank you!
If you have an index on each of the columns (a), (b), and (c), you can do:
select t.*
from t
where t.a = (select max(t2.a) from t t2) or
t.b = (select max(t2.b) from t t2) or
t.c = (select max(t2.c) from t t2) ;
The where clause should be able to make use of the indexes. If not, you can split this into subqueries. Something like this:
select t.*
from t
where t.a = (select max(t2.a) from t t2)
union all
select t.*
from t
where t.b = (select max(t2.b) from t t2)
select t.*
from t
where t.c = (select max(t2.c) from t t2) ;

Reducing the list of results (SQL)

I stuck on an SQL statement since 2 days now and I hope you can help me with that.
The result of my select is a list with 4 attributes A, B, C and D (below is an example list of 5 datasets):
1. A=1 B=100 C=200 D=300
2. A=2 B=200 C=100 D=300
3. A=3 B=300 C=200 D=100
4. A=3 B=100 C=100 D=200
5. A=3 B=300 C=100 D=200
The list shall be reduced, so that every attribute A is in the list only once.
In the example above the dataset 1. and 2. should be in the list, because A=1 and A=2 exists only once.
For A=3 I have to build a query to identify the dataset, that will be in the final list. Some rules should apply:
Take the dataset with the highest value of B; if not distinct then
Take the dataset with the highest value of C; if not distinct then
Take the dataset with the highest value of D.
In the example above the dataset 3. should be taken.
The expected result is:
1.A=1 B=100 C=200 D=300
2.A=2 B=200 C=100 D=300
3.A=3 B=300 C=200 D=100
I hope you understand my problem. I've tried various versions of SELECT-statements with HAVING and EXISTS (or NOT EXISTS), but my SQL knowledge isn't enough.
Probably there is an easier way to solve this problem, but this one works:
CREATE TEMP TABLE TEST (
A INTEGER,
B INTEGER,
C INTEGER,
D INTEGER
);
insert into TEST values (1,1,1,1);
insert into TEST values (2,1,5,1);
insert into TEST values (2,2,1,1);
insert into TEST values (3,1,4,1);
insert into TEST values (3,2,1,4);
insert into TEST values (3,2,3,1);
insert into TEST values (3,3,1,5);
insert into TEST values (3,3,2,3);
insert into TEST values (3,3,2,7);
insert into TEST values (3,3,3,1);
insert into TEST values (3,3,3,2);
select distinct
t1.A,
t2.B as B,
t3.C as C,
t4.D as D
from TEST t1
join (select A ,MAX (B) as B from TEST group by A)t2 on t2.A=t1.A
join (select A, B, MAX(C) as C from TEST group by A,B)t3 on t3.A=t2.A and t3.B=t2.B
join (select A, B, C, MAX (D) as D from TEST group by A,B,C)t4 on t4.A=t3.A and t4.B=t3.B and t4.C=t3.C;
Result:
a b c d
1 1 1 1
2 2 1 1
3 3 3 2
Tested on IBM Informix Dynamic Server Version 11.10.FC3.
This type of prioritization query is most easily done with row_number(), but I don't think Informix supports that.
So, one method is to enumerate the rows using a correlated subquery:
select t.*
from (select t.*,
(select count(*)
from t t2
where (t2.b > t.b) or
(t2.b = t.b and t2.c > t.c) or
(t2.b = t.b and t2.c = t.c and t2.d > t.d)
) as NumGreater
from t
) t
where NumGreater = 0;
I have no idea about Informix but you can try. This works in Sql Server. May be it will also work in Informix:
select * from tablename t1
where id = (select first 1 id from tablename t2
where t2.A = t1.A order by B desc, C desc, D desc)
SELECT A, MAX(B) AS B, MAX(C) AS C, MAX(D) AS D
FROM table_name
GROUP BY A

Select distinct on multiple columns simultaneously, and keep one column in PostgreSQL

For a table such as this:
tblA
A,B,C
1,2,t3a
1,3,d4g
1,2,b5e
1,3,s6u
I want to produce a table that selects distinct on both A and B simultaneously, and still keep one value of C, like so:
tblB
A,B,C
1,2,t3a
1,3,d4g
Seems like this would be simple, but not finding it for the life of me.
DROP TABLE IF EXISTS tblA CASCADE;
SELECT DISTINCT ON (A,B), C
INTO tblB
FROM tblA;
When you use DISTINCT ON you should have ORDER BY:
SELECT DISTINCT ON (A,B), C
INTO tblB
FROM tblA
ORDER BY A, B;
This should do the trick
CREATE TABLE tblB AS (
SELECT A, B, max(C) AS max_of_C FROM tblA GROUP BY A, B
)
Use a view to do the distinct and then join it to the original table to pick one row of column C. Inserting into the target is left for you to figure out. Oh, and you could pick up multiple columns from t, not just c - the only thing is that your subquery needs to find a way to limit it to only one row.
create table t (a int, b int, c int);
create view tv as select distinct a, b from t;
insert into t (a, b, c) values(1, 2, 10);
insert into t (a, b, c) values(1, 2, 20);
insert into t (a, b, c) values(1, 3, 30);
insert into t (a, b, c) values(1, 3, 40);
CREATE TABLE tblB AS (
select tv.a, tv.b, t.c from tv, t
where tv.a = t.a and tv.b = t.b
/* pick smallest ctid which is a unique row id built into postgres */
and t.ctid = (select min(ctid) from t s where s.a = t.a and s.b = t.b);
)