Substitute for MySQL's variables in PostgreSQL? - mysql

We often use quick one-off SQL files to insert or update data in an existing database. The SQL is usually written by a developer, tested on the development system, and then imported in the production DB with psql -U dbuser dbname < file.sql.
A (trivial) example might look like this:
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
) VALUES
(
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
'Frodo Baggins',
'Ring bearer',
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
),
(
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
'Samwise Gamgee',
'Rope bearer',
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
),
(
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
'Peregrin Took',
'Ent rider',
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
);
While this works, there's a lot of repetitive code in the subqueries. It would be nice (more efficient and less error prone) to store the relevant values for companies.id and users.id in temporary variables. In this construed example, the performance difference is likely minimal, but in practice we do have more complex queries and updates, and there are often more than three updated/inserted records.
The same example written for MySQL looks like this:
SELECT #company_id := id FROM companies WHERE name = 'Acme Fellowship';
SELECT #admin_id := id FROM users WHERE login = 'admin';
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
) VALUES
(#company_id, 'Frodo Baggins', 'Ring bearer', #admin_id, #admin_id),
(#company_id, 'Samwise Gamgee', 'Rope bearer', #admin_id, #admin_id),
(#company_id, 'Peregrin Took', 'Ent rider', #admin_id, #admin_id);
Is there any way to achieve something similar in PostgreSQL?
What I've looked at:
psql's session variables (with \set): cannot be used to store query results
plpgsql: can only be used in a procedure (we're still running 8.4)
temporary tables: I can't see how to use them without creating ugly and convoluted statements
If there is no direct equivalent for Postgres, what do you think would be the least clumsy way to produce update files of this kind?

Use VALUES() in a SELECT, that should work:
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
)
SELECT
(SELECT id FROM companies WHERE name = 'Acme Fellowship'),
name,
position,
(SELECT id FROM users WHERE login = 'admin'),
(SELECT id FROM users WHERE login = 'admin')
FROM
(VALUES -- all your new content here
('Frodo Baggins', 'Ring bearer'),
('Samwise Gamgee', 'Rope bearer'),
('Peregrin Took', 'Ent rider')
) content(name, position); -- use some aliases to make it readable

Consider using CTEs or subqueries to query values once and inserted them many times.
This way, you can replace MySQL style variables with standard SQL.
INSERT INTO employees
(company_id, name, position, created_by, last_modified_by)
SELECT c.id , name, position, u.id , u.id
FROM (SELECT id FROM companies WHERE name = 'Acme Fellowship') c
,(SELECT id FROM users WHERE login = 'admin') u
,(VALUES
('Frodo Baggins', 'Ring bearer')
,('Samwise Gamgee', 'Rope bearer')
,('Peregrin Took', 'Ent rider')
) v(name, position)
Assuming that companies.name and users.login are, in fact, unique. Multiple hits would multiply the rows to be inserted.
Read about the INSERT command in the manual.
Here is my test setup with temporary tables in case anyone wants to have a quick look:
CREATE TEMP TABLE companies (id int, name text);
INSERT INTO companies VALUES (17, 'Acme Fellowship');
CREATE TEMP TABLE users (id int, login text);
INSERT INTO users VALUES (9, 'admin');
CREATE TEMP TABLE employees (
company_id int
,name text
,position text
,created_by int
,last_modified_by int);

This is an old question, but I found that using WITH statements made my life easier :)
WITH c AS (
SELECT company_id,
FROM companies
WHERE name = 'Acme Fellowship'
), u AS (
SELECT *
FROM users
WHERE login = 'admin'
), n AS (
SELECT *
FROM
(VALUES -- all your new content here
('Frodo Baggins', 'Ring bearer'),
('Samwise Gamgee', 'Rope bearer'),
('Peregrin Took', 'Ent rider')
) content(name, position)
)
INSERT INTO employees (
company_id,
name,
position,
created_by,
last_modified_by
)
SELECT c.company_id, n.name, n.position, u.id, u.id
FROM c, u, n

Related

How to filter a table base on json array value in another table

I have 2 tables, one for users and one for posts:
create database db;
create table if not exists db.users
(
uid char(10) primary key,
username char(10),
following json,
blocked json
);
insert into db.users (uid, username, following, blocked)
VALUES ('uid_0', 'user_0', '["uid_1", "uid_2"]', '["uid_3"]');
insert into db.users (uid, username, following, blocked)
VALUES ('uid_1', 'user_1', '["uid_0", "uid_2", "user_3"]', '[]');
insert into db.users (uid, username, following, blocked)
VALUES ('uid_2', 'user_2', '["uid_0"]', '[]');
insert into db.users (uid, username, following, blocked)
VALUES ('uid_3', 'user_3', '["uid_1"]', '[]');
create table if not exists db.posts
(
id char(10) primary key,
owner char(10),
text char(100)
);
insert into db.posts (id, owner, text)
VALUES ('post_0', 'uid_0', 'text_0');
insert into db.posts (id, owner, text)
VALUES ('post_1', 'uid_1', 'text_1');
insert into db.posts (id, owner, text)
VALUES ('post_2', 'uid_2', 'text_2');
insert into db.posts (id, owner, text)
VALUES ('post_3', 'uid_3', 'text_3');
What I want to do is to query the posts of one user based on the following list and on the blocked list.
The far I could go is to transform the following list into a table using a sentinel table that just has numbers from 0 to 1000.
SET #following = (select following
from firestore_mirror.users
where uid = 'userId');
SELECT JSON_EXTRACT(#following, CONCAT('$[', helper._row, ']')) as uid
FROM (SELECT #following AS helper) AS A
INNER JOIN firestore_mirror.t_list_row AS helper
ON helper._row < JSON_LENGTH(#following);
this gives me this
"value_0"
"value_1"
"value_2"
"value_3"
"value_4"
But when I try this I just get an empty result
SET #following = (select following
from firestore_mirror.users
where uid = 'userId');
select *
from firestore_mirror.posts
where owner in (SELECT JSON_EXTRACT(#following, CONCAT('$[', helper._row, ']')) as uid
FROM (SELECT #following AS helper) AS A
INNER JOIN firestore_mirror.t_list_row AS helper
ON helper._row < JSON_LENGTH(#following));
I am using Cloud SQL with MySql 8.0.
In my head (:))) the result that I am expecting would be
select *
from db.posts
where owner in (select following from db.users where uid = 'uid_0')
and owner not in (select blocked from db.users where uid = 'uid_0');
id, owner, text
"post_1", "uid_1", "text_1"
"post_2", "uid_2", "text_2"
SELECT posts.*
FROM users u1
JOIN users u2 ON JSON_SEARCH(u1.following, 'one', u2.uid)
-- AND JSON_SEARCH(u1.blocked, 'one', u2.uid) IS NULL
JOIN posts ON u2.username = posts.owner
WHERE u1.uid = #uid
If the same uid may be present in both following and blocked columns, and the posts owned by such user must not be returned, then uncomment.
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=2399bc226e47b3b93e3e5016908677ee

Get users who don't have sent email MySQL

My DB has the following tables,
User (id, name)
EmailTemplate (id, subject, template)
EmailTrack (id, user_id, email_template_id)
I want to get the users who don't have sent specific email_template yet by the SQL query.
You could use the not exists operator:
SELECT *
FROM user u
WHERE NOT EXISTS (SELECT *
FROM emailtemplate temp
JOIN emailtrack track ON temp.id = track.email_template_id
WHERE template = 'some_specific_template' AND
track.user_id = u.id)

UNION query functionality and the query structure

What I'm trying to achieve is to insert values into a table if that value does not exist in 2 extra tables.
INSERT INTO visitor(
visitor_username,
email,
PASSWORD
)
SELECT * FROM
(
SELECT
'admin2000',
'adminemail#mail.com',
'123456'
) AS tmp
WHERE NOT EXISTS
(
SELECT
admin.admin_username,
admin.email
FROM
admin AS admin
WHERE
admin.admin_username = 'admin2000' AND admin.email =
'adminemail#mail.com'
UNION
SELECT
staff.staff_username,
staff.email
FROM
staff AS staff
WHERE
staff.staff_username = 'admin2000' AND staff.email =
'adminemail#mail.com'
)
LIMIT 1
In the WHERE NOT EXIST part when I only ask for *_username (example: admin_username or staff_username) it works well, but when I need to verify if the email exists too it does not work as intended.
Am I using WHERE NOT EXIST properly? if the username 'admin2000' and email 'adminemail#mail.com' exist on the table 'admin' and I'm trying to insert it into 'visitor' table it inserts it and it should not be doing that.
The problem is the AND in the subqueries. It searches for rows that have that username AND that email. So if you have an admin called admin2000, but with a different e-mail address, that admin won't be returned by the subquery, and so the new row will be inserted.
Use OR instead of AND, and the the problem will be solved.
You seem to want to be writing a query like this:
INSERT INTO visitor (visitor_username, email, PASSWORD)
SELECT t.*
FROM (SELECT 'admin2000' as visitor_username, 'adminemail#mail.com' as email, '123456' as PASSWORD
) t
WHERE NOT EXISTS (SELECT 1
FROM admin a
WHERE a.visitor_username = t.visitor_username AND a.email = t.email
)
UNION
SELECT s.staff_username, s.email, ? as password
FROM staff s
WHERE s.staff_username = 'admin2000' AND s.email =
'adminemail#mail.com';
Note that the second subquery is missing a password, so there is an error.
This seems more concisely written using a single query:
INSERT INTO visitor (visitor_username, email, PASSWORD)
SELECT t.*
FROM (SELECT 'admin2000' as visitor_username, 'adminemail#mail.com' as email, '123456' as PASSWORD
) t
WHERE NOT EXISTS (SELECT 1
FROM admin a
WHERE a.admin_username = t.visitor_username AND a.email = t.email
) AND
NOT EXISTS (SELECT 1
FROM staff s
WHERE s.staff_username = t.visitor_username AND s.email = t.email
);

Sql renaming several raw data

I have been through some solutions published here, but none of them solved my problem.
I want to set my username column to receive now unique usernames.
But for that I need to rename in certain situations, more than 1000 duplicated usernames already registered.
I tried this solution:
UPDATE profile n
JOIN (SELECT username, MIN(profile_id) min_id FROM profile
GROUP BY username HAVING COUNT(*) > 1) d
ON n.username = d.username AND n.profile_id <> d.min_id SET n.username = CONCAT(n.username, '1');
But it gives me for the same user name for example tony, tony1, tony11, tony111 and so on up to tony1111111111111... up to 1000, make the username have a long long lenght.
I would like a solution to get only up 4digites after the username word. 0001,0002,0003,0004....1000
Can somebody help me here?
Thank you in advance
how about something like:
UPDATE profile n JOIN (
SELECT profile_id, username, (#row_number:=#row_number+1) as cntr
FROM profile, (SELECT #row_number:=0) AS t
WHERE username IN ( SELECT username
FROM profile
GROUP BY username HAVING COUNT(*) > 1 )
and (username, profile_id) not in ( SELECT username, MIN(profile_id)
FROM profile
GROUP BY username HAVING COUNT(*) > 1 )
) d ON n.profile_id = d.profile_id
SET n.username = CONCAT(n.username, d.cntr);
This is the best I can come up with at the moment.... the problem is that it will share the counter between all usernames... you you will have Alejandro, Alejandro1, Pedro, Pedro2, Juan, Juan3 .....
I believe that this that you've commented is wrong...No Update. Only select: select * from profile n JOIN ( SELECT username, min_id, #row_number:=#row_number+1 as cntr FROM ( SELECT username, MIN(profile_id) min_id FROM profile GROUP BY username HAVING COUNT(*) > 1 ) AS t2 , (SELECT #row_number:=0) AS t ) d ON n.username = d.username

MySQL Query: select all the books a user has completed reviewing

I have the following 5 tables:
users(user_id)
books(book_id, author_id)
source_phrases(source_phrase_id, book_id, phrase)
synonym_phrases(synonym_phrase_id, source_phrase_id, reader_id, synonym)
synonym_ratings(synonym_ratings_id, synonym_phrase_id, rater_id, rating)
I am trying to get a query that will select all the books a user has completed reviewing.
A user will have completed reviewing a book if they have done the following for each source phrase:
User has suggested a synonym for the source phrase (reader_id in synonym_phrases table is the users id)
OR
The user has rated a synonym for the source phrase (rater_id in synonym_ratings table is the users id)
SELECT b.*
FROM books b
WHERE book_id NOT IN
(
SELECT sp.book_id
FROM source_phrases sp
WHERE source_phrase_id NOT IN
(
SELECT syp.source_phrase_id
FROM synonym_phrases syp
WHERE reader_id = #user_id
)
AND source_phrase_id NOT IN
(
SELECT syp.source_phrase_id
FROM synonym_phrases syp
JOIN synonym_ratings sr
ON sr.synonym_phrase_id = syp.synonym_phrase_id
AND sr.rater_id = #user_id
)
)
AND book_id IN
(
SELECT sp.book_id
FROM source_phrases sp
)