How to fetch in subquerys with the ID of the current row? - sql

Hi StackOverflow Community,
I'm very new at SQL and I have a question with a problem that I'm having that I don't know how to fix (Btw I'm new at SQL).
So I have these tables:
TABLE 1 - Users
-ID -NAME
1 User1
2 User2
3 User3
TABLE 2 - JOBS
-ID -JOB -USER -ID_DIRECCTION
1 Football Match User1 1
2 Bascketball Match User3 1
3 Swimming Race User2 2
4 Handball Game User3 2
5 Tennis Match User2 3
TABLE 3 - DIRECCTIONS
-ID -DIRECCTION -NUMBER
1 Fail Street 34
2 Swimming Street 45
3 Fake Street 01
What I want to do is to show the Table of Jobs but adding a new column with a SELECT that shows the last user that have been on the direcction (If no one has been in the direcction the result will be null) and if are multiple users the most recent one.
I'm close to get it with my current query (i think), but the main problem is that when I do the subquery there's a way to fetch the inner query with the ID of the current row where's executing the main query??
SELECT j.ID, j.JOB, j.USER, j.ID_DIRECCTION,
(SELECT j.USER
FROM USERS u INNER JOIN
JOBS j
ON j.USER = u.ID INNER JOIN
DIRECCTIONS d
ON d.ID = j.ID_DIRECCTION
WHERE d.ID = (SELECT d.ID FROM DIRECCTIONS WHERE d.ID = j.ID_DIRECCTION --I want this to be current ID_DIRECCTION row)
ORDER BY j.ID DESC FETCH FIRST 1 ROWS ONLY
) AS LAST_USER
FROM JOBS j;
How is the way to select the ID of the current row to fetch other things in subquerys????
The result should be like:
-ID -JOB -USER -ID_DIRECCTION -LAS_TUSER
1 Football Match User1 1 null*
2 Bascketball Match User3 1 User1*
3 Swimming Race User2 2 null*
4 Handball Game User3 2 User2*
5 Tennis Match User2 3 User3*
But I'm always getting the same value in LAST_USER

It seems you want LAG, but it isn't fully clear why LAS_TUSER = User3 for Tennis Match (job id 5)
SELECT j.*,
LAG(u.name)
over (
PARTITION BY j.id_direcction
ORDER BY j.id )
FROM users u
inner join jobs j
ON j.username = u.name
ORDER BY j.id
Demo

It's unclear from your question whether you really have the user name in the jobs table, or just the ID.
If you do have the name then you have all th einformation you need in the jobs table and you can just use lag().
But if you have the ID then you need to join to the users table, for both the current and lag IDs. You can still get the previous ID the same way:
select id, job, id_user, id_direcction,
lag(id_user) over (partition by id_direcction order by id) as last_id_user
from jobs
but then use that as a CTE or inline view, and join from that:
select j.id, j.job, u.name, j.id_direcction, lu.name as last_name
from (
select id, job, id_user, id_direcction,
lag(id_user) over (partition by id_direcction order by id) as last_id_user
from jobs
) j
join users u on u.id = j.id_user
left join users lu on lu.id = j.last_id_user
With CTEs to provide the sample data, and making assumptions about what those really hold:
-- sample data
with users (id, name) as (
select 1, 'User1' from dual
union all select 2, 'User2' from dual
union all select 3, 'User3' from dual
),
jobs (id, job, id_user, id_direcction) as (
select 1, 'Football Match', 1, 1 from dual
union all select 2, 'Bascketball Match', 3, 1 from dual
union all select 3, 'Swimming Race', 2, 2 from dual
union all select 4, 'Handball Game', 3, 2 from dual
union all select 5, 'Tennis Match', 2, 3 from dual
)
-- actual query
select j.id, j.job, u.name, j.id_direcction, lu.name as last_name
from (
select id, job, id_user, id_direcction,
lag(id_user) over (partition by id_direcction order by id) as last_id_user
from jobs
) j
join users u on u.id = j.id_user
left join users lu on lu.id = j.last_id_user
order by j.id;
ID JOB NAME ID_DIRECCTION LAST_
---------- ----------------- ----- ------------- -----
1 Football Match User1 1
2 Bascketball Match User3 1 User1
3 Swimming Race User2 2
4 Handball Game User3 2 User2
5 Tennis Match User2 3
You don't need to refer to the direcctions table unless you want to get its description. I've included a version that does that in this db<>fiddle.
Like #KaushikNayak, I don't see how or why you would expect to see User3 against the last row, based on the data you provided.

