SQL Query causing me lack of sleep - sql

So I am revising for an exam and have struck a big rock in the SQL river (or waste ground)
I made the following tables and inserted the following data:
create table Permissions
(
fileName VARCHAR(40),
userID VARCHAR (16),
type VARCHAR(10),
startdate DATE,
duration NUMBER (5),
constraint Pri_key PRIMARY KEY (userID,fileName)
);
create table Files
(
name VARCHAR(20),
fsize INT,
numberofpermissions INT,
constraints PRI_KEY2 PRIMARY KEY (name)
);
create table Users
(
id VARCHAR(20),
password VARCHAR (20),
constraint Pri_key3 PRIMARY KEY (id)
);
-- after all tables create:
alter table Permissions
add constraint Forn_key FOREIGN KEY (userID) REFERENCES Users(id)
INITIALLY DEFERRED DEFERRABLE;
alter table Permissions
add constraint Forn_key2 FOREIGN KEY (filename) REFERENCES Files(name)
INITIALLY DEFERRED DEFERRABLE;
insert into Permissions VALUES ('Agenda','Jones','read','19-JAN-10',30);
insert into Permissions VALUES ('Agenda','Chun','read','19-JAN-10',30);
insert into Permissions VALUES ('Agenda','Rashid','write','17-JAN-10',50);
insert into Permissions VALUES ('Finance','Chun','write','05-DEC-09',50);
insert into Permissions VALUES ('AnnualReport','Jones','write','12-DEC-09',50);
insert into Users VALUES ('Jones', 'duck');
insert into Users VALUES ('Chun', 'tiger');
insert into Users VALUES ('Adams', 'shark');
insert into Users VALUES ('Rashid', 'puma');
insert into Files VALUES ('Agenda', 32, 3);
insert into Files VALUES ('FinanceTables',645, 0);
insert into Files VALUES ('Finance', 120, 1);
insert into Files VALUES ('AnnualReport', 1205, 1);
commit;
I Am now trying to write a SQL command to display for each user who has
permissions for files of a total size of more than 50: the user’s
id, the total size of all the files the user has permissions for, and
the user’s password.
Here is what I have so far but when i try to add anything in to get the password, SQL+ throws up a hissy fit and there will be a hole in my screen soon!
SELECT permissions.userID, sum(fsize) AS Totalsize
FROM files, permissions
where permissions.filename = files.name
group by permissions.userid
having SUM(fsize) > 50;

In oracle, you need to specify the entire group by
SELECT permissions.userID, users.password, sum(fsize) AS Totalsize
FROM files, permissions, users
where permissions.filename = files.name
and users.id = permissions.userID
group by permissions.userid, permissions.password
having SUM(fsize) > 50;
This is different from MySQL where the group by can be implied, but this is more correct.

Join against the user table, and add the password to the group by clause:
SELECT permissions.userID, users.password, sum(fsize) AS Totalsize
FROM files, permissions, users
where permissions.filename = files.name
and users.id = permissions.userID
group by permissions.userid, users.password
having SUM(fsize) > 50;

Use the JOIN syntax. It complies with ANSI SQL. Joining using WHERE is an old syntax that should not be used any more.
SELECT
u.id AS userid, u.password, SUM(f.fsize) AS Totalsize
FROM
users u
INNER JOIN permissions p
ON u.id = p.userID
INNER JOIN files f
ON p.filename = f.name
GROUP BY
u.id, u.password
HAVING
SUM(f.fsize) > 50;
Note that conditions based on aggregation functions must be placed in the HAVING clause. The difference between the WHERE and the HAVING clause is, that WHERE is executed before grouping and HAVING is executed after grouping.
Also, the GROUP BY clause must contain all the expressions from the SELECT-list that don't have an aggregation function.

This should work:
SELECT Users.id, Users.password, Totalsize
FROM Users
INNER JOIN
(SELECT userId, SUM(fSize) AS TotalSize
FROM permissions
INNER JOIN files ON permissions.filename = files.name
group by permissions.userid
having SUM(fsize) > 50) t ON t.userId = Users.Id
This could be done without the inner join of course, however if you gonna need more fields this makes it easier to add them without having to add the extra fields to the group by clause.

Related

How to access a specific column in the result set of a cursor

