I am trying to combine the data of three tables but running into a minor issue.
Let's say we have 3 tables
Table A
ID | ID2 | ID3 | Name | Age
1 2x 4y John 23
2 7j Mike 27
3 1S1 6HH Steve 67
4 45 O8 Carol 56
Table B
| ID2 | ID3 | Price
2x 4y 23
7j 8uj 27
x4 Q6 56
Table C
|ID | Weight|
1 145
1 210
1 240
2 234
2 110
3 260
3 210
4 82
I want to get every record from table A of everyone who weighs 200 or more but they cannot be in table B. Table A and C are joined by ID. Table A and B are joined by either ID2 or ID3. ID2 and ID3 don't both have to necessarily be populated but at least 1 will. Either can be present or both and they will be unique. So expected result is
3 | 1S1 | 6HH | Steve| 67
Note that a person can have multiple weights but as long as at least one record is 200 or above they get pulled.
What I have so far
Select *
From tableA x
Where
x.id in (Select distinct y.id
From tableA y, tableC z
Where y.id = z.id
And z.weight >= '200'
And y.id not in (Select distinct h.id
From tableA h, tableB k
Where (h.id2 = k.id2 or h.id3 = k.id3)))
When I do this it seems to ignore the check on tableB and I get John, Mike and Steve. Any ideas? Sorry it's convoluted, this is what I have to work with. I am doing this in oracle by the way.
This sounds like exists and not exists. So a direct translation is:
select a.*
from tableA a
where exists (select 1 from tableC c where c.id = a.id and c.weight >= 200) and
not exists (select 1 from tableB b where b.id2 = a.id2 or b.id3 = a.id3);
Splitting the or into two separate subqueries can often improve performance:
select a.*
from tableA a
where exists (select 1 from tableC c where c.id = a.id and c.weight >= 200) and
not exists (select 1 from tableB b where b.id2 = a.id2) and
not exists (select 1 from tableB b where b.id3 = a.id3);
Here's what I came up with.
SELECT DISTINCT
A.ID,
A.ID2,
A.ID3,
A.Name,
A.Age
FROM
A
LEFT OUTER JOIN C ON C.ID = A.ID
LEFT OUTER JOIN B ON
B.ID2 = A.ID2
OR B.ID3 = A.ID3
WHERE
C.Weight >= 200
AND B.Price IS NULL
BELOW is test data
CREATE TABLE A
(
ID INT,
ID2 VARCHAR(3),
ID3 VARCHAR(3),
Name VARCHAR(10),
Age INT
);
INSERT INTO A VALUES (1, '2x', '4y', 'John', 23);
INSERT INTO A VALUES (2, '7j', NULL , 'Mike', 27);
INSERT INTO A VALUES (3, '1S1', '6HH', 'Steve', 67);
INSERT INTO A VALUES (4, '45', 'O8', 'Carol', 56);
CREATE TABLE B
(
ID2 VARCHAR(3),
ID3 VARCHAR(3),
Price INT
);
INSERT INTO B VALUES ('2x', '4y', 23);
INSERT INTO B VALUES ('7j', '8uj', 27);
INSERT INTO B VALUES ('x4', 'Q6', 56);
CREATE TABLE C
(
ID INT,
Weight INT
);
INSERT INTO C VALUES (1, 145);
INSERT INTO C VALUES (1, 210);
INSERT INTO C VALUES (1, 240);
INSERT INTO C VALUES (2, 234);
INSERT INTO C VALUES (2, 110);
INSERT INTO C VALUES (3, 260);
INSERT INTO C VALUES (3, 210);
INSERT INTO C VALUES (4, 82);
Select a.id, a.id2, a.id3
From table_a a
Left join table_c c on a.id = c.id
Where c.weight >=200
And not exists
(Select 1
From table_b b
Where a.id = b.id2
Or a.id = b.id3
);
I was beating to the answers, but I used INNER JOIN on tables a and c and a NOT EXISTS on table b.
--This first section is creating the test data
with Table_A (id, id2, id3, Name, age) as
(select 1, '2x', '4y', 'John', 23 from dual union all
select 2, '7j', null, 'Mike', 27 from dual union all
select 3, '1S1', '6HH', 'Steve', 67 from dual union all
select 4, '45', 'O8', 'Carol', 56 from dual),
Table_B(id2, id3, price) as
(select '2x', '4y', 23 from dual union all
select '7j', '8uj', 27 from dual union all
select 'x4', 'Q6', 56 from dual),
Table_C(id, weight) as
(select 1, 145 from dual union all
select 1, 210 from dual union all
select 1, 240 from dual union all
select 2, 234 from dual union all
select 2, 110 from dual union all
select 3, 260 from dual union all
select 3, 210 from dual union all
select 4, 82 from dual)
--Actual query starts here
select distinct a.*
from table_a a
--join to table c, include the weight filter
inner join table_c c on (a.id = c.id and c.weight >= 200)
where not exists -- The rest is the NOT EXISTS to exclude the values in table b
(select 1 from table_b b
where a.id2 = b.id2
or a.id3 = b.id3);
Related
There are two tables Table1 and Table2
A B C S
a 1 21 Summer
b 2 22 Summer
c 3 34 Summer
D E F S
a 1 21 Summer
d 5 22 Summer
f 2 34 Summer
I wanted to fetch all columns of Table first and count of column F from table second based on conditions such as column B OF table first is less than values of column E of table second.
I tried this query
select a.*,
(select count(F) from Table2 JOIN Table1 on table1.S=table2.S where table1.B<table2.E ) AS Cnt
from Table1 a
However it is giving me same count for all rows which is incorrect
I need count based on condition fulfilled.
This will work
DECLARE #T1 TABLE (A Varchar(10), B INT, C INT, S Varchar(10))
DECLARE #T2 TABLE (D Varchar(10), E INT, F INT, S Varchar(10))
INSERT INTO #T1 VALUES
('a', 1, 21,'Summer'),
('b', 2 ,22,'Summer'),
('c', 3, 34,'Summer')
INSERT INTO #T2 VALUES
('a', 1, 21,'Summer'),
('d', 5 ,22,'Summer'),
('f', 2 ,34,'Summer')
SELECT
T1.A,T1.B,T1.C,T1.S,
COUNT(T2.S) as Counts
FROM #T1 T1
left JOIN #T2 T2
ON T1.S = T2.S AND T1.B < T2.E
GROUP BY T1.A,T1.B,T1.C,T1.S
Output
A B C S Counts
a 1 21 Summer 2
b 2 22 Summer 1
c 3 34 Summer 1
select *,
(select count(F)
from T2
where T1.S = T2.S AND T1.B < T2.E
) as Cnt from T1
I think you just want a correlated subquery:
select a.*,
(select count(t2.F)
from Table2 t2
where a.S = t2.S and t1.B < t2.E
) as Cnt
from Table1 a
I am adding a commission percent column to an existing query. However, the commission data sits in a table unrelated to the main table (A) used within the query. However, these two tables have common columns/values with Table B.
I have three tables, A, B and C below
Table A
Reference Value_Name Renewal_Code
1 A N
2 A R
3 B N
4 A R
4 A N
Table B
Reference Value_Name Prod_Code
1 A 0016
2 A 0027
4 A 0032
4 A 0032
Table C
A_Prod_Code A_Tans_Code **Commission_Percent**
0016 Renewal 5
0027 Renewal 5
0032 New 10
0032 Renewal 5
I need to get the Commission_Percent from Table C relating to the corresponding Renewal_Code from Table A. This is the same as A_Tans_Code from Table C except that Table C spells out Renewal or New and Table A only uses R or N.
I have been able to pull through the Commission_Percent column into the output by using Table B for common values, but all values show as NULL.
I have also tried using a decode statement in order to link the Renewal_code/A_Trans_Code columns from Tables A and C.
(
SELECT
distinct c.commision_percent
FROM
TableA a
JOIN TableB b ON a.reference = b.reference
AND b.value_name = 'A'
JOIN TableC c ON b.prod_code = c.a_prod_code
AND b.value_name = 'A'
JOIN TableC c ON a.renewal_code = decode(c.a_trans_code, 'Rewnal','R','New','N')
) Commission_Percent
I need the correct commission_percent for Renewal and New business to come through for each reference. So far, I am only getting NULLs as I am having a hard time linking Tables A and C's Renewal_code and A_prod_code columns.
Any help is greatly appreciated!
This creates reproducible testing by cleaning up temp tables and re-inserting the data.
And could be a model for other solutions.
The SQL 'Select...' joins to a single TableC that has two parts to the ON condition-- prod_code and renewal_code (instead of two joins). Just remove the "a.*," to use it in your sql. (the decode function was changed to use a subscript of the first char of the A_Trans_Code).
IF OBJECT_ID('tempdb..#TableA') IS NOT NULL DROP TABLE #TableA
GO
CREATE TABLE #TableA
( Reference INTEGER
, Value_Name VARCHAR(10)
, Renewal_Code VARCHAR(10) )
INSERT INTO #TableA VALUES( 1, 'A', 'N' );
INSERT INTO #TableA VALUES( 2, 'A', 'R' );
INSERT INTO #TableA VALUES( 3, 'B', 'N' );
INSERT INTO #TableA VALUES( 4, 'A', 'R' );
INSERT INTO #TableA VALUES( 4, 'A', 'N' );
IF OBJECT_ID('tempdb..#TableB') IS NOT NULL DROP TABLE #TableB
GO
CREATE TABLE #TableB
( Reference INTEGER
, Value_Name VARCHAR(10)
, Prod_Code VARCHAR(10) )
INSERT INTO #TableB VALUES( 1, 'A', '0016' );
INSERT INTO #TableB VALUES( 2, 'A', '0027' );
INSERT INTO #TableB VALUES( 4, 'A', '0032' );
INSERT INTO #TableB VALUES( 4, 'A', '0032' );
IF OBJECT_ID('tempdb..#TableC') IS NOT NULL DROP TABLE #TableC
GO
CREATE TABLE #TableC
( Prod_Code VARCHAR(10)
, A_Trans_Code VARCHAR(10)
, Commission_Percent INTEGER )
INSERT INTO #TableC VALUES( '0016', 'Renewal', 5 );
INSERT INTO #TableC VALUES( '0027', 'Renewal', 5 );
INSERT INTO #TableC VALUES( '0032', 'New', 10 );
INSERT INTO #TableC VALUES( '0032', 'Renewal', 5 );
SELECT distinct a.*, c.commission_percent
FROM #TableA a
JOIN #TableB b ON a.reference = b.reference
AND a.value_name = b.value_name
JOIN #TableC c ON b.prod_code = c.prod_code
AND a.renewal_code = SUBSTRING(c.a_trans_code,1,1)
Results are--
Reference Value_Name Renewal_Code commission_percent
2 A R 5
4 A N 10
4 A R 5
Code to put in your sql
( SELECT distinct c.commission_percent
FROM TableA a
JOIN TableB b ON a.reference = b.reference
AND a.value_name = b.value_name
JOIN TableC c ON b.prod_code = c.prod_code
AND a.renewal_code = SUBSTRING(c.a_trans_code,1,1)
) Commission_Percent
WITH table_a AS (
SELECT 1 AS reference, 'A' AS value_name, 'N' AS renewal_code FROM DUAL UNION ALL
SELECT 2 AS reference, 'A' AS value_name, 'R' AS renewal_code FROM DUAL UNION ALL
SELECT 3 AS reference, 'B' AS value_name, 'N' AS renewal_code FROM DUAL UNION ALL
SELECT 4 AS reference, 'A' AS value_name, 'R' AS renewal_code FROM DUAL UNION ALL
SELECT 4 AS reference, 'A' AS value_name, 'N' AS renewal_code FROM DUAL
),
table_b AS (
SELECT 1 AS reference, 'A' AS value_name, '0016' AS prod_code FROM DUAL UNION ALL
SELECT 2 AS reference, 'A' AS value_name, '0027' AS prod_code FROM DUAL UNION ALL
/* SELECT 4 AS reference, 'A' AS value_name, '0032' AS prod_code FROM DUAL UNION ALL duplicate row excluded */
SELECT 4 AS reference, 'A' AS value_name, '0032' AS prod_code FROM DUAL
),
table_c AS (
SELECT '0016' AS a_prod_code, 'Renewal' AS a_tans_code, 5 AS commission_percent FROM DUAL UNION ALL
SELECT '0027' AS a_prod_code, 'Renewal' AS a_tans_code, 5 AS commission_percent FROM DUAL UNION ALL
SELECT '0032' AS a_prod_code, 'New' AS a_tans_code, 10 AS commission_percent FROM DUAL UNION ALL
SELECT '0032' AS a_prod_code, 'Renewal' AS a_tans_code, 5 AS commission_percent FROM DUAL
)
SELECT a.reference AS ref_a,
a.value_name AS value_name_a,
b.reference AS ref_b,
b.value_name AS value_name_b,
a.renewal_code,
b.prod_code,
c.commission_percent
FROM table_a a
LEFT OUTER JOIN table_b b
ON a.value_name = b.value_name
AND a.reference = b.reference
LEFT OUTER JOIN table_c c
ON SUBSTR(c.a_tans_code, 1, 1) = a.renewal_code
AND c.a_prod_code = b.prod_code
;
The common table expression (CTE) is just to create the same values you posted (CTE is the part using the WITH construct).
Results:
REF_A VALUE_NAME_A REF_B VALUE_NAME_B RENEWAL_CODE PROD_CODE COMMISSION_PERCENT
---------- --------------- ---------- --------------- --------------- ---------- --------------------
2 A 2 A R 0027 5
4 A 4 A N 0032 10
4 A 4 A R 0032 5
1 A 1 A N 0016
3 B N
You may have to check the values in the rows you posted that do not result in a join. This also assumes there are no codes:code_name relationships that violate the rule of first letter of code name = code.
Assume the following simplified schema:
create table main_table
(
a number,
b number,
c number
);
create table other_table
(
c number,
d number
)
Now, what i want to achieve:
I have a query on main_table, that groups by a,b.
I need to use the "all values of c" in subquery in select clause to get some data from other tables.
I can't join to the other table unfortunately.
Pseudocode would be:
select mt.a,
mt.b,
(select /* some aggregated value */
from other_table ot
where ot.c in (all_values_of_c_within_group)
)
from main table mt
group by mt.a, mt.b
There are two ways i know it's possible to handle this:
Use join on other_table and then aggregate values from there - unfortunately i can't do it, because of how the real query is structured (3 nested views, 800 sloc, 30 values in group by - long story)
Use listagg and then 'delistagg' it with 'instr'. Pseudocode:
/*(...)*/
(select /* some_aggregated_value */
from other_table ot
where instr(',' || listagg(
to_char(mt.c), ',') within group (order by 1),
',' || ot.c) > 0
)
/*(...)*/
But that's just terrible code, and it automatically prevents using any potentially existing indexes on other_table.c.
Is there a syntax to properly get "all values of column within group?
It is unclear without some data and expected results what you are trying to achieve but I think you do what you want using collections:
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table main_table( a, b, c ) AS
SELECT 1, 1, 1 FROM DUAL UNION ALL
SELECT 1, 1, 2 FROM DUAL UNION ALL
SELECT 1, 1, 3 FROM DUAL
/
create table other_table( c, d ) AS
SELECT 1, 4 FROM DUAL UNION ALL
SELECT 3, 6 FROM DUAL UNION ALL
SELECT 5, 8 FROM DUAL
/
CREATE TYPE number_table AS TABLE OF NUMBER
/
Query 1:
SELECT a,
b,
( SELECT LISTAGG( d, ',' ) WITHIN GROUP ( ORDER BY d )
FROM other_table
WHERE c MEMBER OF m.cs
) ds
FROM (
SELECT a,
b,
CAST( COLLECT( c ) AS number_table ) AS cs
FROM main_table
GROUP BY a, b
) m
Results:
| A | B | DS |
|---|---|-----|
| 1 | 1 | 4,6 |
Query 2: But it seems simpler to just use a LEFT OUTER JOIN:
SELECT a,
b,
LISTAGG( d, ',' ) WITHIN GROUP ( ORDER BY d ) ds
FROM main_table m
LEFT OUTER JOIN other_table o
ON ( m.c = o.c )
GROUP BY a, b
Results:
| A | B | DS |
|---|---|-----|
| 1 | 1 | 4,6 |
You may just be able to aggregate the subquery, e.g. with sum as the aggregate function:
select mt.a,
mt.b,
sum(
(select d
from other_table ot
where ot.c = mt.c)
) as sum_d
from main_table mt
group by mt.a, mt.b;
With some made-up data:
insert into main_table values (1, 2, 3);
insert into main_table values (1, 2, 4);
insert into main_table values (2, 3, 4);
insert into main_table values (2, 3, 5);
insert into main_table values (2, 3, 6);
insert into other_table values (3, 10);
insert into other_table values (4, 11);
insert into other_table values (5, 12);
insert into other_table values (6, 13);
that query gives:
A B SUM_D
---------- ---------- ----------
2 3 36
1 2 21
As you noted, with an extra row:
insert into main_table values (2, 3, 4);
that query counts a matching c's d value multiple times, so you get 47 instead of 36:
A B SUM_D
---------- ---------- ----------
2 3 47
1 2 21
You can add a distinct:
select mt.a,
mt.b,
sum(distinct
(select d
from other_table ot
where ot.c = mt.c)
) as sum_d
from main_table mt
group by mt.a, mt.b;
A B SUM_D
---------- ---------- ----------
1 2 21
2 3 36
This assumes that c, or at least the combination of c, d, is unique in other_table.
This should work, and should not impose the uniqueness requirements on other_table that Alex's answer does.
select mt.a,
mt.b,
(select sum(d) /* some aggregated value */
from other_table ot
where ot.c in ( SELECT mt2.c
FROM main_table mt2
WHERE mt2.a = mt.a AND mt2.b = mt.b
)
) agg
from main_table mt
group by mt.a, mt.b;
It has to go to main_table again for each group, but considering you already are accessing those records, we should be talking about extra logical I/O instead of extra physical I/O.
Using Alex Poole's test data (with the duplicate MAIN_TABLE row), I get this in 12c:
+---+---+-----+
| A | B | AGG |
+---+---+-----+
| 2 | 3 | 36 |
| 1 | 2 | 21 |
+---+---+-----+
I have following table structure. I would like to get sum at each level from TAB2.
TAB1 stores hierarchy in level columns.
TAB1
----- ----- ---- ----
KEY L1 L2 L3
---- ----- ----- ----
A A
B A B
C A B C
D A B D
TAB2
-----
KEY TC
---- ----
A 10
B 11
C 6
D 12
X 11
Expected Output:
KEY SUM
---- ----
A 39
B 29
C 6
D 12
X 11
Here is SQLFiddle Link: LINK TO FIDDLE
Oracle Setup
Create table TAB1 (pKey varchar2(10),level1 varchar2(10),level2 varchar2(10),level3 varchar2(10),level4 varchar2(10));
insert into TAB1(pKey,level1) values('A','A');
insert into TAB1(pKey,level1,level2) values('B','A','B');
insert into TAB1(pKey,level1,level2,level3) values('C','A','B','C');
insert into TAB1(pKey,level1,level2,level3) values('D','A','B','D');
Create table TAB2 (pKey varchar(10), tc integer);
insert into TAB2(pKey,tc) values('A',10);
insert into TAB2(pKey,tc) values('B',11);
insert into TAB2(pKey,tc) values('C',6);
insert into TAB2(pKey,tc) values('D',12);
insert into TAB2(pKey,tc) values('X',11);
Query:
SELECT t2.pKey,
SUM( COALESCE( t4.TC, t2.tc ) ) AS tc
FROM tab2 t2
LEFT OUTER JOIN
tab1 t1
ON ( t2.pKey = t1.pKey )
LEFT OUTER JOIN
tab1 t3
ON ( t1.level1 = t3.level1
AND ( t1.level2 IS NULL OR t1.level2 = t3.level2 )
AND ( t1.level3 IS NULL OR t1.level3 = t3.level3 )
AND ( t1.level4 IS NULL OR t1.level4 = t3.level4 ) )
LEFT OUTER JOIN
tab2 t4
ON ( t3.pKey = t4.pKey )
GROUP BY t2.pKey;
Output:
PKEY TC
---------- ----------
D 12
A 39
B 29
C 6
X 11
In the solution provided below (including the input data as factored subqueries), first I show how to use unpivot and additional operations to normalize tab1 (the result is the factored subquery n for "normalized"). Then, if you had the data in normal form, the output could be obtained by a direct application of standard hierarchical querying as shown at the bottom of my code.
with
tab1 (key, L1, L2, L3) as (
select 'A', 'A', null, null from dual union all
select 'B', 'A', 'B' , null from dual union all
select 'C', 'A', 'B' , 'C' from dual union all
select 'D', 'A', 'B' , 'D' from dual
),
tab2 (key, TC) as (
select 'A', 10 from dual union all
select 'B', 11 from dual union all
select 'C', 6 from dual union all
select 'D', 12 from dual union all
select 'X', 11 from dual
),
unpiv (key, l, ancestor) as (
select key, to_number(substr(lv, 2)), ancestor from tab1
unpivot (ancestor for lv in (L1, L2, L3))
),
d (key, depth) as (
select key, max(l)
from unpiv
group by key
),
n (child, parent, TC) as (
select d.key, u.ancestor, tab2.TC
from unpiv u
right outer join d
on u.key = d.key and u.l = d.depth - 1
left outer join tab2
on d.key = tab2.key
)
SELECT key, sum(TC) as sum_TC
from (
select connect_by_root child as key, TC
from n
connect by prior child = parent
)
group by key
order by key;
Along the way, in unpiv, I already had all the parent-child relationships, so I could have joined that directly with tab2 on unpiv.key = tab2.key and summed TC grouping by ancestor (similar to MT0's solution). Instead, I wanted to demonstrate two separate steps: (1) normalizing tab1 and (2) how easy it is to use hierarchical queries on normalized tables.
Output:
KEY SUM_TC
--- ----------
A 39
B 29
C 6
D 12
I have two tables like this.
A B
1 12
2 13
3 12
4 13
5 15
B C
12 APPLE
13 ORANGE
14 MANGO
15 BANANA
I need output as below...
count(A) B C
2 12 APPLE
2 13 ORANGE
0 14 MANGO
1 15 BANANA
I have written the query using joins but I am stuck at displaying the count as zero in case of empty value.
Use a left join to get the values of table2 even if there are no records for them in table1
select T2.B, T2.C, count(T1.A)
from table2 T2
left join table1 T1 on T1.B = T2.B
group by T2.B, T2.C
Try this:
SELECT COUNT(T1.A) as Cnt,T2.B,T2.C
FROM Table2 T2 LEFT JOIN
Table1 T1 ON T1.B=T2.B
GROUP BY T2.B,T2.C
Result:
CNT B C
2 12 APPLE
1 15 BANANA
0 14 MANGO
2 13 ORANGE
See result in SQL Fiddle.
Just to give you another option. You don't need a join, as you only want to show table2 records along with another value which you can get in a sub-query.
select (select count(*) from table1 where table1.B = table2.B) as cnt, B, C
from table2
order by B;
There are other ways to do it (using subquery), but I would use this following one:
-- SETUP
CREATE TABLE #TABLE1 (A INT, B INT);
CREATE TABLE #TABLE2 (B INT, C CHAR(10));
INSERT #TABLE1
SELECT 1, 12 UNION ALL
SELECT 2, 13 UNION ALL
SELECT 3, 12 UNION ALL
SELECT 4, 13 UNION ALL
SELECT 5, 15
INSERT #TABLE2
SELECT 12, 'APPLE' UNION ALL
SELECT 13, 'ORANGE' UNION ALL
SELECT 14, 'MANGO' UNION ALL
SELECT 15, 'BANANA'
-- query
SELECT COUNT(qty.A), dsc.B, dsc.C
FROM #TABLE2 dsc
LEFT JOIN #TABLE1 qty ON (dsc.B = qty.B)
GROUP BY dsc.B, dsc.C
ORDER BY dsc.B, dsc.C;
DECLARE #TABLE1 TABLE (A INT, B INT)
INSERT INTO #TABLE1 VALUES
(1, 12),
(2, 13),
(3, 12),
(4, 13),
(5, 15)
DECLARE #TABLE2 TABLE (B INT, C VARCHAR(20))
INSERT INTO #TABLE2 VALUES
(12,'APPLE'),
(13,'ORANGE'),
(14,'MANGO'),
(15,'BANANA')
SELECT t2.C
,ISNULL(COUNT(t1.B), 0) total_Count
FROM #TABLE2 t2 LEFT JOIN #TABLE1 t1
ON t2.B = t1.B
GROUP BY t2.C
C total_Count
APPLE 2
BANANA 1
MANGO 0
ORANGE 2