Related

SQL apply MAX to left join table having non null rows

table name: country
id country_name
1 USA
2 GERMANY
3 RUSSIA
table name: user
id user_name points country_id
1 user1 20 1
2 user2 10 2
3 user3 11 2
Result should be country-user with maximum points and only country if no user available(3rd record),like below
country_name user_name points
USA user1 20
GERMANY user3 11
RUSSIA (null) (null)
Currently I am using below query but it is sometime taking too much time, like when i have 100000 records.
SELECT c.country_name,u.user_name,u.points FROM country c
LEFT JOIN user u on u.country_id = c.id
WHERE (u.points = (SELECT MAX(points) AS points FROM user WHERE user.id = u.id) OR u.points IS NULL)
So, is there any other way to do it more efficiently, time-wise.
Thanks already!
You can use ROW_NUMBER():
SELECT c.country_name, u.user_name, u.points
FROM country c LEFT JOIN
(SELECT u.*,
ROW_NUMBER() OVER (PARTITION BY u.country_id ORDER BY u.points DESC) as seqnum
FROM user u
WHERE u.points IS NOT NULL
) u
ON u.country_id = c.id AND u.seqnum = 1;
Note: This returns one user per country, even if there are ties for the top one. If you want all of them, use RANK() instead of ROW_NUMBER().

Get root elements for list of children in hierarchical table

I have the following 2 tables, with hierarchical data in the first one:
COMPANIES:
COMPANY_ID PARENT_COMPANY_ID
---------- -----------------
1 NULL
2 1
3 2
4 3
5 NULL
6 5
USERS:
USER_ID COMPANY_ID
------- ----------
1 1
2 4
3 5
4 6
I would like to make a query which outputs user ids next to their root company ids. I have tried with the following Common Table Expression (CTE) query, but it fails to output users that are directly under a root company:
Query:
WITH ROOT (COMPANY_ID, ROOT_ID) AS (
SELECT
CHILD.COMPANY_ID,
PARENT.COMPANY_ID
FROM COMPANIES CHILD
INNER JOIN COMPANIES PARENT
ON CHILD.PARENT_COMPANY_ID = PARENT.COMPANY_ID
WHERE
PARENT.PARENT_COMPANY_ID IS NULL
UNION ALL
SELECT
C.COMPANY_ID,
ROOT.ROOT_ID
FROM ROOT
INNER JOIN COMPANIES C
ON ROOT.COMPANY_ID = C.PARENT_COMPANY_ID
)
SELECT
U.USER_ID,
R.ROOT_ID
FROM USERS U
INNER JOIN ROOT R
ON U.COMPANY_ID = R.COMPANY_ID;
Actual output:
USER_ID ROOT_COMPANY_ID
------- ---------------
4 5
2 1
Expected output:
USER_ID ROOT_COMPANY_ID
------- ---------------
1 1
2 1
3 5
4 5
So my query is missing the users with user id 1 and 2 with their respective root companies 1 and 5.
I have created this sqlfiddle with my example:
http://sqlfiddle.com/#!4/36d33a/1
What am I missing here?
I am using Oracle 11 but using H2 for unit tests. So my query needs to be a CTE query and not an Oracle connect by query since H2 only understands the former.
You need to select COMPANY_ID as both COMPANY_ID and ROOT_ID for root companies:
WITH ROOT(COMPANY_ID, ROOT_ID) AS (
SELECT COMPANY_ID, COMPANY_ID FROM COMPANIES WHERE PARENT_COMPANY_ID IS NULL
UNION ALL
SELECT C.COMPANY_ID, ROOT.ROOT_ID FROM COMPANIES C JOIN ROOT
ON C.PARENT_COMPANY_ID = ROOT.COMPANY_ID
) SELECT USER_ID, ROOT_ID FROM USERS JOIN ROOT
ON USERS.COMPANY_ID = ROOT.COMPANY_ID;
You can use Oracle's classical Hierarchical Query including CONNECT_BY_ROOT together with ROW_NUMBER() Analytic Function to filter out the root company in the first query(subquery) for companies table, and then join with the users table :
WITH company AS
(
SELECT c.company_id,
CONNECT_BY_ROOT NVL(c.parent_company_id,c.company_id) AS root_company_id,
ROW_NUMBER() OVER (PARTITION BY c.company_id ORDER BY level DESC) AS rn
FROM companies c
CONNECT BY PRIOR c.company_id = c.parent_company_id
)
SELECT u.user_id, c.root_company_id
FROM company c
JOIN users u
ON u.company_id = c.company_id
WHERE rn = 1
Demo

