Using SELECT in INSERT in Derby SQL - sql

I have the following SQL code to create and populate a few tables in Derby:
CREATE TABLE GROUPS (
GRP_ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1) PRIMARY KEY,
GRP_NAME VARCHAR(256) NOT NULL,
DISPLAY_NAME VARCHAR(256));
CREATE TABLE USERS (
USR_ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1) PRIMARY KEY,
USR_NAME VARCHAR(256) NOT NULL,
PASSWORD VARCHAR(512) NOT NULL,
DISPLAY_NAME VARCHAR(256));
CREATE TABLE USR_GRP_MEMBERSHIP (
USR_ID INTEGER NOT NULL REFERENCES USERS ON DELETE CASCADE,
GRP_ID INTEGER NOT NULL REFERENCES GROUPS ON DELETE CASCADE,
CONSTRAINT USR_ID_GRP_ID UNIQUE (USR_ID, GRP_ID));
INSERT INTO GROUPS(GRP_NAME, DISPLAY_NAME) VALUES('Users', 'Users');
INSERT INTO GROUPS(GRP_NAME, DISPLAY_NAME) VALUES('AdminUsers', 'Administrators');
INSERT INTO USERS(USR_NAME, PASSWORD, DISPLAY_NAME) VALUES('user', 'userpwd', 'User');
INSERT INTO USERS(USR_NAME, PASSWORD, DISPLAY_NAME) VALUES('admin', 'adminpawd', 'Administrator');
#The following code populates the USR_GRP_MEMBERSHIP table using the IDs of users
#& groups inserted in the previous set of insert statements
INSERT INTO USR_GRP_MEMBERSHIP VALUES((SELECT USR_ID FROM USERS WHERE USR_NAME = 'user'), (SELECT GRP_ID FROM GROUPS WHERE GRP_NAME = 'Users'));
INSERT INTO USR_GRP_MEMBERSHIP VALUES((SELECT USR_ID FROM USERS WHERE USR_NAME = 'admin'), (SELECT GRP_ID FROM GROUPS WHERE GRP_NAME = 'Users'));
INSERT INTO USR_GRP_MEMBERSHIP VALUES((SELECT USR_ID FROM USERS WHERE USR_NAME = 'admin'), (SELECT GRP_ID FROM GROUPS WHERE GRP_NAME = 'AdminUsers'));
My Question is: The last 3 INSERT statements have the SELECT statements repeating in them. How do I store the output of those SELECT statements in a variable and use them in these 3 INSERT statements?

One way to accomplish this is to use placeholders in prepared statements?
Here's a good starting point: https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
Your statement would then look something like:
INSERT INTO USR_GRP_MEMBERSHIP
VALUES ((SELECT USR_ID FROM USERS WHERE USR_NAME = ?),
(SELECT GRP_ID FROM GROUPS WHERE GRP_NAME = ?));
and you would replace the placeholders with 'user'/'Users', 'admin'/'Users', 'admin'/'AdminUsers' at runtime by using SetString (twice) and then ExecuteUpdate, for each pair.

Related

Create combination sql table

I'm trying to create a sql table in data base in VS that has room and userid column, but the sql will only accept your input if the userid exists in users table and room exists in rooms tables
Allows:
Users table:
Userid
1
2
3
RoomUsers table:
Room ----- User
1 1
2. 1
1. 2
1. 3
2. 3
Won't allow:
Users table:
Userid
1
2
RoomUsers table:
Room ----- User
1 4
Normal foreign key wont work because it only allows one of each index and not multiple, how can I allow what I need to occur,to happen?
(This would be a mess in comments)
Probably we are having an XY problem here. The thing you describe is simply solved with a foreign key. ie:
CREATE TABLE users (id INT IDENTITY NOT NULL PRIMARY KEY, ad VARCHAR(100));
CREATE TABLE rooms (id INT IDENTITY NOT NULL PRIMARY KEY, ad VARCHAR(100));
CREATE TABLE room_user
(
RoomId INT NOT NULL
, UserId INT NOT NULL
, CONSTRAINT PK_roomuser
PRIMARY KEY(RoomId, UserId)
, CONSTRAINT fk_room
FOREIGN KEY(RoomId)
REFERENCES dbo.rooms(id)
, CONSTRAINT fk_user
FOREIGN KEY(UserId)
REFERENCES dbo.users(id)
);
INSERT INTO dbo.users(ad)
OUTPUT
Inserted.id, Inserted.ad
VALUES('RayBoy')
, ('John')
, ('Frank');
INSERT INTO dbo.rooms(ad)
OUTPUT
Inserted.id, Inserted.ad
VALUES('Room1')
, ('Room2')
, ('Room3');
INSERT INTO dbo.room_user(RoomId, UserId)VALUES(1, 1), (1, 2), (2, 3);
-- won't allow
INSERT INTO dbo.room_user(RoomId, UserId)VALUES(999, 888);

