insert multiple rows into sql using in statement - sql

I am trying to write an sql script to do a bulk insert. I need it to add the users that are managers into the manager's group. I tried to write it like this
INSERT INTO group_member (group_id, user_id) VALUES ((SELECT group_id FROM user_group WHERE group_name = 'Manager') , (SELECT user_id
FROM user WHERE manager=1 and user_status = 1));
but I am getting this error
Subquery returns more than 1 row
I understand the error but am not sure how to work around it so that I do not miss any users.
When run there can be 0 to many managers, not sure if that will make a difference.
sql version: 5.6.27
CREATE TABLE user_group(
group_id INT(11) NOT NULL AUTO_INCREMENT,
group_name VARCHAR(128) NULL DEFAULT NULL,
PRIMARY KEY (group_id)
);
CREATE TABLE user (
user_id INT(11) NOT NULL AUTO_INCREMENT,
user_name VARCHAR(128) NULL DEFAULT NULL,
manager INT(11) NOT NULL
user_status INT(11) NOT NULL
PRIMARY KEY (user_id)
);
CREATE TABLE group_member (
group_id INT(11) NOT NULL,
user_id INT(11) NOT NULL,
PRIMARY KEY (group_id, user_id)
);

You want insert . . . select:
INSERT INTO group_member (group_id, user_id)
SELECT g.group_id, u.user_id
FROM (SELECT group_id FROM user_group WHERE group_name = 'Manager') g CROSS JOIN
(SELECT user_id FROM user WHERE manager = 1 and user_status = 1) u;
If the group already has members, you might want to filter them out.
You can also write this as:
INSERT INTO group_member (group_id, user_id)
SELECT g.group_id, u.user_id
FROM user u JOIN
user_group g
ON g.group_name = 'Manager' AND
(u.manager = 1 and u.user_status = 1);

