Oracle SQl Dev, display id, firstname, surname and users number of files - sql

I am trying to write a query in oracle sql developer that will list
UNum, FirstName, Surname, and the number of files the users has, if the user has 0 it should be displayed as 0 next to their name.
(PK) = Primary Key
(FK) = Foreign Key
The database schema is as follows:
Building(buildingNum(PK), Description, instname, buildName, state, postcode)
User(UNum(PK), buildingNum(FK), Surname, FirstName, initials, title)
File(FileNum(PK), title)
UserAccount(FileNum(PK)(FK), UNum(PK)(FK))
Job(JobNum(PK), id, title)
Interest(JobNum(PK)(FK), UNum(PK)(FK), Description)
So far i have tried the following block of code:
Select User.UNum, User.FirstName, User.Surname, Count(UserAccount.FileNum)
from User, UserAccount
where User.UNum = UserAccount.UNum
group by User.UNum, User.FirstName, User.Surname;
I end up with the result being a long list of Users made up of UNum, FirstName and Surname followed by the number of files they have, however none of the results return with a 0. the list also seems to be extremely long for the size of the database. How do i find those who also have 0 files and is there anything else im doing wrong? expected result should be a much shorter list of users including those who don't have any files (There are 7 people in the database with 0 files) Thanks.

You want a left join:
Select u.UNum, u.FirstName, u.Surname,
Count(ua.FileNum)
from User u left join
UserAccount ua
on u.UNum = ua.UNum
group by u.UNum, u.FirstName, u.Surname;
This also introduces table aliases, which makes it simpler to write and read the queries.
You can also write this using a correlated subquery:
select u.UNum, u.FirstName, u.Surname,
(select count(ua.FileNum)
from UserAccount ua
where u.UNum = ua.UNum
)
from User u;
This version might have somewhat better performance.

You want a left join. That keeps rows from the table left of the join operator even if no matching row is found in the table right to it.
SELECT user.unum,
user.firstname,
user.surname,
count(useraccount.filenum)
FROM user
LEFT JOIN useraccount
ON useraccount.unum = user.unum
GROUP BY user.unum,
user.firstname,
user.surname;

Related

Oracle SQL dev, count under heading

I am currently trying to get the database to list a interest job number, and users title and count the users interested in the interest description. The query is meant to count under the heading "No. Users Interested. Interests with no interested users should be excluded.
(PK) = Primary Key
(FK) = Foreign Key
The database schema is as follows:
Building(buildingNum(PK), Description, instname, buildName, state, postcode)
User(UNum(PK), buildingNum(FK), Surname, FirstName, initials, title)
File(FileNum(PK), title)
UserAccount(FileNum(PK)(FK), UNum(PK)(FK))
Job(JobNum(PK), id, title)
Interest(JobNum(PK)(FK), UNum(PK)(FK), Description)
So far i have tried the following block of code:
select I.JobNum, U.title, count(I.description) AS 'No. Academics Interested'
from Interest I join Users U
where U.UNum = I.UNum AND I.description != null;
I'm struggling with how to do this using a sub query, all i receive is an error as this doesn't work. I'm not sure of how I do the Count(I.description) under a heading and how i should be doing it. Thanks to anyone who can help.
You presumably would need to aggregation using GROUP BY here:
SELECT
i.JobNum,
u.title,
COUNT(i.description) AS "No. Academics Interested"
FROM Interest i
LEFT JOIN Users u
ON u.UNum = i.UNum
GROUP BY
i.JobNum,
u.title;
Note that the check I.description != null (which should actually be i.description IS NOT NULL) is not necessary here, because the COUNT function by default does not count NULL values.

oracle sql dev, Using a subquery, List buildings where user has no interest

I am currently trying to get the database to list a buildings, BuildingNum, BuildingName and instname that have a user who has not Interest(interest.description = null).
(PK) = Primary Key
(FK) = Foreign Key
The database schema is as follows:
Building(buildingNum(PK), Description, instname, buildName, state, postcode)
User(UNum(PK), buildingNum(FK), Surname, FirstName, initials, title)
File(FileNum(PK), title)
UserAccount(FileNum(PK)(FK), UNum(PK)(FK))
Job(JobNum(PK), id, title)
Interest(JobNum(PK)(FK), UNum(PK)(FK), Description)
So far i have tried the following block of code:
select B.buildingNum, B.BuildName, B.instname
from Building B join User U
where B.deptNum = U.deptNum in (select I.Description
from interest I
where description = null);
I'm struggling with how to do this using a sub query, all i receive is an error as this doesn't work. Im not sure if i should be using the join like that or if i have added the subquery correctly. Thanks to anyone who can help.
If you want no interests, use exists:
select b.buildingNum, b.BuildName, b.instname
from Building b
where exists (select 1
from users u left join
interest i
on i.unum = u.unum
where b.deptNum = u.deptNum and
i.unum is null -- no interests
);
The subquery returns users (in a given building) that have no interests. The exists is simply saying that at least one exists.
As a note: = null is never used for comparisons. It never returns a true value. The correct syntax is is null.

