Conditional testing in Oracle SQL - sql

I am trying to write query on a tables which query record based on some condition plus I need one column as status which should checking matching record on another table and set value based on that test.
For example, we have these two tables:
| USER_ID | FIRST_NAME | LAST_NAME | CURR_STATUS |
--------------------------------------------------
| 1234567 | ABC | DEF | ACTIVE |
| 8910111 | GHI | JKL | INACTIVE |
| 2131415 | MNO | PQR | INACTIVE |
| 1617181 | STU | VWX | ACTIVE |
| 9202122 | YZA | BCD | ACTIVE |
--------------------------------------------------
Table: USER
| USER_ID | DOC_NAME |
---------------------
| 1234567 | EFG.TXT |
| 1617181 | HIJ.PDF |
----------------------
Table: USER_DOC
> select first_name, last_name, curr_status
//TODO some sql code for HAS_DOC logic
from USER
where CURR_STATUS = 'ACTIVE';
| FIRST_NAME | LAST_NAME | CURR_STATUS | HAS_DOC |
--------------------------------------------------
| ABC | DEF | ACTIVE | YES |
| STU | VWX | ACTIVE | YES |
| YZA | BCD | ACTIVE | NO |
--------------------------------------------------
Result
Right now I am doing this in Java code with two separate SQL calls:
First to get the all record matching record from USER table.
Then calling second query in the for loop
select user_id from user_doc from user_id = :userId;
I am trying to see if it is can be done with single SQL query and it is faster.
Any suggestion?

One possible solution is to use the EXISTS operator:
SELECT u.first_name, u.last_name, u.curr_status
FROM user u
WHERE u.curr_status = 'ACTIVE'
AND EXISTS (SELECT d.doc_name FROM user_doc d WHERE d.user_id = u.user_id);
If you need more information (say, a document count), you may also try
SELECT u.first_name, u.last_name, u.curr_status, COUNT(d.doc_name) AS num_documents
FROM user u LEFT JOIN user_doc d ON d.user_id = u.user_id
WHERE u.curr_status = 'ACTIVE'
GROUP BY u.first_name, u.last_name, u.curr_status
HAVING num_documents > 0;
One last alternative would be to use the IN operator
SELECT u.first_name, u.last_name, u.curr_status
FROM user u
WHERE u.curr_status = 'ACTIVE'
AND u.user_id IN (SELECT d.user_id FROM user_doc d);
EDIT The "HAS_DOC" attribute can be derived from the count, for example
-- Gets all users
SELECT u.first_name, u.last_name, u.curr_status, COUNT(d.doc_name) AS num_documents
FROM user u LEFT JOIN user_doc d ON d.user_id = u.user_id
WHERE u.curr_status = 'ACTIVE'
GROUP BY u.first_name, u.last_name, u.curr_status;
and in your Java code:
boolean hasDoc = resultSet.getInt("num_documents") > 0;

I recommend something like this. (EDIT: tested, and it works)
select u1.first_name, u1.last_name, u1.curr_status,
nvl2(u2.user_id, 'YES', 'NO') as has_doc
from USER u1
left join (select u.user_id
from USER u
where u.CURR_STATUS = 'ACTIVE'
and exists (
select null
from user_doc ud
where ud.user_id = u.user_id)
) u2 ON u1.user_id = u2.user_id
where u1.CURR_STATUS = 'ACTIVE';

Related

SQL Query JOIN and Aggregate

