join multiple times in SQL - sql

These are the 2 tables.
Tech_data:
Id Tech Agent1_id Agent2_ID
1 JAVA 1 2
2 SQL 3 4
Agent_table
Id Name
1 Mike
2 John
3 Jim
4 Baron
I need to write a query to bring the below output
TECH_ID Tech Agent1_Name Agent2_Name
1 Java Mike John
2 SQL Jim Baron
I wrote LEFT OUTER JOIN ON tech_id=agent1_id, but i do not know how to join 2 ids in ON condition.

To prevent having to do multiple joins to the same table, you can unpivot, join and then pivot (then if you had 50 ID columns you would still only need to perform one join):
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Tech_data (Id, Tech, Agent1_id, Agent2_ID ) AS
SELECT 1, 'JAVA', 1, 2 FROM DUAL UNION ALL
SELECT 2, 'SQL', 3, 4 FROM DUAL;
CREATE TABLE Agent_table ( Id, Name ) AS
SELECT 1, 'Mike' FROM DUAL UNION ALL
SELECT 2, 'John' FROM DUAL UNION ALL
SELECT 3, 'Jim' FROM DUAL UNION ALL
SELECT 4, 'Baron' FROM DUAL;
Query 1:
SELECT *
FROM (
SELECT t.id,
t.tech,
t.num,
a.name
FROM (
SELECT *
FROM tech_data
UNPIVOT ( Agent_ID FOR num IN ( Agent1_id AS 1, Agent2_id AS 2 ) )
) t
INNER JOIN Agent_table a
ON ( t.agent_id = a.id )
)
PIVOT ( MAX( name ) FOR num IN ( 1 AS Agent1_Name, 2 AS Agent2_Name ) )
Results:
| ID | TECH | AGENT1_NAME | AGENT2_NAME |
|----|------|-------------|-------------|
| 1 | JAVA | Mike | John |
| 2 | SQL | Jim | Baron |

You can add a second left outer join just like the one you have used, on the same table by giving them different aliases as follows.
select t.Id tech_id, t.tech, a1.name, a2.name
from tech_data t
left outer join agent_table a1 on a1.Id = t.agent1_id
left outer join agent_table a2 on a2.Id = t.agent2_Id;
Check the fiddle below:
http://sqlfiddle.com/#!4/73f02b/1

Related

SQL recursive id nodes

I have a table structure like so
Id Desc Node
---------------------
1 A
2 Aa 1
3 Ab 1
4 B
5 Bb 4
6 Bb1 5
these Desc values are presented in a listview to the user, if the user chooses Bb, I want the ID 5 and also the ID 4 becuase thats the root node of that entry, simular to that if the user chooses Bb1, I need ID 6, 5 and 4
I am only able to query one level up, but there could be n levels, so my query at the moment looks like this
SELECT Id
FROM tbl
WHERE Desc = 'Bb1'
OR Id = (SELECT Node FROM tbl WHERE Desc = 'Bb1');
You can do this with Recursive CTE like below
Schema:
CREATE TABLE #TAB (ID INT, DESCS VARCHAR(10), NODE INT)
INSERT INTO #TAB
SELECT 1 AS ID, 'A' DESCS, NULL NODE
UNION ALL
SELECT 2 , 'AA', 1
UNION ALL
SELECT 3, 'AB', 1
UNION ALL
SELECT 4, 'B', NULL
UNION ALL
SELECT 5, 'BB', 4
UNION ALL
SELECT 6, 'BB1', 5
Now do recursive CTE for picking node value and apply it again on #TAB with a Join.
;WITH CTE AS(
SELECT ID, DESCS, NODE FROM #TAB WHERE ID=6
UNION ALL
SELECT T.ID, T.DESCS, T.NODE FROM #TAB T
INNER JOIN CTE C ON T.ID = C.NODE
)
SELECT * FROM CTE
When you pass 6 to the first query in CTE, the result will be
+----+-------+------+
| ID | DESCS | NODE |
+----+-------+------+
| 6 | BB1 | 5 |
| 5 | BB | 4 |
| 4 | B | NULL |
+----+-------+------+

Selecting groups of rows where at least one row of each group meets a criteria