Find potential duplicate names in database

I have two tables in a SQL Server Database:
Table: People
Columns: ID, FirstName, LastName
Table: StandardNames
Columns: Nickname, StandardName
Sample Nicknames would be Rick, Rich, Richie when StandardName is Richard.
I would like to find duplicate contacts in my People table but replace any of the nicknames with the standard name. IE: sometimes I have Rich Smith other times it is Richard Smith in the People table. Is this possible? I realize it might be multiple joins to the same table but can't figure out how to start.
Firstly, you need to determine how many duplicates you have in your People table...
SELECT p.FirstName, COUNT(*)
FROM People AS p
INNER JOIN StandardNames AS sn
ON CHARINDEX(sn.Nickname, p.FirstName) > 0 OR
CHARINDEX(sn.Nickname, p.LastName) > 0
GROUP BY p.FirstName
HAVING COUNT(*) > 1
That's just to get an idea of what data you're trying to find in relation to the Nicknames that may possibly exist inside (as a wildcard word search) the Firstname and Lastname columns.
If you are happy with the items found then expand on the query to update the values.
Let's say you wanted to change the Firstname to be the Standardname...
UPDATE p2
SET p2.FirstName = p2.Standardname
FROM
(SELECT p.ID, sn.StandardName
FROM People AS p
INNER JOIN StandardNames AS sn
ON CHARINDEX(sn.Nickname, p.FirstName) > 0 OR
CHARINDEX(sn.Nickname, p.LastName) > 0) AS a
INNER JOIN People AS p2 ON p2.ID = a.ID
So this will obviously find all the People IDs that have a match based on the query above, and it will update the People table by replacing the FirstName with the StandardName.
However, there are issues with this due to the limitation of your question.
the StandardNames table should have its own ID field. All tables should have an ID column as its primary table. That's just my view.
this is only going to work for data it matches using the CHARINDEX() function. What you really need is something to find based on a "sound" or similarity to the nicknames. Check out the SOUNDEX() function and apply your logic from there.
And this is assuming your IDs above are unique!
Good luck
You could standardize the names by joining, and count the number of occurrences. Extracting the ID is a bit fiddly, but also quite possible. I'd suggest the following - use a case expression to find the contact with the standard name, and if you don't have one, just take the id of the first duplicate:
SELECT COALESCE(MIN(CASE FirstName WHEN StandardName THEN id END), MIN(id)),
StandardName,
LastName,
COUNT(*)
FROM People p
LEFT JOIN StandardNames s ON FirstName = Nickname AND
GROUP BY StandardName, LastName

FIRST ORDER BY ... THEN GROUP BY

I have two tables, one stores the users, the other stores the users' email addresses.
table users: (userId, username, etc)
table userEmail: (emailId, userId, email)
I would like to do a query that allows me to fetch the latest email address along with the user record.
I'm basically looking for a query that says
FIRST ORDER BY userEmail.emailId DESC
THEN GROUP BY userEmail.userId
This can be done with:
SELECT
users.userId
, users.username
, (
SELECT
userEmail.email
FROM userEmail
WHERE userEmail.userId = users.userId
ORDER BY userEmail.emailId DESC
LIMIT 1
) AS email
FROM users
ORDER BY users.username;
But this does a subquery for every row and is very inefficient. (It is faster to do 2 separate queries and 'join' them together in my program logic).
The intuitive query to write for what I want would be:
SELECT
users.userId
, users.username
, userEmail.email
FROM users
LEFT JOIN userEmail USING(userId)
GROUP BY users.userId
ORDER BY
userEmail.emailId
, users.username;
But, this does not function as I would like. (The GROUP BY is performed before the sorting, so the ORDER BY userEmail.emailId has nothing to do).
So my question is:
Is it possible to write the first query without making use of the subqueries?
I've searched and read the other questions on stackoverflow, but none seems to answer the question about this query pattern.
But this does a subquery for every row and is very inefficient
Firstly, do you have a query plan / timings that demonstrate this? The way you've done it (with the subselect) is pretty much the 'intuitive' way to do it. Many DBMS (though I'm not sure about MySQL) have optimisations for this case, and will have a way to execute the query only once.
Alternatively, you should be able to create a subtable with ONLY (user id, latest email id) tuples and JOIN onto that:
SELECT
users.userId
, users.username
, userEmail.email
FROM users
INNER JOIN
(SELECT userId, MAX(emailId) AS latestEmailId
FROM userEmail GROUP BY userId)
AS latestEmails
ON (users.userId = latestEmails.userId)
INNER JOIN userEmail ON
(latestEmails.latestEmailId = userEmail.emailId)
ORDER BY users.username;
If this is a query you do often, I recommend optimizing your tables to handle this.
I suggest adding an emailId column to the users table. When a user changes their email address, or sets an older email address as the primary email address, update the user's row in the users table to indicate the current emailId
Once you modify your code to do this update, you can go back and update your older data to set emailId for all users.
Alternatively, you can add an email column to the users table, so you don't have to do a join to get a user's current email address.