Add data to many-to-many relation with one SQL command

I have a basic understanding of SQL databases and I might overlooked something, but I cannot figure out the following problem: there is a many-to-many relationship (for example: users - user_roles - roles). Is it possible to add (new) role to a (new) user with one SQL command (atomic operation)? Currently I use Sqlite.
I am aware of the SELECT last_insert_rowid(); command and with this and several SQL commands I can achieve what I want. But I want to incorporate it into one command (so the server, in this case Sqlite, can optimize the query, etc.). I have no idea, how it is done in real life (one command vs. several one in one transaction), that´s the root cause of this question.
So far this is what I was able to do:
pragma foreign_keys = on;
CREATE TABLE users (
user_id integer primary key autoincrement,
user_name text not null unique
);
CREATE TABLE roles (
role_id integer primary key autoincrement,
role_name text not null unique
);
CREATE TABLE user_roles (
user_id integer not null,
role_id integer not null,
foreign key (user_id) references users(user_id),
foreign key (role_id) references roles(role_id),
primary key (user_id, role_id)
);
insert into users (user_name) values ('Joe');
insert into roles (role_name) values ('admin');
insert into user_roles (user_id, role_id) values (
(select user_id from users where user_name = 'Joe'),
(select role_id from roles where role_name = 'admin')
);
If both user and role exists (Joe and admin), then it works fine.
But I cannot figure out, how to achieve "add-if-missing-then-return-id" behavior if Joe or admin is mission from database.
Example (both user and role are missing):
insert into user_roles (user_id, role_id) values (
(select user_id from users where user_name = 'Bill'),
(select role_id from roles where role_name = 'user')
);
Result:
Execution finished with errors.
Result: NOT NULL constraint failed: user_roles.user_id
You could create view from user_roles table:
CREATE VIEW user_roles_view AS
SELECT
U.user_name, R.role_name
FROM user_roles AS UR
INNER JOIN users AS U ON u.user_id = UR.user_id
INNER JOIN roles AS R ON r.role_id = UR.role_id;
Views in SQLite are read-only unless you create an INSTEAD OF trigger on it. This way you can specify a command or sequence of commands that are executed when the view is modified using INSERT, UPDATE or DELETE statement. For INSERT it could go like this:
CREATE TRIGGER user_roles_view_insert INSTEAD OF INSERT ON user_roles_view
BEGIN
INSERT OR IGNORE INTO users (user_name) VALUES (NEW.user_name);
INSERT OR IGNORE INTO roles (role_name) VALUES (NEW.role_name);
INSERT OR IGNORE INTO user_roles (user_id, role_id) VALUES (
(SELECT user_id FROM users WHERE user_name = NEW.user_name),
(SELECT role_id FROM roles WHERE role_name = NEW.role_name)
);
END;
Note the usage of INSERT OR IGNORE to prevent inserting duplicate values into all of the three tables. Here's how you would insert values via the view:
INSERT INTO user_roles_view VALUES ('Joe', 'admin');
-- The above statement creates:
-- a row (1, 'Joe') in table users,
-- a row (1, 'admin) in table roles,
-- a row (1, 1) in table user_roles.
INSERT INTO user_roles_view VALUES ('Joe', 'admin');
-- The above statement doesn't add any additional records, because all appropriate records
-- already exist.
INSERT INTO user_roles_view VALUES ('Joe', 'system');
-- The above statement creates:
-- a row (2, 'system') in table roles,
-- a row (1, 2) in table user_roles.
INSERT INTO user_roles_view VALUES ('Alice', 'admin'), ('Bob', 'system');
-- The above statement creates:
-- a row (2, 'Alice') in table users,
-- a row (3, 'Bob') in table users,
-- a row (2, 1) in table user_roles,
-- a row (3, 2) in table user_roles
All of the above statements produce the following output from user_roles_view (SELECT * FROM user_roles_view):
user_name
role_name
Joe
admin
Joe
system
Alice
admin
Bob
system

Complex SQL Count Query

Hello I've been stuck with one SQL query for my assignment and was hoping for some help.
I need to get the Project ID for the best executed project -the project where (VERY_GOOD record count + GOOD record count) - (VERY_BAD record count + BAD record count) is greatest
My schema and test records in database (HSQLDB)
CREATE TABLE
PROJECT
(
ID IDENTITY NOT NULL PRIMARY KEY,
PROJECT_NAME VARCHAR(255) NOT NULL
);
CREATE TABLE
RECORD
(
ID IDENTITY NOT NULL PRIMARY KEY,
RESULT VARCHAR(255) NOT NULL,
);
CREATE TABLE
RECORD_PROJECT
(
PROJECT_ID INTEGER NOT NULL,
RECORD_ID INTEGER NOT NULL,
PRIMARY KEY(PROJECT_ID, RECORD_ID),
FOREIGN KEY (PROJECT_ID) REFERENCES PROJECT(ID) ON DELETE CASCADE,
FOREIGN KEY (RECORD_ID) REFERENCES RECORD(ID)
);
And test data:
INSERT INTO PROJECT (PROJECT_NAME) VALUES ('Bake a cake');
INSERT INTO PROJECT (PROJECT_NAME) VALUES ('Clean the house');
INSERT INTO RECORD (RESULT) VALUES ('GOOD');
INSERT INTO RECORD (RESULT) VALUES ('VERY_GOOD');
INSERT INTO RECORD (RESULT) VALUES ('VERY_GOOD');
INSERT INTO RECORD (RESULT) VALUES ('BAD');
INSERT INTO RECORD (RESULT) VALUES ('VERY_BAD');
INSERT INTO RECORD_PROJECT (PROJECT_ID, RECORD_ID) VALUES (0,0);
INSERT INTO RECORD_PROJECT (PROJECT_ID, RECORD_ID) VALUES (1,1);
INSERT INTO RECORD_PROJECT (PROJECT_ID, RECORD_ID) VALUES (1,2);
INSERT INTO RECORD_PROJECT (PROJECT_ID, RECORD_ID) VALUES (0,3);
INSERT INTO RECORD_PROJECT (PROJECT_ID, RECORD_ID) VALUES (1,4);
(I removed unrelated fields from tables)
So with this data I have 3 good records and 2 bad, I would need to get the project which has the highest 'rating', which according to this right now would be Clean the house with 3 good ratings over 2 negative for other project.
Maybe someone would figure this out, thanks!
That should be the (not testet) SQL in MySQL-Dialect:
SELECT rp.PROJECT_ID, p.PROJECT_NAME
SUM(CASE WHEN rp.RECORD_ID < 3 THEN 1 ELSE 0 END) AS rating
FROM RECORD_PROJEKT AS rp
JOIN PROJECT AS p ON p.ID = rp.PROJECT_ID
GROUP BY rp.PROJECT_ID
ORDER BY rating DESC

what is efficient way to represent data entered by two different users in the database

I have a table called Request and data will be entered by two types of users (Company staff and site members). Initially I had a column called createdby. But, the staff and the members table primary keys are integers with identity. So i had to change it because there could be an ID that corresponds to both entities.
Then I have created two columns in the the request table ByStaffId and ByMemberID. I wonder whether it is the right way.
Given the constraints of the existing tables, your approach sounds reasonable, and means that you will be able to create foreign key constraints from the Request table to the Staff and Member tables. It would not have been possible to create any foreign key constraints with your previous approach (a single CreatedBy field).
If the opportunity to refactor the existing design is available, consider treating Staff and Members as subclasses of the same abstract type (e.g. "User"), using one of the patterns for mapping inheritance to relational tables described here.
The way thing are set up for you now what you did by creating the two columns is the solution.
But, it's not a good one. Basically you should only have one Users table with some way to differ the user types like a separate table(Roles, UserType etc). Then you would be able to have a foreign key in your Request table that will reference the user that created the initial request.
Below is an example of how to relate the Staff and Members together using a common User table that has a 1-to-1 relationship with both a StaffUser table and a MemberUser table.
Granted, this approach results in greater complexity when selecting/inserting/updating/deleting users, so you can decide whether this is worth the extra complexity.
create table [User]
(
UserID int identity(1,1) not null primary key,
Username nvarchar(50) not null
)
create table StaffUser
(
UserID int not null primary key references [User] (UserID),
FirstName nvarchar(50) not null,
LastName nvarchar(50) not null
)
create table MemberUser
(
UserID int not null primary key references [User] (UserID),
Email nvarchar(100) not null,
)
create table Request
(
ByUserID int not null references [User] (UserID),
)
declare #UserID int
insert into [User] values ('john.smith')
set #UserID = scope_identity()
insert into StaffUser values (#UserID, 'John', 'Smith')
insert into Request values (#UserID)
insert into [User] values ('billy.bob')
set #UserID = scope_identity()
insert into StaffUser values (#UserID, 'Billy', 'Bob')
insert into Request values (#UserID)
insert into [User] values ('member1')
set #UserID = scope_identity()
insert into MemberUser values (#UserID, 'member1#awesome.com')
insert into Request values (#UserID)
insert into [User] values ('member2')
set #UserID = scope_identity()
insert into MemberUser values (#UserID, 'member2#awesome.com')
insert into Request values (#UserID)
insert into [User] values ('member3')
set #UserID = scope_identity()
insert into MemberUser values (#UserID, 'member3#awesome.com')
insert into Request values (#UserID)
-- select staff
select
StaffUser.UserID,
[User].Username,
StaffUser.FirstName,
StaffUser.LastName
from StaffUser
inner join [User] on
[User].UserID = StaffUser.UserID
-- select members
select
MemberUser.UserID,
[User].Username,
MemberUser.Email
from MemberUser
inner join [User] on
[User].UserID = MemberUser.UserID
-- select all users
select
StaffUser.UserID,
[User].Username
from StaffUser
inner join [User] on
[User].UserID = StaffUser.UserID
union all
select
MemberUser.UserID,
[User].Username
from MemberUser
inner join [User] on
[User].UserID = MemberUser.UserID
select * from Request
drop table Request
drop table MemberUser
drop table StaffUser
drop table [User]
Below is a slightly more complicated structure that accomplishes the same thing as the example above, but in this case Member and Staff are more decoupled from User.
create table [User]
(
UserID int identity(1,1) not null primary key,
CreatedOn datetime not null default getdate()
)
create table StaffUser
(
UserID int not null primary key references [User] (UserID)
)
create table MemberUser
(
UserID int not null primary key references [User] (UserID)
)
create table Staff
(
StaffID int identity(1,1) not null primary key,
FirstName nvarchar(50) not null,
LastName nvarchar(50) not null,
UserID int null references StaffUser (UserID),
)
create table Member
(
MemberID int identity(1,1) not null primary key,
Username nvarchar(50),
Email nvarchar(100) not null,
UserID int null references MemberUser (UserID),
)
create table Request
(
ByUserID int not null references [User] (UserID),
)
declare #UserID int
insert into [User] default values
set #UserID = scope_identity()
insert into StaffUser values (#UserID)
insert into Staff values ('John', 'Smith', #UserID)
insert into Request values (#UserID)
insert into [User] default values
set #UserID = scope_identity()
insert into StaffUser values (#UserID)
insert into Staff values('Billy', 'Bob', #UserID)
insert into Request values (#UserID)
insert into [User] default values
set #UserID = scope_identity()
insert into MemberUser values (#UserID)
insert into Member values ('member1', 'member1#awesome.com', #UserID)
insert into Request values (#UserID)
insert into [User] default values
set #UserID = scope_identity()
insert into MemberUser values (#UserID)
insert into Member values ('member2', 'member2#awesome.com', #UserID)
insert into Request values (#UserID)
insert into [User] default values
set #UserID = scope_identity()
insert into MemberUser values (#UserID)
insert into Member values ('member3', 'member3#awesome.com', #UserID)
insert into Request values (#UserID)
-- select staff
select
Staff.StaffID,
Staff.FirstName,
Staff.LastName,
Staff.UserID
from Staff
-- select members
select
Member.MemberID,
Member.Username,
Member.Email,
Member.UserID
from Member
-- select all users
select
[User].UserID,
Staff.FirstName + ' ' + Staff.LastName as Name,
[User].CreatedOn
from [User]
inner join Staff on
Staff.UserID = [User].UserID
union all
select
[User].UserID,
Member.Username as Name,
[User].CreatedOn
from [User]
inner join Member on
Member.UserID = [User].UserID
select * from Request
drop table Request
drop table Member
drop table Staff
drop table MemberUser
drop table StaffUser
drop table [User]
Why you ever wanted to use separate tables to distinguish members from staff? I'd rather implement Role table and distinguish users by assigned roles.
.createdby column that may reference to one of these tables? Bad idea but it might work if you'd use Guid as primary key in staff and memvers

Database auto-increment column

Is it possible to create a Database which has 1 column (but not the column of primary key) to be auto-increment? So that when I insert value to the database, i don't need to fill in the value myself, and DB will fill in that value for that column for me (and increment every time I do a new insert)?
Thank you.
Yes, of course it is possible. Just make this column a unique key (not a primary key) and it has to be declared with a special attribute: "IDENTITY" for SQL Server, and
"AUTO_INCREMENT" for MySQL (see the example below) . And another column can be a primary key.
On MySQL database the table could be declared like this:
CREATE TABLE `mytable` (
`Name` VARCHAR(50) NOT NULL,
`My_autoincrement_column` INTEGER(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`Name`),
UNIQUE KEY `My_autoincrement_column` (`My_autoincrement_column`)
);
Yes, you can do this. Here is a sample for SQL Server using IDENTITY:
CREATE TABLE MyTable (
PrimaryKey varchar(10) PRIMARY KEY,
IdentityColumn int IDENTITY(1,1) NOT NULL,
DefaultColumn CHAR(1) NOT NULL DEFAULT ('N')
)
INSERT INTO MyTable (PrimaryKey) VALUES ('A')
INSERT INTO MyTable (PrimaryKey) VALUES ('B')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('C', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('D', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('E', DEFAULT)
--INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('F', NULL) -- ERROR
--> Cannot insert the value NULL into column 'DefaultColumn', table 'tempdb.dbo.MyTable'; column does not allow nulls. INSERT fails.
SELECT * FROM MyTable
Here is an example using SQL Server using functions to roll-your-own incrementing column. This is by means not fault tolerant or the way I would do it. (I'd use the identity feature.) However, it is good to know that you can use functions to return default values.
DROP TABLE MyTable
GO
DROP FUNCTION get_default_for_mytable
GO
CREATE FUNCTION get_default_for_mytable
()
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #ResultVar int
-- Add the T-SQL statements to compute the return value here
SET #ResultVar = COALESCE((SELECT MAX(HomeBrewedIdentityColumn) FROM MyTable),0) + 1
-- Return the result of the function
RETURN #ResultVar
END
GO
CREATE TABLE MyTable (
PrimaryKey varchar(10) PRIMARY KEY,
IdentityColumn int IDENTITY(1,1) NOT NULL,
DefaultColumn CHAR(1) NOT NULL DEFAULT ('N'),
HomeBrewedIdentityColumn int NOT NULL DEFAULT(dbo.get_default_for_mytable())
)
GO
INSERT INTO MyTable (PrimaryKey) VALUES ('A')
INSERT INTO MyTable (PrimaryKey) VALUES ('B')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('C', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('D', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('E', DEFAULT)
--INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('F', NULL) -- ERRROR
--> Cannot insert the value NULL into column 'DefaultColumn', table 'tempdb.dbo.MyTable'; column does not allow nulls. INSERT fails.
SELECT * FROM MyTable
Results
PrimaryKey IdentityColumn DefaultColumn HomeBrewedIdentityColumn
---------- -------------- ------------- ------------------------
A 1 N 1
B 2 N 2
C 3 Y 3
D 4 Y 4
E 5 N 5
I think you can have only 1 identity autoincrement column per table, this columns doesn't have to be the primary key but it would mean you have to insert the primary key yourself.
If you already have a primary key which is auto increment then I would try and use this if possible.
If you are trying to get an row ID to range on for querying then I would look at creating a view which has the row ID in it (not SQL 2000 or below).
Could you add in what your primary key is and what you intend to use the auto increment column for and it might help come up with a solution
On sql server this is called an identity column
Oracle and DB2 have sequence but I think you are looking for identity and all major dbms (mysql, sql server, db2, oracle) support it.