I'm trying to SELECT groups of rows having one row with a certain criteria.
I've tried it with CASE WHEN statements without any success. Keep in mind this table has hundred of records.
What I'm trying to accomplish is this:
One row of the group must have a subcategory equal to "GAMECONSOLE".
Rows having the same category, description and section form one group.
The ID is different so MIN and MAX does not work either.
ID SECTION DESCRIPTION CATEGORY SUBCATEGORY
21349 14010014 TODDLER TOY GAMECONSOLE
21278 14010014 TODDLER TOY BICYCLE
21431 15020021 TODDLER TOY CHESS
In this example the two first rows should be selected because they form one group and one row of the group is a "GAMECONSOLE".
CASE WHEN is used when you have to take a decision within a column expression. Filtering on row level must be done in a WHERE clause:
SELECT T.id, T.section, T.description, T.category, T.subcategory
FROM
myTable T
INNER JOIN myTable S
ON T.section = S.section AND
T.description = S.description AND
T.category = S.category
WHERE
S.subcategory = 'GAMECONSOLE'
You can join the table with itself on the columns that have to be equal. The table with alias S selects the right subategory. T selects all corresponding rows of the groups.
SELECT a1.ID
, a1.SECTION
, a1.DESCRIPTION
, a1.CATEGORY
, a1.SUBCATEGORY
FROM MyTable a1
INNER JOIN MyTable a2 ON a2.DESCRIPTION = a1.DESCRIPTION
AND a2.CATEGORY = a1.CATEGORY
AND a2.SECTION = a1.SECTION
WHERE a2.SUBCATEGORY = 'GAMECONSOLE'
-- you may want to further filter the Where clause and apply a group by or distinct to get the actual results you are wanting
Your description sounds like:
select
...
from
(
select
...
,sum(case when subcategory = 'GAMECONSOLE' then 1 else 0 end)
over (partition by category, description, section) as cnt
from tab
) dt
where cnt > 0
SELECT *
FROM myTable T
WHERE Section = (SELECT Section
FROM myTable Q
WHERE Q.subcategory = 'GAMECONSOLE')
Using an analytic function you can get the answer without using a self join.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST( ID, SECTION, DESCRIPTION, CATEGORY, SUBCATEGORY ) AS
SELECT 1, 1, 'TODDLER', 'TOY', 'GAMECONSOLE' FROM DUAL
UNION ALL SELECT 2, 1, 'TODDLER', 'TOY', 'BICYCLE' FROM DUAL
UNION ALL SELECT 3, 2, 'TODDLER', 'TOY', 'CHESS' FROM DUAL
UNION ALL SELECT 4, 3, 'COMPUTERS', 'SOFTWARE', 'BOOK' FROM DUAL
UNION ALL SELECT 5, 4, 'COMPUTERS', 'SOFTWARE', 'SOFTWARE' FROM DUAL
UNION ALL SELECT 6, 5, 'COMPUTERS', 'HARDWARE', 'MONITOR' FROM DUAL
UNION ALL SELECT 7, 6, 'COMPUTERS', 'HARDWARE', 'GAMECONSOLE' FROM DUAL
UNION ALL SELECT 8, 7, 'COMPUTERS', 'HARDWARE', 'KEYBOARD' FROM DUAL
UNION ALL SELECT 9, 8, 'TODDLER', 'BEDDING', 'BED' FROM DUAL
Query 1:
SELECT ID, SECTION, DESCRIPTION, CATEGORY, SUBCATEGORY
FROM (
SELECT t.*,
COUNT( CASE SUBCATEGORY WHEN 'GAMECONSOLE' THEN 1 END ) OVER ( PARTITION BY DESCRIPTION, CATEGORY ) AS HAS_SUBCATEGORY
FROM TEST t
)
WHERE HAS_SUBCATEGORY > 0
Results:
| ID | SECTION | DESCRIPTION | CATEGORY | SUBCATEGORY |
|----|---------|-------------|----------|-------------|
| 8 | 7 | COMPUTERS | HARDWARE | KEYBOARD |
| 7 | 6 | COMPUTERS | HARDWARE | GAMECONSOLE |
| 6 | 5 | COMPUTERS | HARDWARE | MONITOR |
| 3 | 2 | TODDLER | TOY | CHESS |
| 2 | 1 | TODDLER | TOY | BICYCLE |
| 1 | 1 | TODDLER | TOY | GAMECONSOLE |
Try
SELECT * FROM <TABLE_NAME> WHERE SUBCATEGORY like "GAMECONSOLE";
or
SELECT * FROM <TABLE_NAME> WHERE SUBCATEGORY = "GAMECONSOLE";
Replace <TABLE_NAME> with the actual table name.
Further readings:
https://dev.mysql.com/doc/refman/5.0/en/select.html