SQL - re-arrange a table via query

I have a poorly designed table that I inherited.
It looks like:
User Field Value
-------------------
1 name Aaron
1 email aaron#company.com
1 phone 800-555-4545
2 name Mike
2 email mike#group.org
2 phone 777-123-4567
(etc, etc)
I would love to extract this data via a query in the more sensible format:
User Name Email Phone
-------------------------------------------
1 Aaron aaron#company.com 800-555-4545
2 Mike mike#group.org 777-123-4567
I'm a SQL novice, but have tried several queries with variations of Group By,
all without anything even close to success.
Is there a SQL technique to make this easy?
this not a 'badly designed table'; but in fact an Entity Attribute Value (EAV) table. unfortunately, relational databases are poor platforms to implement such tables, and negate most of the nice things of RDBMS. A common case of using the wrong shovel to nail in a screw.
but i think this would work (based on Marcus Adams' answer, which i don't think would work (edit: now it does))
SELECT User1.Value AS name, User2.Value AS email, User3.Value AS phone
FROM Users User1
LEFT JOIN Users User2
ON User2.User = User1.User AND User2.Field='email'
LEFT JOIN Users User3
ON User3.User = User1.User AND User3.Field='phone'
WHERE User1.Field = 'name'
ORDER BY User1.User
Edit: got some niceties from other answers (LEFT Joins, and the field names on the ON clauses), now does anybody know how to put the remaining WHERE a little higher? (but not on the first JOIN's ON, that's too ugly), of course it doesn't matter since the query optimizer uglyfies it back anyway.
At my work we are unfortunate to have a database design like this. But this kind of design works better for us then a traditional database design because of the different records we have to store and gives us the flexibility that we need. The database that we are using stores millions of records.
This would be the fastest way to run the query on a large database using MSSQL. It saves from having to do as many joins which could be very costly.
DECLARE #Results TABLE
(
UserID INT
, Name VARCHAR(50)
, Email VARCHAR(50)
, Phone VARCHAR(50)
)
INSERT INTO #Results
SELECT DISTINCT User FROM UserValues
UPDATE
R
SET
R.Name = UV.Value
FROM
#Results R
INNER JOIN
UserValues UV
ON UV.User = R.UserID
WHERE
UV.Field = 'name'
UPDATE
R
SET
R.Email = UV.Value
FROM
#Results R
INNER JOIN
UserValues UV
ON UV.User = R.UserID
WHERE
UV.Field = 'Email'
UPDATE
R
SET
R.Phone = UV.Value
FROM
#Results R
INNER JOIN
UserValues UV
ON UV.User = R.UserID
WHERE
UV.Field = 'Phone'
SELECT * FROM #Results
You can use a self join:
SELECT User1.User, User1.Value as Name, User2.Value as Email,
User3.Value as Phone
FROM Users User1
JOIN Users User2
ON User2.User = User1.User
JOIN Users User3
ON User3.User = User1.User
WHERE User1.Field = 'name' AND User2.Field = 'email' AND User3.Field = 'phone'
ORDER BY User1.User
I tested this query, and it works.
I believe this will build the result set you're looking for. From there, you can create a view or use the data to populate a new table.
select user, name, email, phone from
(select user, value as name from table where field='name')
natural join
(select user, value as email from table where field='email')
natural join
(select user, value as phone from table where field='phone')
In MySQL you can do something like this:
SELECT
id,
group_concat(CASE WHEN field='name' THEN value ELSE NULL END) AS name,
group_concat(CASE WHEN field='phone' THEN value ELSE NULL END) AS phone,
...
FROM test
GROUP BY id
The aggregate function actually doesn't matter, as long as you have only one field of each type. You could also use min() or max() instead with the same effect.
A variant of Javier's answer, which has my vote.
SELECT
UserName.name, UserEmail.email, UserPhone.phone
FROM
Users AS UserName
INNER JOIN Users AS UserEmail ON UserName.User = UserEmail.User
AND UserName.field = 'name' AND UserEmail.field = 'email'
INNER JOIN Users AS UserPhone ON UserName.User = UserPhone.User
AND UserPhone.field = 'phone'
Use LEFT JOINs if not all attributes are guaranteed to exist. A composite index over (User,Field) would probably be beneficial for this.