JOIN removing rows without entry - sql

I want to display columns even if they have no entry to show they have no data. I've found that joins have omit row needed.
I have two tables
|TRADEID | Value | Date |
|--------|-------|-----------|
| a | 100 | 01/01/2020|
| b | 500 | 01/01/2020|
| c | 10 | 01/01/2020|
| d | 130 | 01/01/2020|
| ID | TradeID | Role | employeeID|
|-----|---------|---------|-----------|
| 1 | a | Trader | T1 |
| 2 | a | Seller | S1 |
| 3 | b | Trader | T1 |
| 4 | d | Trader | T2 |
| 5 | d | Seller | S1 |
| 6 | d | Reporter| R1 |
I would like to end up with the following
TradeID | Trader | Seller | Reporter| Value|
---------|--------|--------|---------|------|
a | T1 | S1 | | 100 |
b | T1 | | | 500 |
c | | | | 10 |
d | T2 | S1 | R1 | 130 |
My current query is :
select t1.TradeID, r1.employeeID, r2.employeeId, r3.employeeId, t1.value
From tradeTable t1
join RoleTable r1 on t1.TradeID = r1.TradeID and r1.role = 'Trader'
join RoleTable r2 on t1.TradeId = r2.TradeID and r1.role = 'Seller'
join RoleTable r3 on t1.TradeId = r3.TradeID and r1.role = 'Reporter'
This however only returns rows d as it has all the values present.

You can left join:
select t1.TradeID, r1.employeeID trader, r2.employeeId seller, r3.employeeId reporter, t1.value
from tradeTable t1
left join RoleTable r1 on t1.TradeID = r1.TradeID and r1.role = 'Trader'
left join RoleTable r2 on t1.TradeId = r2.TradeID and r1.role = 'Seller'
left join RoleTable r3 on t1.TradeId = r3.TradeID and r1.role = 'Reporter'
Another option is conditional aggregation:
select t1.TradeID,
max(case when r.role = 'Trader' then r.employeeID end) trader,
max(case when r.role = 'Seller' then r.employeeID end) seller,
max(case when r.role = 'Reporter' then r.employeeID end) reporter,
t1.value
from tradeTable t1
left join RoleTable r
group by t1.TradeID, t1.value
You might want to test both options to assess which one is more efficient for your dataset.

Related

SQL self join result of a select

I have two tables, lets call them A and B which I perform an inner join on.
select
A.id,
A.serial_number as serial,
concat(B.type, '-', B.primary, '-', B.secondary) as product_number, A.parent_id as parent
from A
inner join B on A.number_id = B.id) as T1
as a result I get a set that contains parents and children (1 per parent).
+----+--------+-----------------+--------+
| id | serial | product number | parent |
+----+--------+-----------------+--------+
| 1 | 123 | abc | null |
| 2 | 234 | cde | 1 |
| 3 | 456 | abc | null |
| 4 | 895 | cde | 2 |
+----+--------+-----------------+--------+
now I'd like to do a self join to get the following
+----+---------------+------------------------+---------------+-----------------------+
| id | serial parent | product_number parent | serial child | product_number child |
+----+---------------+------------------------+---------------+-----------------------+
| 1 | 123 | abc | 234 | cde |
| 2 | 456 | abc | 895 | cde |
+----+---------------+------------------------+---------------+-----------------------+
What would be the best approach for this, I simply couldn't find an easy solution... is there a way to join T1 with itself?
I think that's more joins:
select
ap.id as id_parent,
ap.serial_number as serial_parent,
concat_ws('-', bp.type, bp.primary, bp.secondary) as product_number_parent,
ac.child as id_child,
ac.serial_number as serial_child,
concat_ws('-', bc.type, bc.primary, bc.secondary) as product_number_child
from a ap
inner join a ac on ac.parent = ap.id
inner join b bp on bp.id = ap.astrol_number_id
inner join b bc on bc.id = ac.astrol_number_id
where ap.parent is null

Issue with SQL join and group