I need it to add the users that are managers into the manager's group
INSERT INTO group_member (group_id, user_id)
SELECT (SELECT group_id FROM `group` WHERE group_name = 'Manager'),
user_id
FROM user WHERE manager=1 and user_status = 1;
Of course, there must be only one group with the name of Manager.
Test
mysql> SELECT * FROM `user`;
+---------+-------------+---------+
| manager | user_status | user_id |
+---------+-------------+---------+
| 1 | 1 | 1 |
| 1 | 1 | 2 |
+---------+-------------+---------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM `group`;
+----------+------------+
| group_id | group_name |
+----------+------------+
| 17 | Manager |
+----------+------------+
1 row in set (0.00 sec)
mysql> INSERT INTO group_member (group_id, user_id)
-> SELECT (SELECT group_id FROM `group` WHERE group_name = 'Manager'),
-> user_id
-> FROM user WHERE manager=1 and user_status = 1;
Query OK, 2 rows affected (0.08 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * FROM group_member;
+----------+---------+
| group_id | user_id |
+----------+---------+
| 17 | 1 |
| 17 | 2 |
+----------+---------+
2 rows in set (0.00 sec)

Related

Earliest timestamp with join before group

I have 3 tables users, levels, attempts in PostgreSQL. I need to select the earliest attempts by attempts.created_at for each user for each level and get sum of attempts.rate for each user.
CREATE TABLE IF NOT EXISTS users
(
id BIGSERIAL PRIMARY KEY,
nickname VARCHAR(255) UNIQUE
);
CREATE TABLE IF NOT EXISTS levels
(
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
);
CREATE TABLE IF NOT EXISTS attempts
(
id BIGSERIAL PRIMARY KEY,
rate INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL,
level_id BIGINT REFERENCES levels (id),
user_id BIGINT REFERENCES users (id)
);
For example attempts content
id | rate | created_at | level_id | user_id
------------------------------------------------------------
1 | 10 | 2022-10-21 16:53:13.818000 | 1 | 1
2 | 20 | 2022-10-21 11:53:13.818000 | 1 | 1
3 | 30 | 2022-10-21 14:53:13.818000 | 1 | 1
4 | 40 | 2022-10-21 10:53:13.818000 | 2 | 1 -- (nickname = 'Joe')
5 | 100 | 2022-11-21 10:53:13.818000 | 1 | 2 -- (nickname = 'Max')
For level 1 and user 1 earliest row with id = 2 for level 2 with id = 4, I need select
nickname | sum
-----------------
Max | 100
Joe | 60
As a result for user Max (user with id = 1) sum of the earliest attempts of all levels = 100. And order by sum descending.
Something like this but how to select only one earliest attempt for each level before summing:
select u.nickname, sum(a.rate) as sum
from attempts a
inner join users u on a.user_id = u.id
inner join levels l on l.id = a.level_id
-- on a.created_at is the earliest for level and user
group by u.id
order by sum desc
select user_id
,sum(rate)
from
(
select distinct on (level_id, user_id) *
from t
order by level_id, user_id, created_at
) t
group by user_id
user_id
sum
2
100
1
60
Fiddle

POSTGRESQL - Find a row with a specific set of join table data

I have Users in Groups. I am trying to find which Group contains ONLY a specific set of Users. For instance Bob is in the group [Bob + John], but also in the group [Bob + John + Steve], and I would like to match the first one.
I use a join table groups_users to link Users to Groups.
I am having a hard time coming up with a query that will use the join table to match the users to the group, but also using that join table to exclude groups (groups that do not have the exact set of user searched).
Here is a fiddle with some data.
Schema (PostgreSQL v13 (Beta))
CREATE TABLE users (
id SERIAL PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
username VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE groups (
id SERIAL PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE groups_users (
group_id INT NOT NULL,
user_id INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_group
FOREIGN KEY(group_id)
REFERENCES groups(id),
CONSTRAINT fk_user
FOREIGN KEY(user_id)
REFERENCES users(id)
);
INSERT INTO users (username)
VALUES ('bob'), ('john'), ('steve');
INSERT INTO groups DEFAULT VALUES;
INSERT INTO groups DEFAULT VALUES;
INSERT INTO groups DEFAULT VALUES;
INSERT INTO groups_users (group_id, user_id)
VALUES (1, 1),
(1, 2),
(2, 2),
(2, 3),
(3, 1),
(3, 2),
(3, 3);
Query #1
SELECT * FROM groups_users
WHERE groups_users.user_id IN (1, 2);
| group_id | user_id | created_at |
| -------- | ------- | ------------------------ |
| 1 | 1 | 2020-11-22T16:12:35.796Z |
| 1 | 2 | 2020-11-22T16:12:35.796Z |
| 2 | 2 | 2020-11-22T16:12:35.796Z |
| 3 | 1 | 2020-11-22T16:12:35.796Z |
| 3 | 2 | 2020-11-22T16:12:35.796Z |
We see that we match groups 1, 2 and 3, but we only want to match 1, and I don't know how to go about querying this.
Thank you for your help.
You can group your groups and aggregate the user_ids into arrays. Than can compare these aggregations with created user_id arrays:
demo:db<>fiddle
SELECT
group_id
FROM
groups_users
GROUP BY group_id
HAVING ARRAY_AGG(user_id) = ARRAY[1,2]
This is gross and I apologize, but what this query does is gets a count of the row results for groups with your filter applied and then compares it to the total members of the group and only includes groups which only include those members.
SELECT t1.group_id FROM
(
SELECT group_id, COUNT(group_id) AS Instances FROM groups_users
WHERE groups_users.user_id IN (1, 2)
GROUP BY group_id
) T1
INNER JOIN
(
SELECT group_id, COUNT(group_id) AS Instances FROM groups_users
GROUP BY group_id
) T2
ON T1.group_id = T2.group_id and t1.Instances = t2.Instances

Insert data into two columns only if not duplicate

I have a table user_interests with id(AUTO_INC), user_id, user_interest columns.
I want a easy way to insert data into user_id and user_interest without duplicate entries.
E.g. if I have a table like this before.
+------------------------------+
| ID | user_id | user_interest |
+------------------------------+
| 1 | 2 | Music |
| 2 | 2 | Swimming |
+------------------------------+
If I now insert into table (user_id, user_interest) values ((2, Dance),(2, Swimming), I only need (2,dance) entry to be inserted - not (2, swimming) since (2, swimming) already exists in the table.
I have seen upsert commands, and have also tried creating a command like below but it doesn't work.
INSERT INTO `user_interests`( `user_id`,`interest` )
VALUES ("2","Music")
WHERE (SELECT COUNT(`interest`) FROM `user_interests`
WHERE `interest` = "Music" AND `user_id` = "2"
Having COUNT(`interest`) <=0 )
Use NOT EXISTS method :
INSERT INTO your_table (user_id ,user_interest )
SELECT #userId , #UserIntreset
WHERE NOT EXISTS(SELECT 1 FROM your_table user_id = #userid AND user_interest
= #userinterest )
Or Create unique constraint in your table,
ALTER TABLE your_table
ADD CONSTRAINT Constraint_Name UNIQUE (Column_Name1,Column_Name2)

PostgreSQL Composite Primary Key and Serial increment?

I'm trying to create a table as follows:
CREATE TABLE SCHEDULE (
SESSIONID SERIAL,
MODULECODE VARCHAR(10),
CONSTRAINT SCHEDULE_FOREIGN_KEY FOREIGN KEY (MODULECODE) REFERENCES MODULES (MODULECODE),
CONSTRAINT SCHEDULE_PRIMARY_KEY PRIMARY KEY (SESSIONID, MODULECODE));
The idea being that SESSION ID would auto increment with each new row but only local to MODULECODE, for example:
----------------------
|SESSIONID|MODULECODE|
|---------|----------|
| 1 | A |
| 2 | A |
| 3 | A |
| 1 | B |
| 2 | B |
| 1 | C |
| 2 | C |
|--------------------|
I believe this is how AUTO_INCREMENT functions in MySQL but I suspect PostgreSQL doesn't work this way. How else would I achieve this in PostgreSQL?
Show the data as suggested by #Juan
select
row_number() over (
partition by modulecode order by modulecode
) as sessionid,
modulecode
from schedule
Then when the user asks for a certain sessionid from a certain module do:
select *
from schedule
where sessionid = (
select sessionid
from (
select
sessionid,
row_number() over (order by sessionid) as module_sessionid
from schedule
where modulecode = 'B'
) s
where module_sessionid = 2
)
as hourse said you cant do it on your db. But you can asign those values in the select
SELECT row_number() over (partition by MODULECODE order by MODULECODE) as SESSIONID,
MODULECODE
FROM YourTable

SELECT to pick users who both viewed a page

I have a table that logs page views of each user:
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| view_id | int(11) | NO | PRI | NULL | auto_increment |
| page_id | int(11) | YES | MUL | NULL | |
| user_id | int(11) | YES | MUL | NULL | |
+--------------+--------------+------+-----+---------+----------------+
For every pair of users, I would like to generate a count of how many pages they have both looked at.
I simply do not know how to do this. : ) I am using mysql, in case it has a non-standard feature that makes this a breeze.
select u1.user_id, u2.user_id, count(distinct u1.page_id) as NumPages
from logtable u1
join
logtable u2
on u1.page_id = u2.page_id
and u1.user_id < u2.user_id /* This avoids counting pairs twice */
group by u1.user_id, u2.user_id;
But you should consider filtering this somewhat...
(Edited above to put u1.page_id, it was originally just page_id, which is really bad of me)
SELECT DISTINCT page_id
FROM logtable
WHERE user_id = 1 OR user_id = 2
GROUP BY page_id
HAVING COUNT(DISTINCT user_id) = 2
This table returns all pages they both have looked at. If you want the count, then just make this a subquery and count the rows.
SELECT COUNT(*) FROM (the query above) s;
Update, let's do it for all pairs of users then.
SELECT u1.user_id, u2.user_id, COUNT(DISTINCT u1.page_id)
FROM logtable u1, logtable u2
WHERE u1.user_id < u2.user_id
AND u1.page_id = u2.page_id
GROUP BY u1.user_id, u2.user_id
For users_ids 100 and 200.
SELECT
page_id
FROM table1
WHERE user_id IN (100, 200)
GROUP BY page_id
HAVING MAX(CASE WHEN user_id = 100 THEN 1 ELSE 0 END) = 1
AND MAX(CASE WHEN user_id = 200 THEN 1 ELSE 0 END) = 1;
select a.user_id as user1, b.user_id as user2, count(distinct a.page_id) as views
from yourtable a, yourtable b
where a.page_id = b.page_id
and a.user_id < b.user_id
group by a.user_id, b.user_id
change yourtable to the name of your table ..