mySQL query: How to insert with UNION? - select

I am kind of new to mySQL:s union functions, at least when doing inserts with them. I have gotten the following to work based upon a example found on the net:
INSERT INTO tableOne(a, b)
SELECT a, $var FROM tableOne
WHERE b = $var2
UNION ALL SELECT $var,$var
Ok, nothing strange about that. But what happens when I want to insert a third value into the database that has nothing to do with the logic of the Select being done?
Like : INSERT INTO tableOne(a, b, c )
How could that be done?

You can "select" literal values too:
mysql> select 'hello', 1;
+-------+---+
| hello | 1 |
+-------+---+
| hello | 1 |
+-------+---+
1 row in set (0.00 sec)
Hence, you can also use that in INSERT INTO ... SELECT FROM and UNIONs.
INSERT INTO someTable (a, b, c) VALUES
SELECT id, name, 5
FROM someOtherTable
UNION
SELECT id, alias, 8
FROM anotherTable

Related

Using a PostgreSQL function inside a loop

Suppose I have a PostgreSQL function that takes 2 parameters: id (INT), email (TEXT) and can be called like this:
SELECT * FROM my_function(101, 'myemail#gmail.com')
I want to run a SELECT query from a table that would return multiple id's:
SELECT id FROM mytable
| id |
--+------+
| 101 |
--+------+
| 102 |
--+------+
| 103 |
How would I loop through and plug each of the returned id's into my function in a query. FOr this example just assume the default email is alwasy "myemail#gmail.com"
I'm on mobile so I can't test it, but I think maybe this will work.
SELECT * FROM (select my_function(id, 'myemail#gmail.com') from mytable);
You can use a cross join:
SELECT *
FROM my_table mt
cross join lateral my_function(mt.id, 'myemail#gmail.com') as mf

postgresql, insert several sets of 3 items with 2 same item in every set

I need to insert several sets of 3 items. 2 items are the same in every set.
I want to put at position of $1 this clause: EXTRACT(EPOCH FROM NOW() AT TIME ZONE 'UTC')
WITH data(serial_id, banned_ts, reason) AS (
VALUES (x, $1, $2),(y, $1, $2),(z, $1, $2)
)
INSERT INTO _serial_ids_banned (serial_id, banned_ts, reason)
SELECT d.serial_id, d.banned_ts, d.reason
FROM data d
WHERE NOT EXISTS
(SELECT 1 FROM _serial_ids_banned b
WHERE b.serial_id = d.serial_id);
If I do it then the clause will be evaluated 3 times(or more depending on number of the sets). Is it possible to create such a query that will evaluate the clause only once and use it in every set?
or with explicit join:
t=# prepare so1(text,text) as
with p(banned_ts, reason) as (values ($1,$2))
, sid(serial_id) as (values('x'),('y'),('z'))
, data as (select * from sid join p on true)
select * from data;
PREPARE
t=# execute so1('a','b');
serial_id | banned_ts | reason
-----------+-----------+--------
x | a | b
y | a | b
z | a | b
(3 rows)

Postgresql table name or alias in SELECT and WHERE clauses without specifying column name