oracle 11g pivot query optimization - multiple rows to single row

I have below tables
user table
USER_ID USER_NAME
1 smith
2 clark
3 scott
4 chris
5 john
property table
P_ID PROPERTY
1 first_name
2 last_name
3 age
4 skill
user_property table
PV_ID USER_ID P_ID VALUE
1 1 1 Smith
2 1 2 A
3 1 3 34
4 1 4 Java
5 1 4 DB
6 2 1 Clark
7 2 2 B
8 2 3 39
9 2 4 Java
10 2 4 net
11 2 4 linux
12 3 1 Scott
13 3 2 C
14 3 3 31
I want to write a query which will fetch data from all above tables as below:(Skill will be first skill for that user if available otherwise null)
USER_ID USER_NAME FIRST_NAME LAST_NAME SKILL
1 smith Smith A Java
2 clark Clark B Java
3 scott Scott C null
I have tried like below but getting performance issue:
SELECT
u.user_id,
u.user_name,
MAX(DECODE(p.property, 'first_name', text_value)) firstName,
MAX(DECODE(p.property, 'last_name', text_value)) lastName,
MAX(DECODE(p.property, 'age', text_value)) age,
MAX(DECODE(p.property, 'skill', text_value)) skill
FROM user u,
property p,
user_property up,
WHERE u.user_id = up.user_id
AND p.p_id = up.p_id
GROUP BY u.user_id,
u.user_name;
How could i write this as optimized query for oracle 11g.
The performance of your query depends on the size of the table and on the indexes you have on these tables. It most cases, it is best practice to have an index on each primary and foreign key. The index on the primary key is a must anyway. The index on the foreign key speeds up joins and prevents table locks when you delete rows.
An alternative to your query would be to use more joins instead of subselects and to use the WITH clause to simplify it:
with t as (
select u.user_id, u.user_name, up.p_id, up.value
from user_property up
join user u on u.user_id = up.user_id
)
select u.user_id, u.user_name,
t_first_name.value first_name,
t_last_name.value last_name,
(select min(value) from t where t.user_id = u.user_id and t.p_id = 4) skill
from user u
left join t t_first_name on t_first_name.user_id = u.user_id and t_first_name.p_id = 1
left join t t_last_name on t_last_name.user_id = u.user_id and t_last_name.p_id = 2;
BTW: It's a data model which isn't well suited for SQL. I hope these user properties are the exception and the rest of the database has a cleaner design.
I have tried below query however getting Cartesian product.
with t as (
select u.user_id, u.user_name, up.p_id, up.value
from user_property up
join user u on u.user_id = up.user_id
where u.user_name = 'smith'
)
select u.user_id, u.user_name,
t_first_name.value first_name,
t_last_name.value last_name,
(select min(value) from t where t.user_id = u.user_id and t.p_id = 4) skill
from user u
left join t t_first_name on t_first_name.user_id = u.user_id and t_first_name.p_id = 1
left join t t_last_name on t_last_name.user_id = u.user_id and t_last_name.p_id = 2;
if i execute below query i get 5 as mentioned in my above example (As there are 5 rows in my user_property table for user_id 1 in example)
select count(u.user_id)
from user_property up
join user u on u.user_id = up.user_id
where u.user_name = 'smith'
Hence if i execute below query i get count as 3 as there are 3 rows in my use table example
with t as (
select u.user_id, u.user_name, up.p_id, up.value
from user_property up
join user u on u.user_id = up.user_id
where u.user_name = 'smith'
)
select count(u.user_id)
from user u
left join t t_first_name on t_first_name.user_id = u.user_id and t_first_name.p_id = 1
left join t t_last_name on t_last_name.user_id = u.user_id and t_last_name.p_id = 2;