Oracle sql group sum

I have table With ID,Sub_ID and value coloumns
ID SUB_ID Value
100 1 100
100 2 150
101 1 100
101 2 150
101 3 200
102 1 100
SUB ID can vary from 1..maxvalue( In this example it is 3). I need Sum of values for each Sub_ID. If SUB_ID is less than MAXVALUE for a particlaur ID then it should take MAX(SUB_ID) of each ID As shown below ( In this example for ID=100 for SUB_ID 3 it should take 150 i.e 2<3 so value=150))
SUB_ID SUM(values) Remarks
1 300 (100+100+100)
2 400 (150+150+100)
3 450 (150+200+100)
This can be easily done in PL/SQL . Can we use SQL for the same using Model Clause or any other options
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TableA ( ID, SUB_ID, Value ) AS
SELECT 100, 1, 100 FROM DUAL
UNION ALL SELECT 100, 2, 150 FROM DUAL
UNION ALL SELECT 101, 1, 100 FROM DUAL
UNION ALL SELECT 101, 2, 150 FROM DUAL
UNION ALL SELECT 101, 3, 200 FROM DUAL
UNION ALL SELECT 102, 1, 100 FROM DUAL
Query 1:
WITH sub_ids AS (
SELECT LEVEL AS sub_id
FROM DUAL
CONNECT BY LEVEL <= ( SELECT MAX( SUB_ID ) FROM TableA )
),
max_values AS (
SELECT ID,
MAX( VALUE ) AS max_value
FROM TableA
GROUP BY ID
)
SELECT s.SUB_ID,
SUM( COALESCE( a.VALUE, m.max_value ) ) AS total_value
FROM sub_ids s
CROSS JOIN
max_values m
LEFT OUTER JOIN
TableA a
ON ( s.SUB_ID = a.SUB_ID AND m.ID = a.ID )
GROUP BY
s.SUB_ID
Results:
| SUB_ID | TOTAL_VALUE |
|--------|-------------|
| 1 | 300 |
| 2 | 400 |
| 3 | 450 |
Try this
SELECT SUB_ID,SUM(values),
(SELECT DISTINCT SUBSTRING(
(
SELECT '+'+ CAST(values AS VARCHAR)
FROM table_Name AS T2
WHERE T2.SUB_ID = d.SUB_ID
FOR XML PATH ('')
),2,100000)[values]) as values
FROm table_Name d
GROUP BY SUB_ID
How about something like this:
select max_vals.sub_id, sum(nvl(table_vals.value,max_vals.max_value)) as sum_values
from (
select all_subs.sub_id, t1.id, max(t1.value) as max_value
from your_table t1
cross join (select sub_id from your_table) all_subs
group by all_subs.sub_id, t1.id
) max_vals
left outer join your_table table_vals
on max_vals.id = table_vals.id
and max_vals.sub_id = table_vals.sub_id
group by max_vals.sub_id;
The inner query gets you a list of all sub_id/id combinations and their fall-back values. The out query uses an nvl to use the table value if it exists and the fall-back value if it doesn't.

Left outer join on aggregate queries

