How can this SELECT query be tuned any further or this is the ONLY efficient way? I'm using Oracle Exadata 19c.
It joins two tables multiple times but on different conditions and returns data as different columns.
There is a better way but I don't know what it is.
DDLs/ DMLs to try it out:
CREATE TABLE TRAN (
C1 VARCHAR2(50) NOT NULL,
C2 VARCHAR2(50),
C3 VARCHAR2(50),
C4 VARCHAR2(50),
C5 VARCHAR2(50),
C6 VARCHAR2(50),
C7 VARCHAR2(50),
C8 VARCHAR2(50),
C9 VARCHAR2(50)
);
CREATE TABLE ADDR (
C1 VARCHAR2(50) NOT NULL,
C2 VARCHAR2(50),
BRANCH VARCHAR2(50),
ADDR VARCHAR2(50)
);
insert into TRAN (C1, C2, C3, C4, C5, C6, C7, C8, C9) values ('A111', 'Q1', 'Q1', 'Q1', 'Q1', 'Q1', 'Q1', 'Q1', 'Q1');
insert into TRAN (C1, C2, C3, C4, C5, C6, C7, C8, C9) values ('A222', 'Q2', 'Q2', 'Q2', null, null, null, null, 'Q2');
insert into TRAN (C1, C2, C3, C4, C5, C6, C7, C8, C9) values ('A333', 'Q3', 'Q3', 'Q3', 'Q3', 'Q3', 'Q3', 'Q3', 'Q3');
insert into TRAN (C1, C2, C3, C4, C5, C6, C7, C8, C9) values ('A444', null, null, null, null, 'Q4', 'Q4', 'Q4', 'Q4');
insert into ADDR (C1, C2, BRANCH, ADDR) values ('A111', 'Q1', 'CHN', 'INDIA');
insert into ADDR (C1, C2, BRANCH, ADDR) values ('A222', 'Q2','BLR', 'USA');
insert into ADDR (C1, C2, BRANCH, ADDR) values ('A444', 'Q4', 'HYD', 'UK');
commit;
Select Query:
WITH T1 as (SELECT tran.* FROM tran),
T2 as (SELECT ADDR.* FROM ADDR)
SELECT
T1.C1,
T21.BRANCH AS BRANCH1,
T21.ADDR AS ADDR1,
T22.BRANCH AS BRANCH2,
T22.ADDR AS ADDR2,
T23.BRANCH AS BRANCH3,
T23.ADDR AS ADDR3,
T24.BRANCH AS BRANCH4,
T24.ADDR AS ADDR4,
T25.BRANCH AS BRANCH5,
T25.ADDR AS ADDR5,
T26.BRANCH AS BRANCH6,
T26.ADDR AS ADDR6,
T27.BRANCH AS BRANCH7,
T27.ADDR AS ADDR7,
T28.BRANCH AS BRANCH8,
T28.ADDR AS ADDR8
FROM T1
LEFT OUTER JOIN T2 T21 ON T1.C1 = T21.C1 AND T1.C2 = T21.C2
LEFT OUTER JOIN T2 T22 ON T1.C1 = T22.C1 AND T1.C3 = T22.C2
LEFT OUTER JOIN T2 T23 ON T1.C1 = T23.C1 AND T1.C4 = T23.C2
LEFT OUTER JOIN T2 T24 ON T1.C1 = T24.C1 AND T1.C5 = T24.C2
LEFT OUTER JOIN T2 T25 ON T1.C1 = T25.C1 AND T1.C6 = T25.C2
LEFT OUTER JOIN T2 T26 ON T1.C1 = T26.C1 AND T1.C7 = T26.C2
LEFT OUTER JOIN T2 T27 ON T1.C1 = T27.C1 AND T1.C8 = T27.C2
LEFT OUTER JOIN T2 T28 ON T1.C1 = T28.C1 AND T1.C9 = T28.C2;
For every join, the first condition is on the same columns but the second one differs, with different columns from T1 to the same column in T2.
I have rewritten those multiple joins using OR and multiple CASE statements in a SELECT clause but the execution time is higher despite the low cost.
For the given output, referring table T2 8 times is the only way?
Expected Input/Output:
If you are only expecting one row for each C1 value then you can JOIN once and pivot using conditional aggregation:
SELECT t.c1,
MAX(CASE WHEN a.c2 = t.c2 THEN a.branch END) AS branch1,
MAX(CASE WHEN a.c2 = t.c2 THEN a.addr END) AS addr1,
MAX(CASE WHEN a.c2 = t.c3 THEN a.branch END) AS branch2,
MAX(CASE WHEN a.c2 = t.c3 THEN a.addr END) AS addr2,
MAX(CASE WHEN a.c2 = t.c4 THEN a.branch END) AS branch3,
MAX(CASE WHEN a.c2 = t.c4 THEN a.addr END) AS addr3,
MAX(CASE WHEN a.c2 = t.c5 THEN a.branch END) AS branch4,
MAX(CASE WHEN a.c2 = t.c5 THEN a.addr END) AS addr4,
MAX(CASE WHEN a.c2 = t.c6 THEN a.branch END) AS branch5,
MAX(CASE WHEN a.c2 = t.c6 THEN a.addr END) AS addr5,
MAX(CASE WHEN a.c2 = t.c7 THEN a.branch END) AS branch6,
MAX(CASE WHEN a.c2 = t.c7 THEN a.addr END) AS addr6,
MAX(CASE WHEN a.c2 = t.c8 THEN a.branch END) AS branch7,
MAX(CASE WHEN a.c2 = t.c8 THEN a.addr END) AS addr7,
MAX(CASE WHEN a.c2 = t.c9 THEN a.branch END) AS branch8,
MAX(CASE WHEN a.c2 = t.c9 THEN a.addr END) AS addr8
FROM tran t
LEFT OUTER JOIN addr a
ON ( t.c1 = a.c1
AND a.c2 IN (t.c2, t.c3, t.c4, t.c5, t.c6, t.c7, t.c8, t.c9))
GROUP BY t.c1
Which, for the sample data, outputs:
C1
BRANCH1
ADDR1
BRANCH2
ADDR2
BRANCH3
ADDR3
BRANCH4
ADDR4
BRANCH5
ADDR5
BRANCH6
ADDR6
BRANCH7
ADDR7
BRANCH8
ADDR8
A444
null
null
null
null
null
null
null
null
HYD
UK
HYD
UK
HYD
UK
HYD
UK
A333
null
null
null
null
null
null
null
null
null
null
null
null
null
null
null
null
A111
CHN
INDIA
CHN
INDIA
CHN
INDIA
CHN
INDIA
CHN
INDIA
CHN
INDIA
CHN
INDIA
CHN
INDIA
A222
BLR
USA
BLR
USA
BLR
USA
null
null
null
null
null
null
null
null
BLR
USA
fiddle
Related
I have a table as below-
id
name
col1
col2
col3
1
aaa
1
null
null
1
aaa
null
1
null
1
aaa
null
null
1
1
aaa
null
null
2
1
aaa
null
1
null
2
bbb
null
null
null
2
bbb
1
null
null
i want the output as below-
id
name
col1
col2
col3
1
aaa
1
1
1
1
aaa
null
1
2
2
bbb
1
null
null
i tried doing this:
select id ,name, max(col1),max(col2),max(col3)
from table group by id, name;
but the output is this, which is not as expected
id
name
col1
col2
col3
1
aaa
1
1
2
can u pls help me achieve the desired output.
many thanks in advance.
You may try this as is.
Firstly, the rows in each group specified by (id, name) columns are enumerated. Note, that if you don't use the order by clause in the row_number function (as in the example), such an enumeration may be done in some non-repeatable and non-predictable way. So, if you want a repeatable result, you should have a way to order the rows explicitly. I've added an additional row, and you may use it uncommenting the order by clause to get the exact result as you specified in the question.
Secondly, the rows in each group are processed with a Recursive Common Table Expression statement. A new surrogate column grp (additional group number inside each group) is introduced. The first row of each group (having rn_=1) gets all the original values and grp=1. The grp value of each next row is increased by 1, if the the value of c1, c2 or c3 in this next row is not null and a value in a previous row of the corresponding column is non null as well.
If the group number is not increased, a non-null value from a previous row is copied instead the current value for each cx.
If it is increased, the current row values are retained.
Finally, we use this surrogate group number in the group by clause.
dbfiddle link.
with mytab (id, name, c1, c2, c3, ord) as
(
values
(1, 'aaa', 1, null::int, null::int, 10)
, (1, 'aaa', null, 1, null, 20)
, (1, 'aaa', null, null, 1, 30)
, (1, 'aaa', null, null, 2, 40)
, (1, 'aaa', null, 1, null, 50)
, (2, 'bbb', null, null, null, 1)
, (2, 'bbb', 1, null, null, 2)
)
-- Rows enumeration inside all groups
, mytab2 as
(
select row_number () over (partition by id, name
--order by ord
) rn_, t.*
from mytab t
)
--
, a (id, name, rn_, grp, c1, c2, c3) as
(
select id, name, rn_, 1 as grp, c1, c2, c3
from mytab2
where rn_ = 1
union all
select t.id, t.name, t.rn_
, a.grp
+ case
when a.c1 is not null and t.c1 is not null
or a.c2 is not null and t.c2 is not null
or a.c3 is not null and t.c3 is not null
then 1
else 0
end as grp
, case
when a.c1 is not null and t.c1 is not null
or a.c2 is not null and t.c2 is not null
or a.c3 is not null and t.c3 is not null
then t.c1
else coalesce (a.c1, t.c1)
end as c1
, case
when a.c1 is not null and t.c1 is not null
or a.c2 is not null and t.c2 is not null
or a.c3 is not null and t.c3 is not null
then t.c2
else coalesce (a.c2, t.c2)
end as c2
, case
when a.c1 is not null and t.c1 is not null
or a.c2 is not null and t.c2 is not null
or a.c3 is not null and t.c3 is not null
then t.c3
else coalesce (a.c3, t.c3)
end as c3
from a, mytab2 t
where t.id = a.id and t.name = a.name and t.rn_ = a.rn_ + 1
)
select id, name
, max (c1) as c1
, max (c2) as c2
, max (c3) as c3
from a
group by id, name, grp
I have two tables:
TableA which contains description of all products :
codeProduct description
1 ok
2 yes
TableB contains hierarchy between products only with codes :
level_1 level_2 level_3 level_4
1 2 23 75
1 2 53 85
How could I get a final table that contains for each level the description
level_1 description_1 level_2 description_2 level_3 description_3 level_4 description_4
You need use of tableA several time each time you need a value for a column in tableB
select b.level_1
, a1.description description_1
, b.level_2
, a2.description description_2
, b.level_3
, a3.description description_3
, b.level_4
, a4.description description_4
from TableB b
left join TableA a1 on a1.codeProduct = b.level_1
left join TableA a2 on a2.codeProduct = b.level_2
left join TableA a3 on a3.codeProduct = b.level_3
left join TableA a4 on a4.codeProduct = b.level_4
use left join if you not all matching value between the two tables or INNER JOIN if you have all the macthing values
select b.level_1
, a1.description description_1
, b.level_2
, a2.description description_2
, b.level_3
, a3.description description_3
, b.level_4
, a4.description description_4
from TableB b
INNER join TableA a1 on a1.codeProduct = b.level_1
INNER join TableA a2 on a2.codeProduct = b.level_2
INNER join TableA a3 on a3.codeProduct = b.level_3
INNER join TableA a4 on a4.codeProduct = b.level_4
Try this:
CREATE TABLE #TABLEA
(
CODEPRODUCT INT NOT NULL
, DESCRIPTION VARCHAR (100) NOT NULL
);
INSERT INTO #TABLEA VALUES (1, 'ok') ;
INSERT INTO #TABLEA VALUES (2, 'yes');
CREATE TABLE #TABLEB
(
LEVEL_1 INT NOT NULL
, LEVEL_2 INT NOT NULL
, LEVEL_3 INT NOT NULL
, LEVEL_4 INT NOT NULL
);
INSERT INTO #TABLEB VALUES(1, 2, 23, 75) , (1, 2, 53, 85);
SELECT #TABLEB.LEVEL_1
, TA_L1.DESCRIPTION AS DESCRIPTION1
, #TABLEB.LEVEL_2
, TA_L2.DESCRIPTION AS DESCRIPTION2
, #TABLEB.LEVEL_3
, TA_L3.DESCRIPTION AS DESCRIPTION3
, #TABLEB.LEVEL_4
, TA_L4.DESCRIPTION AS DESCRIPTION4
FROM #TABLEB
LEFT JOIN #TABLEA TA_L1
ON #TABLEB.LEVEL_1 = TA_L1.CODEPRODUCT
LEFT JOIN #TABLEA TA_L2
ON #TABLEB.LEVEL_2 = TA_L2.CODEPRODUCT
LEFT JOIN #TABLEA TA_L3
ON #TABLEB.LEVEL_3 = TA_L3.CODEPRODUCT
LEFT JOIN #TABLEA TA_L4
ON #TABLEB.LEVEL_4 = TA_L4.CODEPRODUCT;
Result:
LEVEL_1, DESCRIPTION1, LEVEL_2, DESCRIPTION2, LEVEL_3, DESCRIPTION3, LEVEL_4, DESCRIPTION4
1 ok 2 yes 23 NULL 75 NULL
1 ok 2 yes 53 NULL 85 NULL
I am using Oracle database and I am trying to combine a basic Order By and a custom one in one of my query.
Here's my table :
table1
-----------------
C1 | C2 | C3 | C4
I am trying to order it like that :
SELECT C1,C2,C3,C4 FROM table1
ORDER BY C1, C2, C3, (
CASE C4
WHEN C4 = 'value1' THEN 1
WHEN C4 = 'value2' THEN 2
WHEN C4 = 'value3' THEN 3
END
)
But I'm getting "Missing keyword" and I can't find which one, any ideas?
You can try
SELECT C1,C2,C3,C4 FROM table1
ORDER BY C1, C2, C3, (
CASE
WHEN C4 = 'value1' THEN 1
WHEN C4 = 'value2' THEN 2
WHEN C4 = 'value3' THEN 3
END
)
OR
SELECT C1,C2,C3,C4 FROM table1
ORDER BY C1, C2, C3, (
CASE C4
WHEN 'value1' THEN 1
WHEN 'value2' THEN 2
WHEN 'value3' THEN 3
END
)
I have a requirement in my project that I have this data with me:
C1 | C2 | C3 | C4
A | B | 2 | X
A | B | 3 | Y
C | D | 4 | Q
C | D | 1 | P
Where C1, C2, C3 and C4 are columns name in Database
And I have need to show data like this
C1 | C2 | C3 | C4
A | B | 5 | X
C | D | 5 | Q
The answer to this is fairly simple. Just follow my solution below:
--CREATE THE SAMPLE TABLE
CREATE TABLE TABLE1 (C1 char(1) NULL, C2 char(1) NULL, C3 int NULL, C4 char(1) NULL);
GO
--INSERT THE SAMPLE VALUES
INSERT INTO TABLE1 VALUES ('A', 'B', 2, 'X'), ('A', 'B', 3, 'Y'), ('C', 'D', 4, 'Q'), ('C','D', 1, 'P');
GO
--SELECT SUM(C3) AND GROUP BY ONLY C1 AND C2, THEN SELECT TOP 1 ONLY FROM C4
SELECT
C1,
C2,
SUM(C3) AS C3,
(SELECT TOP(1) C4 FROM TABLE1 AS B WHERE A.C1 = B.C1) AS C4
FROM
TABLE1 AS A
GROUP BY
C1,
C2;
GO
--CLEAN UP THE DATABASE, DROP THE SAMPLE TABLE
IF EXISTS(SELECT name FROM sys.tables WHERE object_id = OBJECT_ID(N'TABLE1')) DROP TABLE TABLE1;
GO
Let me know if this helps.
Assuming you mean the first record ordered by c4 (grouped by c1 and c2), then this will work establishing a row_number and using max with case:
with cte as (
select *,
row_number() over (partition by c1, c2 order by c4) rn
from yourtable
)
select c1, c2, sum(c3), max(case when rn = 1 then c4 end) c4
from cte
group by c1, c2
SQL Fiddle Demo
However, if you don't want to order by c4, then you need some other column to ensure the correct order of the results. Without an order by clause, there's no guarantee on how they are returned.
I hope you choose 'X' and 'Q' as those rows where inserted first, while grouping C1 and C2.
I would suggest you to add an identity column in your table and work based on it as given below.
Table:
DECLARE #DB TABLE (ID INT IDENTITY(1,1),C1 VARCHAR(10),C2 VARCHAR(10),C3 INT,C4 VARCHAR(10))
INSERT INTO #DB VALUES
('A','B',2,'X'),
('A','B',3,'Y'),
('C','D',4,'Q'),
('C','D',1,'P')
Code:
SELECT A.*,B.C4
FROM (
SELECT C1,C2,SUM(C3) C3 FROM #DB
GROUP BY C1,C2) A
JOIN
(
SELECT C1,C2,C4 FROM (
SELECT *,ROW_NUMBER() OVER (PARTITION BY C1,C2 ORDER BY ID) [ROW]
FROM #DB) LU WHERE LU.ROW = 1) B
ON A.C1 = B.C1 AND A.C2 = B.C2
Result:
My Oracle statement has two parts:
Select statement 1 is returning rows as:
a b c NULL
a x y NULL
Select statement 2 is returning rows as:
a b c d
e f g h
I want to union both the selects provided for a row having same columns(except NULL column) as in select 2 , only the not NULL row is returned.
Output:
a b c d
a x y NULL
e f g h
CHANGED REQUIREMENTS:
The requirements are bit changed now and i have case like:
Select statement 1 as:
a b c e NULL
a x y s NULL
Select statement 2 as:
a b c d text
e f g h text
Output:
a b c d text
a x y s NULL
e f g h text
I.e. in case of NULL field in last column, I need to fetch the row from "Select statement 2".
Considering that first three columns are not nullable you can use FULL OUTER JOIN:
with t1 as (
select 'a' c1, 'b' c2, 'c' c3, null c4 from dual
union all
select 'a', 'x', 'y', null from dual),
t2 as (
select 'a' c1, 'b' c2, 'c' c3, 'd' c4 from dual
union all
select 'e', 'f', 'g', 'h' from dual)
select c1, c2, c3, coalesce(t1.c4, t2.c4) c4
from t1 full outer join t2 using(c1, c2, c3);
C1 C2 C3 C4
-- -- -- --
a b c d
e f g h
a x y (NULL)
According to updated requirements:
with t1(c1, c2, c3, c4, c5) as (
select 'a', 'b', 'c', 'e', null from dual
union all
select 'a', 'x', 'y', 's', null from dual),
t2(c1, c2, c3, c4, c5) as (
select 'a', 'b', 'c', 'd', 'qwerty' from dual
union all
select 'e', 'f', 'g', 'h', 'asdfgh' from dual)
select c1,
c2,
c3,
nvl(nvl2(t1.c5, t1.c4, t2.c4), t1.c4) c4,
coalesce(t1.c5, t2.c5) c5
from t1
full outer join t2
using (c1, c2, c3);
C1 C2 C3 C4 C5
-- -- -- -- ------
a b c d qwerty
e f g h asdfgh
a x y s (NULL)
This is fast and dirty hack. Although it works on sample data you provided, it might behave unpredictable on your full dataset. This just gives you an idea how to accomplish your goal. I strongly recommend you to test it thoroughly before use.
Suppose that we got TABLE1 and TABLE2, in this query TAB1 returns same rows in tables and remove the null columns from that rows and TAB2 returns all rows from TABLE1 with out the rows which are the same with the rows in TABLE2 and TAB3 is same TAB2 but the tables changed, and finally UNION ALL TAB1 and TAB2 and TAB3:
SELECT * FROM
(SELECT
CASE WHEN T1.COL1 IS NULL THEN T2.COL1 ELSE T1.COL1 END COL1,
CASE WHEN T1.COL2 IS NULL THEN T2.COL2 ELSE T1.COL2 END COL2,
CASE WHEN T1.COL3 IS NULL THEN T2.COL3 ELSE T1.COL3 END COL3,
CASE WHEN T1.COL4 IS NULL THEN T2.COL4 ELSE T1.COL4 END COL4
FROM
TABLE2 T2 INNER JOIN TABLE1 T1 ON
(T1.COL1 = T2.COL1 OR T1.COL1 IS NULL OR T2.COL1 IS NULL) AND
(T1.COL2 = T2.COL2 OR T1.COL2 IS NULL OR T2.COL2 IS NULL) AND
(T1.COL3 = T2.COL3 OR T1.COL3 IS NULL OR T2.COL3 IS NULL) AND
(T1.COL4 = T2.COL4 OR T1.COL4 IS NULL OR T2.COL4 IS NULL))TAB1
UNION ALL
SELECT * FROM
(SELECT * FROM TABLE1
MINUS
SELECT T1.*
FROM
TABLE2 T2 INNER JOIN TABLE1 T1 ON
(T1.COL1 = T2.COL1 OR T1.COL1 IS NULL OR T2.COL1 IS NULL) AND
(T1.COL2 = T2.COL2 OR T1.COL2 IS NULL OR T2.COL2 IS NULL) AND
(T1.COL3 = T2.COL3 OR T1.COL3 IS NULL OR T2.COL3 IS NULL) AND
(T1.COL4 = T2.COL4 OR T1.COL4 IS NULL OR T2.COL4 IS NULL))TAB2
UNION ALL
SELECT * FROM
(SELECT * FROM TABLE2
MINUS
SELECT T2.*
FROM
TABLE2 T2 INNER JOIN TABLE1 T1 ON
(T1.COL1 = T2.COL1 OR T1.COL1 IS NULL OR T2.COL1 IS NULL) AND
(T1.COL2 = T2.COL2 OR T1.COL2 IS NULL OR T2.COL2 IS NULL) AND
(T1.COL3 = T2.COL3 OR T1.COL3 IS NULL OR T2.COL3 IS NULL) AND
(T1.COL4 = T2.COL4 OR T1.COL4 IS NULL OR T2.COL4 IS NULL))TAB3;
SQL Fiddle1
SQL Fiddle2