I have two tables:
CREATE TABLE a (id INT NOT NULL);
CREATE TABLE b (id INT NOT NULL);
INSERT INTO a VALUES (1), (2);
INSERT INTO b VALUES (1);
If I try to get records from a for which there are records in b (query 1):
SELECT a.id, b FROM a LEFT JOIN b on a.id = b.id WHERE b is NOT NULL;
I get:
id | b
----+-----
1 | (1)
If I try to get records from a for which there are NO records in b (query 2):
SELECT a.id, b FROM a LEFT JOIN b on a.id = b.id WHERE b IS NULL;
I get:
id | b
----+---
2 |
It seems OK.
Then I alter b:
ALTER TABLE b ADD COLUMN s TEXT NULL;
then query 1 does not return any rows, query 2 returns the same rows and
SELECT a.id, b FROM a LEFT JOIN b on a.id = b.id;
returns
id | b
----+------
1 | (1,)
2 |
My questions are:
Why does Postresql allow to use table name or alias in WHERE clause without specifying column name?
What is (1,) in column b of resulting rows?
Why does (1,) not satisfy IS NULL and IS NOT NULL in query 1 and query 2?
P.S. If I alter table b as ALTER TABLE b ADD COLUMN s TEXT NOT NULL DEFAULT '' instead then queries 1 and 2 return the same rows.
Answering by questions:
This is row constructor, so every value from a column builds up a row value (composite value) using values from your columns for its member fields
(1,) is a row constructor with first member being 1 and second member (your text field) which has a null value, thus no value is shown.
You're comparing entire row constructor which actually satisfies both of comparison (is null and is not null)
More on point 3:
select *, b is not null as b_not_null, b is null as b_null from b;
Reult:
id | b_not_null | b_null
----+------------+--------
1 | t | f
A row IS NULL when all of its members have NULL values, otherwise it IS NOT NULL. Reproduce:
create table rowtest ( col1 int, col2 int);
insert into rowtest values (null,null), (1,1), (null,1);
select
col1, col2, rowtest,
case when rowtest is null then true else false end as rowtest_null
from rowtest;
Result:
col1 | col2 | rowtest | rowtest_null
------+------+---------+--------------
| | (,) | t
1 | 1 | (1,1) | f
| 1 | (,1) | f
Actually, for your queries they both could be rewritten to:
Query1: Get records from a with matching records from b
Using INNER JOIN which actually is the same as JOIN:
SELECT a.id, b FROM a JOIN b on a.id = b.id;
Query2: Get records from a with no matching records from b
Using NOT EXISTS instead of LEFT JOIN:
SELECT a.id
FROM a
WHERE NOT EXISTS (
SELECT 1
FROM b
WHERE a.id = b.id
);
For the last query if you really need the second empty column you can add a static value to select list like that:
SELECT a.id, null as b
The table name can be used in the SELECT or WHERE to refer to a record value containing the entire row of the table. In the output of psql a record will appear like (1) (if it has one field), or (1,2) (if it has two fields), etc. The (1,) that you see is a record with two fields that contain the values 1 and NULL. A value of record type can be null, e.g. in a left join if there is no matching row for the second table.

SQL to remove rows with duplicated value while keeping one