So I have two payment tables that I want to compare in a Oracle SQL DB. I want to compare the the total payments using the location and invoice and total payments. It's more comlex then this but basically it is:
select
tbl1.location,
tbl1.invoice,
Sum(tbl1.payments),
Sum(tbl2.payments)
From
tbl1
left outer join tbl2 on
tbl1.location = tbl2.location
and tbl1.invoice = tbl2.invoice
group by
(tbl1.location,tbl1.invoice)
I want the left outer join because in addition to comparing payment amounts, I want see check all orders in tbl1 that may not exist in tbl2.
The issue is that there is that there is multiple records for each order (location & invoice) in both tables (not the same number of records necessarily ie 2 in tbl1 to 1 in tbl2 or vice versa) but the total payments for each order (location & invoice) should match. So just doing a direct join gives me a cartesian product.
So I am thinking I could do two queries, first aggregating the total payments by store & invoice for each and then do a join on those results because in the aggregate results, I would only have one record for each order (store & invoice). But I don't know how to do this. I've tried several subqueries but can't seem the shake the cartesian product. I'd like to be able to do this in one query as opposed to creating tables and joining on those as this will be ongoing.
Thanks in advance for any help.
You can use the With statement to create the two querys and join then as you said. I will put just the sintaxe and if you need more help just ask. Thats because you didn't provide full details on your tables. So I will just guess on my answer.
WITH tmpTableA as (
select
tbl1.location,
tbl1.invoice,
Sum(tbl1.payments) totalTblA
From
tbl1
group by
tbl1.location,
tbl1.invoice
),
tmpTableB as (
select
tbl2.location,
tbl2.invoice,
Sum(tbl2.payments) totalTblB
From
tbl2
group by
tbl2.location,
tbl2.invoice
)
Select tmpTableA.location, tmpTableA.invoice, tmpTableA.totalTblA,
tmpTableB.location, tmpTableB.invoice, tmpTableB.totalTblB
from tmpTableA, tmpTableB
where tmpTableA.location = tmpTableB.location (+)
and tmpTableA.invoice = tmpTableB.invoice (+)
The (+) operator is the left join operator for Oracle Database (Of course, you can use the LEFT JOIN statements if you prefer )
Two other options:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tbl1 ( id, location, invoice, payments ) AS
SELECT 1, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 2, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 3, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 4, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 5, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 6, 'a', 2, 1 FROM DUAL
UNION ALL SELECT 7, 'a', 2, 1 FROM DUAL
UNION ALL SELECT 8, 'a', 2, 1 FROM DUAL
UNION ALL SELECT 9, 'b', 1, 1 FROM DUAL
UNION ALL SELECT 10, 'b', 2, 1 FROM DUAL;
CREATE TABLE tbl2 ( id, location, invoice, payments ) AS
SELECT 1, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 2, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 3, 'a', 1, 1 FROM DUAL
UNION ALL SELECT 4, 'a', 2, 1 FROM DUAL
UNION ALL SELECT 5, 'a', 2, 1 FROM DUAL
UNION ALL SELECT 6, 'b', 1, 1 FROM DUAL
UNION ALL SELECT 7, 'b', 1, 1 FROM DUAL
UNION ALL SELECT 8, 'b', 1, 1 FROM DUAL
UNION ALL SELECT 9, 'b', 1, 1 FROM DUAL
UNION ALL SELECT 10, 'b', 1, 1 FROM DUAL;
Query 1:
This one uses a correlated sub-query to calculate the total for the second table:
SELECT location,
invoice,
SUM( payments ) AS total_payments_1,
COALESCE( (SELECT SUM( payments )
FROM tbl2 i
WHERE o.location = i.location
AND o.invoice = i.invoice),
0 ) AS total_payments_2
FROM tbl1 o
GROUP BY
location,
invoice
ORDER BY
location,
invoice
Results:
| LOCATION | INVOICE | TOTAL_PAYMENTS_1 | TOTAL_PAYMENTS_2 |
|----------|---------|------------------|------------------|
| a | 1 | 5 | 3 |
| a | 2 | 3 | 2 |
| b | 1 | 1 | 5 |
| b | 2 | 1 | 0 |
Query 2:
This one uses a named sub-query to pre-calculate the totals for table 1 then performs a LEFT OUTER JOIN with the second table and includes the total for table 1 in the group.
Without any indexes then, from the explain plans, Query 1 seems to be much more efficient but your indexes might mean the optimizer finds a better plan.
WITH tbl1_sums AS (
SELECT location,
invoice,
SUM( payments ) AS total_payments_1
FROM tbl1
GROUP BY
location,
invoice
)
SELECT t1.location,
t1.invoice,
t1.total_payments_1,
COALESCE( SUM( t2.payments ), 0 ) AS total_payments_2
FROM tbl1_sums t1
LEFT OUTER JOIN
tbl2 t2
ON ( t1.location = t2.location
AND t1.invoice = t2.invoice)
GROUP BY
t1.location,
t1.invoice,
t1.total_payments_1
ORDER BY
t1.location,
t1.invoice
Results:
| LOCATION | INVOICE | TOTAL_PAYMENTS_1 | TOTAL_PAYMENTS_2 |
|----------|---------|------------------|------------------|
| a | 1 | 5 | 3 |
| a | 2 | 3 | 2 |
| b | 1 | 1 | 5 |
| b | 2 | 1 | 0 |
Sorry, my first answer was wrong. Thank you for providing the sqlfiddle, MT0.
The point that i missed is that you need to sum up the payments on each table first, so there's only one line left in each, then join them. This is what MT0 does in his statements.
If you want a solution that looks more "symmetric", try:
select A.location, A.invoice, B.total sum1, C.total sum2
from (select distinct location, invoice from tbl1) A
left outer join (select location, invoice, sum(payments) as total from tbl1 group by location, invoice) B on A.location=B.location and A.invoice=B.invoice
left outer join (select location, invoice, sum(payments) as total from tbl2 group by location, invoice) C on A.location=C.location and A.invoice=C.invoice
which results in
LOCATION INVOICE SUM1 SUM2
a 2 3 2
a 1 5 3
b 1 1 5
b 2 1 (null)