I'm creating a cursor to collect a set of rows that I want to delete in a table. But I also want to delete records in related tables that key off the table. Is it possible to get the field in a single column of a cursor so that I can use that value to delete the fields in the other tables as well (The field isn't explicitly defined as a foreign key in the other tables)
Agree with Sami's comment; people tend to go to cursors because they think in a row-by-row processing mode, but SQLServer is totally focused on operating on things as a set. Consider building a temp table of all the rows you want to delete, then run delete operations that use that temp table as a driver for which rows in which other tables need deleting. If you can't use cascading deletes from FKs you need to engage in joining and delete in order from child up to parent
Example:
CREATE TABLE #delusers (userid INT);
INSERT INTO #delusers VALUES(1);
INSERT INTO #delusers VALUES(2);
INSERT INTO #delusers VALUES(3);
--For relationships:
-- User.ID--hasmany-->Order.UserID and
-- Order.ID--hasmany-->OrderProduct.OrderID
BEGIN TRANSACTION;
DELETE FROM OrderProducts WHERE OrderID IN (SELECT o.id FROM orders o INNER JOIN #delusers u on o.userid = u.userid);
DELETE FROM Orders WHERE UserID IN (SELECT userid from #delusers);
DELETE FROM Users WHERE UserID IN (SELECT userid from #delusers);
COMMIT TRANSACTION;
Here I've used a temp table as an example, but it's as easy to use a table var or even the original table just with a where clause if you feel the need. I've also used IN as it's the easiest way to deliver a readable demo of the overall point; you may choose some different method of coordination
Example 2:
--delete all orders and products on those orders from people with last name smith
--For relationships:
-- User.ID--hasmany-->Order.UserID and
-- Order.ID--hasmany-->OrderProduct.OrderID
BEGIN TRANSACTION;
DELETE FROM OrderProducts WHERE OrderID IN (SELECT o.id FROM orders o INNER JOIN users u on o.userid = u.userid WHERE u.lastname = 'smith');
DELETE FROM Orders WHERE UserID IN (SELECT userid from users WHERE u.lastname = 'smith');
DELETE FROM Users WHERE lastname = 'smith';
COMMIT TRANSACTION;

How do I insert data from one table to another when there is unequal number of rows in one another?

I have a table named People that has 19370 rows with playerID being the primary column. There is another table named Batting, which has playerID as a foreign key and has 104324 rows.
I was told to add a new column in the People table called Total_HR, which is included in the Batting table. So, I have to insert that column data from the Batting table into the People table.
However, I get the error:
Msg 515, Level 16, State 2, Line 183 Cannot insert the value NULL into
column 'playerID', table 'Spring_2019_BaseBall.dbo.People'; column
does not allow nulls. INSERT fails. The statement has been terminated.
I have tried UPDATE and INSERT INTO SELECT, however got the same error
insert into People (Total_HR)
select sum(HR) from Batting group by playerID
I expect the output to populate the column Total_HR in the People table using the HR column from the Batting table.
You could use a join
BEGIN TRAN
Update People
Set Total_HR = B.HR_SUM
from PEOPLE A
left outer join
(Select playerID, sum(HR) HR_SUM
from Batting
group by playerID) B on A.playerID = B.playerID
Select * from People
ROLLBACK
Notice that I've put this code in a transaction block so you can test the changes before you commit
From the error message, it seems that playerID is a required field in table People.
You need to specify all required fields of table People in the INSERT INTO clause and provide corresponding values in the SELECT clause.
I added field playerID below, but you might need to add additional required fields as well.
insert into People (playerID, Total_HR)
select playerID, sum(HR) from Batting group by playerID
It is strange, however, that you want to insert rows in a table that should already be there. Otherwise, you could not have a valid foreign key on field playerID in table Batting... If you try to insert such rows from table Batting into table People, you might get another error (violation of PRIMARY KEY constraint)... Unless... you are creating the database just now and you want to populate empty table People from filled/imported table Batting before adding the actual foreign key constraint to table People. Sorry, I will not question your intentions. I personally would consider to update the query somewhat so that it will not attempt to insert any rows that already exist in table People:
insert into People (playerID, Total_HR)
select Batting.playerID, sum(Batting.HR)
from Batting
left join People on People.playerID = Batting.playerID
where People.playerID is null and Batting.playerID is not null
group by playerID
You need to calculate the SUM and then join this result to the People table to bring into the same rows both Total_HR column from People and the corresponding SUM calculated from Batting.
Here is one way to write it. I used CTE to make is more readable.
WITH
CTE_Sum
AS
(
SELECT
Batting.playerID
,SUM(Batting.HR) AS TotalHR_Src
FROM
Batting
GROUP BY
Batting.playerID
)
,CTE_Update
AS
(
SELECT
People.playerID
,People.Total_HR
,CTE_Sum.TotalHR_Src
FROM
CTE_Sum
INNER JOIN People ON People.playerID = CTE_Sum.playerID
)
UPDATE CTE_Update
SET
Total_HR = TotalHR_Src
;

Database Schema for Claims Authentication

On a database I have the tables: USERS, USERS_PROFILES, USERS_CLAIMS.
create table dbo.USERS
(
Id int identity not null,
Username nvarchar (120) not null,
Email nvarchar (120) not null
);
create table dbo.USERS_PROFILES
(
Id int not null,
[Name] nvarchar (80) not null
);
create table dbo.USERS_CLAIMS
(
Id int not null,
[Type] nvarchar (200) not null,
Value nvarchar (200) not null,
);
I am using Claims authorization. When a user signs up and Identity is created.
The identity contains claims and each claim has a type and a value:
UsernameType > Username from USERS
EmailType > Email from USERS
NameType > Name from USERS_PROFILES
RoleType > Directly from USERS_CLAIMS
So I am creating the Identity from many columns in 3 tables.
I ended up with this because I migrated to Claims Authentication.
QUESTION
Should I move the Username, Email and Name to USERS_CLAIMS?
The USERS_PROFILES table would disappear ...
And USERS table would contain only info like "UserId, LastLoginDate, CreatedDate, ..."
If I want get a user by username I would just get the Claim of type username ...
If I want to sign in the user I just get all claims and create the identity.
So the Identity Model is much similar to the SQL tables.
Does this make sense? How would you design the tables?
Thank You,
Miguel
You are creating a key value store. They are a nightmare to query in SQL. Consider the difficulty of querying user attributes by a value on the USER_CLAIMS table. Example:
-- Users with name and email by username
SELECT p.ID, p.Username, p.Name, p.Email, u.LastLoggedIN
FROM USER_PROFILES p
INNER JOIN Users u on p.ID = u.ID
WHERE p.ID = #UserID
-- Users with name and email by username with a claims table
-- Does not specify whether there is only one email, so this could return multiple
-- rows for a single user.
SELECT p.ID, cUName.Value as Username, cName.Value as Name, cEMail.Value as Email, u.LastLoggedIN
FROM Users u
LEFT OUTER JOIN USER_CLAIMS cName ON u.ID = cName.ID and cName.[Type] = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
LEFT OUTER JOIN USER_CLAIMS cUName ON u.ID = cUName.ID and cUName.[Type] = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier'
LEFT OUTER JOIN USER_CLAIMS cEmail ON u.ID = cEmail.ID and cEmail.[Type] = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email'
WHERE p.ID = #UserID
Can a user have multiple profiles? If not, there is no need for the "USERS_PROFILES" table. Keep the "Username" and "Email" columns on the "USERS" table. If you put them on the "USERS_CLAIMS" table, you would be storing redundant information anytime a user files a claim.
I am not sure what kind of tracking you'd like to have for your users, but I would recommend having a separate table that tracks when a user signs in. Something like this:
CREATE TABLE USERS_LOG (user_id INT, log_in DATETIME);
You can then get rid of the "LastLoginDate" on your "USERS" table and do a join to get the last time the user signed in. It'll give you more ways to track your users and you won't be creating blocks on your "USERS" table by updating it constantly.

How do I process data in Multivalued Column in Oracle PLSQL?

I am working on creating a small database in Oracle for a project at work. One of the columns will need to have multiple values recorded in it. What's the query to create a multivalued column?
If you need a user to enter multiple email addresses, I would consider creating a USER_EMAIL table to store such records.
Create Table User_Email (User_Id int, Email varchar(100));
User_Id would be a foreign key that goes back to your USER table.
Then you can have a 1-n number of email address per user. This is generally the best practice for database normalization. If your emails have different types (ie work, personal, etc.), you could have another column in that table for the type.
If you need to return the rows in a single column, you could then look at using LISTAGG:
select u.id,
listagg(ue.email, ', ') within group (order by ue.email) email_addresses
from users u
left join user_email ue on u.id = ue.user_id
group by u.id
SQL Fiddle Demo
You can try to use VARRAY columns in a Oracle column.
Look at this page: https://www.orafaq.com/wiki/VARRAY
You can see there:
Declaration of a type:
CREATE OR REPLACE TYPE vcarray AS VARRAY(10) OF VARCHAR2(128);
Declaration of a table:
CREATE TABLE varray_table (id number, col1 vcarray);
Insertion:
INSERT INTO varray_table VALUES (3, vcarray('D', 'E', 'F'));
Selection:
SELECT t1.id, t2.column_value
FROM varray_table t1, TABLE(t1.col1) t2
WHERE t2.column_value = 'A' OR t2.column_value = 'D'

how to insert data in table using select statement in sql server

I have two tables Login table and userinfo table. UserInfo table is primary key(UserId) table and login table is foreign key(UserId) table. So my problem is when i insert data in login table value of userid column should come from Userinfo table and value of other columns of log in table like username and password should be inserted directly . Is it possible in single insert statement.
i did the following but it didnt work
insert into login(Userid,username,password)
values(select max(userid) from userinfo,sumit,sumit123)
insert into login(Userid,username,password)
values((select max(userid) from userinfo),'sumit','sumit123');
insert into login (Userid, username, password)
select max(userid), 'sumit', 'sumit123'
from userinfo
[Please note: while that is syntactically correct, I probably wouldn't do it that way.]
Have you tried using a inner JOIN?
INSERT INTO Insurance (Name)
SELECT Employee.Username FROM Employee
INNER JOIN Project ON Employee.EmployeeID = Project.EmployeeID
WHERE Project.ProjectName = 'Hardwork';