New to SQL and have been googling to no avail. Here is the schema:
"users"
Column | Type |
---------------------+--------------------------+
id | text |
name | text |
title | text |
org_id | text |
type. | text |
"organizations"
Column | Type |
--------------------------------+--------------------------+
id | text |
name | text |
"posts"
Column | Type |
-------------------+--------------------------+
id | text |
title | text |
content | jsonb[] |
owner_id | text |
org_id | text |
is_public | boolean |
My goal is to, in one table, show how many private topics, admins, and standard users each organization has, like this:
Org | Users | Admins | Private Posts
----------+-------+-------+---------------
Org1 | 56 | 10 | 22
Org2 | 111 | 10 | 34
Right now, I only get this:
Org | Count | Type | Private Posts
----------+-------+-------+---------------
Org1 | 10 | admin | 22
Org2 | 111 | user | 34
Org1 | 56 | user | 22
Org2 | 10 | admin | 34
Using:
SELECT t1.id as "Org", t1.cnt as "Count", t1.type as "Type", t2.cnt as "Private Posts" from
(SELECT COUNT(u.type) as "cnt", u.type as "type", o.id FROM "users" AS u JOIN
"organizations" AS o ON o.id=u.org_id GROUP BY u.type, o.id) as t1 join
(SELECT COUNT(org_id) as "cnt", org_id from posts WHERE is_public = False group
by org_id) as t2 on t2.org_id = t1.id;
I basically tried to join the users and organizations and count based on organization and user type (t1), then counted the public posts in posts (t2), and tried to join t1 and t2 based on the organization id. Any help is appreciated.
Consider a concept called conditional aggregation that pivots results to needed wide formatted columns based on conditional logic. Postgres maintains the useful FILTER for this type of query but can also use CASE statements as shared with other RDBMS's:
SELECT o.id AS "Org",
COUNT(*) FILTER(WHERE u.type = 'user') AS "Users",
COUNT(*) FILTER(WHERE u.type = 'admin') AS "Admins",
COUNT(*) FILTER(WHERE is_public = False) AS "Private Posts"
FROM users u
JOIN organizations o
ON o.id = u.org_id
JOIN posts p
ON o.id = p.org_id
GROUP BY o.id;

How to generate multiple rows based on a single record when join a table?

I have two tables User and Post. User has many posts, so user_id is the foreign key.
User:
id | name | base
---+------+-------
1 | User1| 5
Post:
id | content | min | max | user_id
---+---------+------+-----+--------
1 | Test | 8 | 10 | 1
2 | Test2 | 10 | 15 | 1
There could also have other table to join(Which is not show in the example). In general, I can joins the posts and other tables:
SELECT users.id, users.content, posts.min, posts.max, posts.content, ...
FROM users
JOIN table_1
ON condition_1
JOIN table_2
ON condition_2
JOIN posts
ON users.id = posts.user_id
Now I want to get users, but join posts and extend the posts based on user.base, which will seperate id=1 post into two records. So finally I want to get 3 records:
user.id | user.name | user.base | post.id | post.content | post.min | post.max | post.user_id
--------+-----------+-----------+---------+--------------+----------+----------+----------
1 | User1 | 5 | 1 | Test | 5 | 8 | 1
1 | User1 | 5 | 1 | Test | 8 | 10 | 1
1 | User1 | 5 | 2 | Test2 | 10 | 15 | 1
Is it possible to get the result via SQL?
I guess below query would do the job if i understood it correctly ,
(select u.id as uid,u.name,u.base,p.id,p.content, base as min,min as max,user_id
from users u,posts p where u.id = p.user_id
and min = (select min(min) from posts p1 where p1.user_id = u.id))
union
select u.id as uid,u.name,u.base,p.id,p.content,min, max,user_id from users
u,posts p where u.id = p.user_id
order by uid,min
dbfiddle example with whole solution : https://dbfiddle.uk/?rdbms=postgres_13&fiddle=592d8eb5803c11b2a30cac388344a402
UPDATE : To join the results with other tables,use the above query as inline view and join with tables with conditions as desired.
Lets assume another table user_addr that has address details for all users and user_id is the foreign key that references user.id
select uid,name,base,pid,content,min,max,magic_table.user_id,addr_typ,addrline1,addrline2,city,postcode,prov,country
from
((select u.id as uid,u.name,u.base,p.id as pid,p.content, base as min,min as max,user_id
from users u,posts p where u.id = p.user_id
order by min asc
fetch first 1 rows only)
union
select u.id as uid,u.name,u.base,p.id as pid,p.content,min, max,user_id from users
u,posts p where u.id = p.user_id) magic_table, user_addr
where magic_table.user_id = user_addr.user_id
Output :
dbfiddle link with solution : https://dbfiddle.uk/?rdbms=postgres_13&fiddle=ea697bbf1502857834b461edd8ccf447

Returning one result per id in SQL in a joined table