SQL - Select what is not in second table from assocciative

I have a table "person", an associative table "person_vaccination" and a table "vaccination".
I want to get the person who has missing vaccinations but so far I only got it to work when I have the id.
SELECT vac.VACCINATION_Name
FROM VACCINATION vac
WHERE vac.VACCINATION_NUMBER NOT IN
(SELECT v.VACCINATION_NUMBER
FROM PERSON per
Join PERSON_VACCINATION pv ON per.PERSON_NUMBER = pv.PERSON_NUMBER
JOIN VACCINATION v ON pv.VACCINATION_NUMBER = v.VACCINATION_NUMBER
WHERE per.PERSON_NUMBER = 6)
It works fine but how do I get all the people missing their vaccinations? (ex:
555 , Vacccination 1
555 , Vacccination 2
666 , Vacccination 1)
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE VACCINATION ( VACCINATION_NUMBER, VACCINATION_NAME ) AS
SELECT 1, 'Vac 1' FROM DUAL
UNION ALL SELECT 2, 'Vac 2' FROM DUAL
UNION ALL SELECT 3, 'Vac 3' FROM DUAL
UNION ALL SELECT 4, 'Vac 4' FROM DUAL;
CREATE TABLE PERSON_VACCINATION ( VACCINATION_NUMBER, PERSON_NUMBER ) AS
SELECT 1, 1 FROM DUAL
UNION ALL SELECT 2, 1 FROM DUAL
UNION ALL SELECT 3, 1 FROM DUAL
UNION ALL SELECT 4, 1 FROM DUAL
UNION ALL SELECT 1, 2 FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
UNION ALL SELECT 3, 2 FROM DUAL;
CREATE TABLE PERSON ( PERSON_NUMBER, PERSON_NAME ) AS
SELECT 1, 'P1' FROM DUAL
UNION ALL SELECT 2, 'P2' FROM DUAL
UNION ALL SELECT 3, 'P3' FROM DUAL;
Query 1:
SELECT p.PERSON_NAME,
v.VACCINATION_NAME
FROM VACCINATION v
CROSS JOIN
PERSON p
WHERE NOT EXISTS ( SELECT 1
FROM PERSON_VACCINATION pv
WHERE pv.VACCINATION_NUMBER = v.VACCINATION_NUMBER
AND pv.PERSON_NUMBER = p.PERSON_NUMBER )
ORDER BY p.PERSON_NAME,
p.PERSON_NUMBER,
v.VACCINATION_NAME,
v.VACCINATION_NUMBER
Results:
| PERSON_NAME | VACCINATION_NAME |
|-------------|------------------|
| P2 | Vac 4 |
| P3 | Vac 1 |
| P3 | Vac 2 |
| P3 | Vac 3 |
| P3 | Vac 4 |
Instead of an INNER JOIN, you should use LEFT JOIN.
Take a look at this link: http://www.w3schools.com/sql/sql_join_left.asp
If you are after people with no vaccinations at all, then you can use a LEFT OUTER JOIN between PERSON and PERSON_VACCINATION, then find all entries where a PERSON_VACCINATION column is NULL.
SELECT PERSON_NUMBER
FROM PERSON P
LEFT OUTER JOIN
PERSON_VACCINATION PV
ON P.PERSON_NUMBER = PV.PERSON_NUMBER
WHERE PV.PERSON_NUMBER IS NULL
If you are unfamiliar with LEFT OUTER JOIN, it tries to find matching rows in PERSON_VACCINATION for each row in PERSON. If there are no matching rows, it leaves the PERSON row in the result set, and shows NULL values for all columns in the PERSON_VACCINATION table.
If you are looking for a list of people and the vaccinations they do not have then #MT0's answer is correct. You need to create a result set containing all possible combinations of PERSON and VACCINATION (a Cross Join), then check which of those combinations actually exist in PERSON_VACCINATION. Any entry that does not exist is are your missing vaccinations.