I have 4 tables I am trying to join and then group data. The data consists of jobs, invoices and accounts. I want to generate a total of each account in each job.
I have the following tables:
Jobs
| ID | JobNumber |
|----|-----------|
| 1 | J200 |
| 2 | J201 |
Job_Invoices
| ID | InvoiceNumber | JobID |
|----|---------------|-------|
| 10 | I300 | 1 |
| 11 | I301 | 2 |
Invoice_Accounts
| ID | InvoiceId | AccountID | Amount |
|----|-----------|-----------|--------|
| 23 | 10 | 40 | 200 |
| 24 | 10 | 40 | 300 |
| 25 | 10 | 41 | 100 |
| 26 | 11 | 40 | 100 |
Accounts
| ID | Name |
|----|------|
| 40 | Sales|
| 41 | EXP |
I am trying the following:
SELECT
J.JobNumber,
A.Name AS "Account",
SUM(JA.Amount) AS 'Total'
FROM
Job J
LEFT JOIN
Job_Invoices JI ON JI.JobID = J.JobID
INNER JOIN
Invoice_Accounts JA ON JA.InvoiceId = JI.ID
INNER JOIN
Accounts A ON A.ID = JA.AccountID
GROUP BY
J.JobNumber, A.Name, JA.Amount
ORDER BY
J.JobNumber
What I expect:
| JobNumber | Account | Total |
|-----------|-----------|-------|
| J200 | EXP | 100 |
| J200 | Sales | 500 |
| J201 | Sales | 100 |
What I get:
| JobNumber | Account | Total |
|-----------|-----------|-------|
| J200 | EXP | 100 |
| J200 | Sales | 200 |
| J200 | Sales | 300 |
| J201 | Sales | 100 |
You don't need the Job table in the query. The INNER JOINs are to the Job_Invoices table, so the outer join is turned into an inner join anyway.
So, you can simplify this to:
SELECT JI.JobNumber, A.Name AS Account, SUM(JA.Amount) AS Total
FROM Job_Invoices JI JOIN
Invoice_Accounts JA
ON JA.InvoiceId = JI.ID JOIN
Accounts A
ON A.ID = JA.AccountID
GROUP BY JI.JobNumber, A.Name
ORDER BY JI.JobNumber;
Also note that you don't need to escape the column aliases. The just makes the query harder to type.
The problem is you have the JA.Amount in your GROUP BY clause. Try taking it out:
SELECT J.JobNumber, A.Name AS "Account", SUM(JA.Amount) AS 'Total'
FROM Job J
LEFT JOIN Job_Invoices JI ON JI.JobID = J.JobID
INNER JOIN Invoice_Accounts JA ON JA.InvoiceId = JI.ID
INNER JOIN Accounts A ON A.ID = JA.AccountID
GROUP BY J.JobNumber, A.Name
ORDER BY J.JobNumber
You can write a query as:
select sum (IA.Amount) as Amount, J.JobNumber,A.Name
from #Invoice_Accounts IA --as it holds the base data
join #Job_Invoices JI on IA.InvoiceId = JI.ID
join #Jobs J on J.id = JI.JobID
join #Accounts A on A.ID = IA.AccountID
group by J.JobNumber,A.Name
Included the Jobs table as it has the JobNumber column. Sample code here..

Combine multiple rows to one after group by