max count with joins

I have 3 tables:
users:
Id Login
1 John
2 Bill
3 Jim
computers:
Id Name
1 Computer1
2 Computer2
3 Computer3
4 Computer4
5 Computer5
sessions:
UserId ComputerId Minutes
1 2 47
2 1 32
1 4 15
2 5 5
1 2 7
1 1 40
2 5 31
I would like to display this resulting table:
Login Total_sess Total_min Most_freq_computer Sess_on_most_freq Min_on_most_freq
John 4 109 Computer2 2 54
Bill 3 68 Computer5 2 36
Jim - - - - -
Myself I can only cover first 3 columns with:
SELECT Login, COUNT(sessions.UserId), SUM(Minutes) FROM users
LEFT JOIN sessions
ON users.Id = sessions.UserId GROUP BY users.Id
And some kind of other columns with:
SELECT main.*
FROM (SELECT UserId, ComputerId, COUNT(*) AS cnt ,SUM(Minutes)
FROM sessions
GROUP BY UserId, ComputerId) AS main
INNER JOIN (
SELECT ComputerId, MAX(cnt) AS maxCnt FROM (
SELECT ComputerId, UserId, COUNT(*) AS cnt FROM sessions GROUP BY ComputerId, UserId
)
AS Counts GROUP BY ComputerId)
AS maxes
ON main.ComputerId = maxes.ComputerId
AND main.cnt = maxes.maxCnt
But I need to get whole resulting table in one query. I feel I'm doing something completely wrong. Need help.
Here you are:
SELECT u.login, t1.total_sess, t1.total_min, t2.mf, t2.sess_mf, t2.min_mf
FROM users u
LEFT JOIN (
SELECT userid, COUNT(minutes) AS total_sess, SUM(minutes) AS total_min
FROM sessions
GROUP BY userid
) AS t1 ON t1.userid = u.id
LEFT JOIN (
SELECT userid, name AS mf, COUNT(*) AS sess_mf, SUM(minutes) AS min_mf
FROM sessions s
JOIN computers c ON c.id = s.computerid
GROUP BY userid, computerid
HAVING COUNT(computerid) >= ALL(SELECT COUNT(*)
FROM sessions s2
WHERE s2.userid = s.userid
GROUP BY s2.computerid)
) AS t2 ON t2.userid = u.id
I'm using MySQL syntax, but it should be pretty portable.
If you need anything more, feel free to ask!
EDIT: I updated the query, the previous one was wrong :(

SQL: Select from many-many through, joined on conditions in through table

Can't get my head around this...
I have 3 tables like this:
Computers
---------
Id
Name
ComputerLogins
--------------
Computer_Id
User_Id
NumberOfLogins
Users
-----
Id
Name
Computers "have and belong to many" Users "through" ComputerLogins.
Sample data:
Computers: Id Name
1 "Alpha"
2 "Beta"
3 "Gamma"
Users: Id Name
1 "Joe"
2 "Fred"
ComputerLogins: Computer_Id User_Id NumberOfLogins
1 1 5
1 2 12
2 1 10
2 2 6
3 1 2
3 2 4
I'm trying to construct a view that will output one row for each record in Computers, and join a Users row through MAX(NumberOfLogins) in ComputerLogins.
Desired output:
Computer_Id User_Id NumberOfLogins
1 2 12
2 1 10
3 2 4
Can you suggest a view query that will produce the desired output?
Thanks!
SELECT
CL.*, U.* --change this as needed
FROM
(
SELECT
Computer_ID, MAX(NumberOfLogins) AS NumberOfLogins
FROM
ComputerLogins
GROUP BY
Computer_ID
) maxC
JOIN
ComputerLogins CL On maxC.Computer_ID = CL.Computer_ID AND maxC.NumberOfLogins = CL.NumberOfLogins
JOIN
Users U On CL.User_ID = U.ID
Wrap in a view etc
Use:
CREATE VIEW your_view AS
SELECT c.id AS computer_id,
u.id AS user_id,
COUNT(*) AS NumberOfLogins
FROM COMPUTERS c
JOIN COMPUTERLOGINS cl ON cl.computer_id = c.id
JOIN USERS u ON u.id = cl.user_id
GROUP BY c.id, u.id