Say I have this table
id | data | value
-----------------
1 | a | A
2 | a | A
3 | a | A
4 | a | B
5 | b | C
6 | c | A
7 | c | C
8 | c | C
I want to remove those rows with duplicated value for each data while keeping the one with the min id, e.g. the result will be
id | data | value
-----------------
1 | a | A
4 | a | B
5 | b | C
6 | c | A
7 | c | C
I know a way to do it is to do a union like:
SELECT 1 [id], 'a' [data], 'A' [value] INTO #test UNION SELECT 2, 'a', 'A'
UNION SELECT 3, 'a', 'A' UNION SELECT 4, 'a', 'B'
UNION SELECT 5, 'b', 'C' UNION SELECT 6, 'c', 'A'
UNION SELECT 7, 'c', 'C' UNION SELECT 8, 'c', 'C'
SELECT * FROM #test WHERE id NOT IN (
SELECT MIN(id) FROM #test
GROUP BY [data], [value]
HAVING COUNT(1) > 1
UNION
SELECT MIN(id) FROM #test
GROUP BY [data], [value]
HAVING COUNT(1) <= 1
)
but this solution has to repeat the same group by twice (consider the real case is a massive group by with > 20 columns)
I would prefer a simpler answer with less code as oppose to complex ones. Is there any more concise way to code this?
Thank you
You can use one of the methods below:
Using WITH CTE:
WITH CTE AS
(SELECT *,RN=ROW_NUMBER() OVER(PARTITION BY data,value ORDER BY id)
FROM TableName)
DELETE FROM CTE WHERE RN>1
Explanation:
This query will select the contents of the table along with a row number RN. And then delete the records with RN >1 (which would be the duplicates).
This Fiddle shows the records which are going to be deleted using this method.
Using NOT IN:
DELETE FROM TableName
WHERE id NOT IN
(SELECT MIN(id) as id
FROM TableName
GROUP BY data,value)
Explanation:
With the given example, inner query will return ids (1,6,4,5,7). The outer query will delete records from table whose id NOT IN (1,6,4,5,7).
This fiddle shows the records which are going to be deleted using this method.
Suggestion: Use the first method since it is faster than the latter. Also, it manages to keep only one record if id field is also duplicated for the same data and value.
I want to add MYSQL solution for this query
Suggestion 1 : MySQL prior to version 8.0 doesn't support the WITH clause
Suggestion 2 : throw this error (you can't specify table TableName for update in FROM clause
So the solution will be
DELETE FROM TableName WHERE id NOT IN
(SELECT MIN(id) as id
FROM (select * from TableName) as t1
GROUP BY data,value) as t2;

Adding the results of two select queries into one table row with PostgreSQL

I am attempting to return the result of two distinct select statements into one row in PostgreSQL. For example, I have two queries each that return the same number of rows:
Select tableid1, tableid2, tableid3 from table1
+----------+----------+----------+
| tableid1 | tableid2 | tableid3 |
+----------+----------+----------+
| 1 | 2 | 3 |
| 4 | 5 | 6 |
+----------+----------+----------+
Select table2id1, table2id2, table2id3, table2id4 from table2
+-----------+-----------+-----------+-----------+
| table2id1 | table2id2 | table2id3 | table2id4 |
+-----------+-----------+-----------+-----------+
| 7 | 8 | 9 | 15 |
| 10 | 11 | 12 | 19 |
+-----------+-----------+-----------+-----------+
Now i want to concatenate these tables keeping the same number of rows. I do not want to join on any values. The desired result would look like the following:
+----------+----------+----------+-----------+-----------+-----------+-----------+
| tableid1 | tableid2 | tableid3 | table2id1 | table2id2 | table2id3 | table2id4 |
+----------+----------+----------+-----------+-----------+-----------+-----------+
| 1 | 2 | 3 | 7 | 8 | 9 | 15 |
| 4 | 5 | 6 | 10 | 11 | 12 | 19 |
+----------+----------+----------+-----------+-----------+-----------+-----------+
What can I do to the two above queries (select * from table1) and (select * from table2) to return the desired result above.
Thanks!
You can use row_number() for join, but I'm not sure that you have guaranties that order of the rows will stay the same as in the tables. So it's better to add some order into over() clause.
with cte1 as (
select
tableid1, tableid2, tableid3, row_number() over() as rn
from table1
), cte2 as (
select
table2id1, table2id2, table2id3, table2id4, row_number() over() as rn
from table2
)
select *
from cte1 as c1
inner join cte2 as c2 on c2.rn = c1.rn
You can't have what you want, as you wrote the question. Your two SELECTs don't have any ORDER BY clause, so the database can return the rows in whatever order it feels like. If it currently matches up, it does so only by accident, and will stop matching up as soon as you UPDATE a row.
You need a key column. Then you need to join on the key column. Anything else is attempting to invent unreliable and unsafe joins without actually using a join.
Frankly, this seems like a pretty dodgy schema. Lots of numbered integer columns like this, and the desire to concatenate them, may be a sign you should be looking at using integer arrays, or using a side-table with a foreign key relationship, instead.
Sample data in case anyone else wants to play:
CREATE TABLE table1(tableid1 integer, tableid2 integer, tableid3 integer);
INSERT INTO table1 VALUES (1,2,3), (4,5,6);
CREATE TABLE table2(table2id1 integer, table2id2 integer, table2id3 integer, table2id4 integer);
INSERT INTO table2 VALUES (7,8,9,15), (10,11,12,19);
Depending on what you're actually doing you might really have wanted arrays.
I think you might need to read these two posts:
Join 2 sets based on default order
How keep data don't sort?
which explain that SQL tables just don't have an order. So you cannot fetch them in a particular order.
DO NOT USE THE FOLLOWING CODE, IT IS DANGEROUS AND ONLY INCLUDED AS A PROOF OF CONCEPT:
As it happens you can use a set-returning function hack to very inefficiently do what you want. It's incredibly ugly and *completely unsafe without an ORDER BY in the SELECTs, but I'll include it for completeness. I guess.
CREATE OR REPLACE FUNCTION t1() RETURNS SETOF table1 AS $$ SELECT * FROM table1 $$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION t2() RETURNS SETOF table2 AS $$ SELECT * FROM table2 $$ LANGUAGE sql;
SELECT (t1()).*, (t2()).*;
If you use this in any real code then kittens will cry. It'll produce insane and bizarre results if the number of rows in the tables differ and it'll produce the rows in orderings that might seem right at first, but will randomly start coming out wrong later on.
THE SANE WAY is to add a primary key properly, then do a join.