UPDATE:
https://www.db-fiddle.com/f/5fGUTSsAhGRPYPk33wDSzz/0
Sorry for asking the question very similar to the previous one, but I am really stuck here.
There are multiple tables:
items → items_roles → roles
items → zones → roles_zones → roles
Structure:
items: id, zone_id
items_roles: role_id, item_id
zones: id
roles_zones: role_id, zone_id
roles: id, role_type_id,
I am trying to add role fields to items, it should take role_type and it is value from items_zones and if it is NULL fetch fallback value from zone (roles_zones).
I started with:
SELECT
items.id,
(z_roles.role_type_id) as z_role_type_id,
(z_roles.id) as z_role_id,
MAX(i_roles.role_type_id) as i_role_type_id,
MAX(i_roles.id) as i_role_id
FROM
items
LEFT JOIN
zones as j_zones ON j_zones.id = items.zone_id
LEFT JOIN
roles_zones ON roles_zones.zone_id = j_zones.id
LEFT JOIN
roles as z_roles ON (z_roles.id = roles_zones.role_id)
LEFT JOIN
items_roles ON items_roles.item_id = items.id
LEFT JOIN
roles as i_roles ON items_roles.role_id = i_roles.id
AND (z_roles.role_type_id = i_roles.role_type_id)
WHERE
items.id = 834
GROUP BY
items.id, z_roles.role_type_id, z_roles.id
ORDER BY
i_role_id;
Looks right:
id |z_role_type_id |z_role_id |i_role_type_id |i_role_id |
----+---------------+----------+---------------+----------+
834 |5 |111 |5 |68 |
834 |11 |120 |11 |120 |
834 |7 |77 | | |
834 |2 |2 | | |
834 |12 |91 | | |
834 |4 |78 | | |
834 |8 |36 | | |
And now this query:
SELECT
items.id,
z_roles.role_type_id as z_role_type_id,
z_roles.id as z_role_id,
MAX(i_roles.role_type_id) AS i_role_type_id,
MAX(i_roles.id) AS i_role_id,
MAX(CASE
WHEN (i_roles.role_type_id = 5) THEN i_roles.id
WHEN (z_roles.role_type_id = 5) THEN z_roles.id
END) AS role_type_5_value,
MAX(CASE
WHEN (i_roles.role_type_id = 11) THEN i_roles.id
WHEN (z_roles.role_type_id = 11) THEN z_roles.id
END) AS role_type_11_value,
MAX(CASE
WHEN (i_roles.role_type_id = 7) THEN i_roles.id
WHEN (z_roles.role_type_id = 7) THEN z_roles.id
END) AS role_type_7_value
FROM
items
LEFT JOIN
zones AS j_zones ON j_zones.id = items.zone_id
LEFT JOIN
roles_zones ON roles_zones.zone_id = j_zones.id
LEFT JOIN
roles AS z_roles ON (z_roles.id = roles_zones.role_id)
LEFT JOIN
items_roles ON items_roles.item_id = items.id
LEFT JOIN
roles AS i_roles ON items_roles.role_id = i_roles.id
AND (z_roles.role_type_id = i_roles.role_type_id)
WHERE
items.id = 834
GROUP BY
items.id,
z_roles.role_type_id,
z_roles.id
ORDER BY
items.id, i_role_id;
Generates this:
id | z_role_type_id | z_role_id | i_role_type_id | i_role_id | role_type_5_value | role_type_11_value | role_type_7_value
-----+----------------+-----------+----------------+-----------+-------------------+--------------------+-------------------
834 | 5 | 111 | 5 | 68 | 111 | |
834 | 11 | 120 | 11 | 120 | | 120 |
834 | 7 | 77 | | | | | 77
834 | 2 | 2 | | | | |
834 | 12 | 91 | | | | |
834 | 4 | 78 | | | | |
834 | 8 | 36 | | | | |
(7 rows)
Multiple rows and wrong value for role_type_5_value. Probably because of MAX aggregator. Is it possible to use something like first aggregator (because rows ordered by i_role_id and first results are right)?
I want this:
id | role_type_5_value | role_type_11_value | role_type_7_value
-----+-------------------+--------------------+-------------------
834 | 68 | 120 | 77
I tried to group by by aggregated fields, (role_type_5_value, role_type_11_value, role_type_7_value) but this is simply not working.
First: removing the unneeded bridge-tables from the main query and squeeze them into EXISTS() terms will simplify your query.(you only need three tables, the rest is glue)
Second: don't put all your terms in the GROUP BY clause.
SELECT
i0.id
, MAX(CASE
WHEN (r1.role_type_id = 5) THEN r1.id
WHEN (r0.role_type_id = 5) THEN r0.id
END) AS role_type_5_value
, MAX(CASE
WHEN (r1.role_type_id = 11) THEN r1.id
WHEN (r0.role_type_id = 11) THEN r0.id
END) AS role_type_11_value
, MAX(CASE
WHEN (r1.role_type_id = 7) THEN r1.id
WHEN (r0.role_type_id = 7) THEN r0.id
END) AS role_type_7_value
FROM items i0
LEFT JOIN roles AS r0
ON EXISTS ( SELECT*
FROM zones AS z0
JOIN roles_zones rz ON rz.zone_id = z0.id
WHERE z0.id = i0.zone_id
AND r0.id = rz.role_id)
LEFT JOIN roles AS r1
ON EXISTS ( SELECT*
FROM items_roles ir
WHERE ir.item_id = i0.id
AND ir.role_id = r1.id
AND r0.role_type_id = r1.role_type_id
)
WHERE i0.id = 834
GROUP BY i0.id
-- r0.role_type_id,
-- r0.id
ORDER BY i0.id;