I have one table of users.
users
| user_id | name |
| ------- | ------ |
| 1 | Jerry |
| 2 | George |
| 3 | Elaine |
| 4 | Kramer |
I have one table that links roles to users, and roles are assigned at a tree.
user_roles
| user_id | role_id | tree_id |
| ------- | ------- | ------- |
| 1 | 5 | 1 |
| 1 | 5 | 2 |
| 2 | 6 | 1 |
| 3 | 7 | 1 |
| 4 | 8 | 1 |
I need to only return results where a user's role is assigned at a certain tree_id, so I'm checking all the roles and trees. At the end I want it to return one row per user.
I'm using Knex and doing a query that looks like:
knex('users')
.leftJoin('user_roles', {'user.user_id': 'user_roles.user_id'})
.whereIn('user_roles.tree_id', arrayOfTreeIds)
.andWhere(moreFilters)
SELECT *
FROM users
LEFT JOIN user_roles on users.user_id = user_roles.user_id
WHERE user_roles.tree_id in (1, 2, 3)
I'm getting five results back instead of four, though. If I try to SELECT DISTINCT it tells me I need to GROUP BY, but I can't get that to work. What do I need to do to get only one result per user id?
You have a user that matches on two different tree_ids, so this multiplies the rows.
In pure SQL, you could use exists instead of a join:
SELECT *
FROM users u
WHERE EXISTS (
SELECT 1
FROM user_roles ur
WHERE ur.user_id = u.user_id AND ur.tree_id in (1, 2, 3)
)
Another option is aggregation:
SELECT u.*
FROM users u
INNER JOIN user_roles ur on u.user_id = ur.user_id
WHERE ur.tree_id in (1, 2, 3)
GROUP BY u.user_id
I changed the LEFT JOIN to an INNER JOIN, because that's, in essence, what you want (and what your original query does).
You can even list the matched roles with string aggregation:
SELECT u.*, STRING_AGG(ur.tree_id::text, ',' ORDER BY ur.tree_id) tree_ids
FROM users u
INNER JOIN user_roles ur on u.user_id = ur.user_id
WHERE ur.tree_id in (1, 2, 3)
GROUP BY u.user_id
Disclaimer: I don't know how to write this in knex!
Demo on DB Fiddle

Different User, friendships and selection

I have two tables:
User:
+----+----------+--+
| | User | |
+----+----------+--+
| pk | email | |
| | password | |
| | ... | |
+----+----------+--+
and Friendship:
+----+-------------+--+
| | FriendShip | |
+----+-------------+--+
| pk | user1_email | |
| pk | user2_email | |
| | date | |
| | accepted | |
+----+-------------+--+
So basically, when someone wants to befriend someone else, we record it in the friendship table.
Let's say now that we want to select all the user with less than 3 friends, it is easy to that except for people who does not have friends because there is no record of that in the friendship table.
My query looks like this for the moment:
SELECT u.email, COUNT(u.email)
FROM user u
INNER JOIN friendship f ON f.user1_email = u.email OR f.user2_email = u.email
GROUP BY u.email
HAVING COUNT(u.email) < 3;
How can I add the result of the asocial people :p?
You could use a UNION to quickly get what you want.
SELECT u.email, COUNT(u.email)
FROM user u
INNER JOIN friendship f ON f.user1_email = u.email OR f.user2_email = u.email
GROUP BY u.email
HAVING COUNT(u.email) < 3
UNION
SELECT u.email, 0
FROM user u
WHERE u.email not in (SELECT user1_email from friendship)
AND u.email not in (SELECT user2_email from friendship);

Include admin role in users table from roles table

Is there a way to query users table like this:
| id | username |
-----------------
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
and user_roles table:
| id_user | id_role |
---------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 3 | 1 |
assuming that role with id = 1 is an admin role, to produce result that looks like this:
| id | username | admin |
-------------------------
| 1 | user1 | Y |
| 2 | user2 | N |
| 3 | user3 | Y |
I think it can be done using nested SELECT statements, but I was wondering if it's doable using JOIN.
Edit:
The admin column value doesn't have to be Y or N, it can be admin role id (1) or NULL or whatever that will let me know if user is an admin
I would try this:
select u.id, u.username, if (id_role is null, 'N', 'Y') as is_admin
from users u
left outer join user_roles r
on u.id = r.id_user and r.id_role = 1
But I'm not 100% sure.
Well, you can join like this, which will give you the id_role in the result.
SELECT u.*, r.id_role
FROM users u
LEFT JOIN user_roles r
ON u.id=r.id_user
You can add WHERE r.id_role=1 to get just the admins, etc.
But to get the admin as "Y" or "N" as you wanted, you can use an IF on whether the id_role is 1 or not.
SELECT u.*, IF(r.id_role = 1, "Y", "N") as admin
FROM users u
LEFT JOIN user_roles r
ON u.id=r.id_user
select u.id_user, if(count(1) > 0, 'Y', 'N')
from user u left outer join user_roles ur on u.user_id = ur.user_roles
where ur.id_role=1
group by u.id_user
I'm not using mysql, so just googled it has the 'if' function, if that's wrong, replace with DECODE, COALESCE or alike.