SQL - Join with multiple condition

I'm trying to join my users table with my jobs table based on a mapping table users_jobs:
Here is what the users table looks like:
users
|--------|------------------|
| id | name |
|--------|----------------- |
| 1 | Ozzy Osbourne |
| 2 | Lemmy Kilmister |
| 3 | Ronnie James Dio |
| 4 | Jimmy Page |
|---------------------------|
jobs table looks like this:
|--------|-----------------|
| id | title |
|--------|-----------------|
| 1 | Singer |
| 2 | Guitar Player |
|--------------------------|
And users_jobs table looks like this:
|--------|-------------|-------------|---------------|-------------|
| id | user_id | job_id | column3 | column4 |
|--------|-------------|-------------|---------------|-------------|
| 1 | 1 | 1 | 0 | 1 |
| 2 | 2 | 1 | 1 | 0 |
| 3 | 3 | 1 | 0 | 1 |
| 4 | 4 | 2 | 1 | 0 |
|----------------------|-------------|---------------|-------------|
For example, let's say the ozzy does a query.
Here is what should expect:
|--------|------------------|------------|--------- |
| id | name | column3 | column4 |
|--------|----------------- |------------|----------|
| 1 | Ozzy Osbourne | 0 | 1 |
| 2 | Lemmy Kilmister | 1 | 0 |
| 3 | Ronnie James Dio | 0 | 1 |
|---------------------------|------------|----------|
Basically, he can only see the job in which he is registered (role) and the users included.
I tried to do this:
SELECT u1.*, uj1.colum3, uj1.column4
FROM users AS u1
JOIN users_jobs AS uj1 ON uj1.user_id = 1
JOIN jobs AS j1 ON j1.id = up1.job_id
WHERE uj1.job_id = 1
Any help would be great!
Looks like you need INNER JOIN Try this :
select u.id, u.column3 , u.column4 from users u
inner join user_jobs uj on u.id=uj.user_id
inner join jobs j on j.id=uj.job_id
where uj.job_id=1;
If you need by certain user_id
select u.id, u.column3 , u.column4 from users u
inner join user_jobs uj on u.id=uj.user_id
inner join jobs j on j.id=uj.job_id
where uj.job_id=1
and u.id=1;
I found a solution.
Using #stackFan approach adding an EXISTS clause to make sure that the user is in.
SELECT u.id, u.column3 , u.column4
FROM users u
INNER JOIN user_jobs uj on u.id = uj.user_id
INNER JOIN jobs j on j.id = uj.job_id
WHERE uj.job_id = <job-ID>
AND
EXISTS (
SELECT *
FROM users_jobs AS uj
WHERE uj.job_id = <job-ID>
AND uj.user_id = <user-ID>
);
Try LEFT JOIN. It will display all users, whether they have job or not.
SELECT u.id, u.name, uj.colum3, uj.column4
FROM users AS u
LEFT JOIN users_jobs uj ON uj.user_id = u.id
LEFT JOIN jobs j ON j.id = uj.job_id

How to join results from two tables in Oracle 10

Let say that I have 2 tables with the same structure : STOCK and NEW_STOCK.
These tables have a primary key composed of (ID_DATE, ID_SELLER, ID_INVOICE, ID_DOC).
Now, I need to get for every (ID_DATE, ID_SELLER, ID_INVOICE, ID_DOC), the value of the amount (field AMOUNT) regarding this requirement:
If a record is present in NEW_STOCK, I get the AMOUNT from NEW_STOCK, otherwise, I get the AMOUNT from STOCK table.
Note that ID_DATE and ID_SELLER are the inputs given to the query, i.e. a query that considers only STOCK table will look like :
select AMOUNT, ID_DATE, ID_SELLER, ID_INVOICE
from STOCK
where ID_DATE = 1
and ID_SELLER = 'SELL1';
STOCK :
+---------+-----------+------------+--------+--------+
| ID_DATE | ID_SELLER | ID_INVOICE | ID_DOC | AMOUNT |
+---------+-----------+------------+--------+--------+
| 1 | SELL1 | IN1 | DOC1 | 100 |
| 1 | SELL1 | IN2 | DOC2 | 50 |
| 1 | SELL1 | IN3 | DOC3 | 42 |
+---------+-----------+------------+--------+--------+
NEW_STOCK:
+---------+-----------+------------+--------+--------+
| ID_DATE | ID_SELLER | ID_INVOICE | ID_DOC | AMOUNT |
+---------+-----------+------------+--------+--------+
| 1 | SELL1 | IN2 | DOC2 | 12 |
+---------+-----------+------------+--------+--------+
Then, I must get the following results:
+---------+-----------+------------+--------+--------+
| ID_DATE | ID_SELLER | ID_INVOICE | ID_DOC | AMOUNT |
+---------+-----------+------------+--------+--------+
| 1 | SELL1 | IN1 | DOC1 | 100 |
| 1 | SELL2 | IN2 | DOC2 | 12 |
| 1 | SELL3 | IN3 | DOC3 | 42 |
+---------+-----------+------------+--------+--------+
ps: I'm working on Oracle 10.
Use outer join and NVL(arg1, arg2) function.
It returns first argument if it is not NULL, otherwise it returns second argument. Example:
select s.AMOUNT, s.ID_DATE, s.ID_SELLER, s.ID_INVOICE,
NVL(n.AMOUNT, s.AMOUNT) amount
from STOCK s, NEW_STOCK n
where s.ID_DATE = n.ID_DATE(+)
and s.ID_SELLER = n.ID_SELLER(+)
and s.ID_INVOICE = n.ID_INVOICE(+)
and s.ID_DOC = n.ID_DOC(+)
and s.ID_DATE = 1
and s.ID_SELLER = 'SELL1';
You can use LEFT OUTER JOIN syntax instead of (+) if you find it more readable. I'm using Oracle since v7 and I like (+) more.
Here is LEFT OUTER JOIN syntax:
select s.AMOUNT, s.ID_DATE, s.ID_SELLER, s.ID_INVOICE,
NVL(n.AMOUNT, s.AMOUNT) amount
from STOCK s left outer join NEW_STOCK n
on s.ID_DATE = n.ID_DATE
and s.ID_SELLER = n.ID_SELLER
and s.ID_INVOICE = n.ID_INVOICE
and s.ID_DOC = n.ID_DOC
where s.ID_DATE = 1
and s.ID_SELLER = 'SELL1';
SELECT * FROM (
SELECT * FROM new_stock
UNION ALL
SELECT * FROM stock
WHERE (ID_DATE,ID_SELLER,ID_INVOICE,ID_DOC) NOT IN
(SELECT ID_DATE,ID_SELLER,ID_INVOICE,ID_DOC FROM new_stock)
)
WHERE ID_DATE = 1
AND ID_SELLER = 'SELL1';
The following should work for it:
SELECT s.AMOUNT, s.ID_DATE, s.ID_SELLER, s.ID_INVOICE
FROM STOCK s
LEFT JOIN NEW_STOCK ns
ON s.ID_DATE = ns.ID_DATE
AND s.ID_SELLER = ns.ID_SELLER
AND s.ID_INVOICE = ns.ID_INVOICE
WHERE s.ID_DATE = 1
AND s.ID_SELLER = 'SELL1'
AND ns.ID_DATE IS NULL
UNION
SELECT AMOUNT, ID_DATE, ID_SELLER, ID_INVOICE
FROM NEW_STOCK
WHERE ID_DATE = 1
AND ID_SELLER = 'SELL1';
Exclude the matched rows from a LEFT JOIN and UNION that set with the results from the NEW_STOCK table.
SELECT COALESCE(NS.AMOUNT, S.AMOUNT) AMOUNT,
S.ID_DATE,
S.ID_SELLER,
S.ID_INVOICE
FROM STOCK S
LEFT JOIN NEW_STOCK NS ON S.ID_DATE = NS.ID_DATE
AND S.ID_SELLER = NS.ID_SELLER
AND S.ID_INVOICE = NS.ID_INVOICE
AND S.ID_DOC = NS.ID_DOC
WHERE S.ID_DATE = 1
AND S.ID_SELLER = 'SELL1'