How to write the below SQL queries? - sql

Consider 3 columns like below. I need to get columns having same col1 with col2 values as 1 and 2 and col3 have 2 different values for col2 where col1 will be same for col2 and col3
Col1 Col2 Col3
A 1 IND
A 2 IND
A 3 IND
B 1 IND
B 2 PAK
B 3 IND
B 4 IND
C 1 IND
C 2 IND
C 3 PAK
C 4 PAK
D 1 IND
D 2 PAK
E 1 PAK
E 2 SA
The result will be as given below
Col1 Col2 Col3
B 1 IND
B 2 PAK
D 1 IND
D 2 PAK
E 1 PAK
E 2 SA
Here is what I tried for col1 and col2:
select col1 from your_table
where col2 in (1,2)
group by col1
having count(distinct col2) > 1

The rows with 3 and 4 in Col2 can be ignored as per your request so
Self Join on col1
Filter to col2 in (1, 2)
col3 is different
Like this
SELECT
*
FROM
MyTable M1
JOIN
MyTable M2 ON M1.COl1 = M2.COl1
WHERE
M1.Col2 IN (1, 2)
AND
M2.Col2 IN (1, 2)
AND
M2.Col3 <> M1.COl3

One approach to this is aggregation. The following gets the column A values that meet your conditions:
select col1
from t
where col2 in (1, 2)
group by col1
having min(col3) <> max(col3);
If you want to get the original rows, there are multiple ways. Apart from obvious ones (such as in, exists, and join), you can use window functions:
select col1, col2, col3
from (select t.*,
min(col3) over (partition by col1) as mincol3,
max(col3) over (partition by col1) as maxcol3
from t
where col2 in (1, 2)
) t
where mincol3 <> maxcol3;

Related

Oracle self join starting with minimum value for each partition

I have this table:
COL1 COL2 COL3
--------------------
A 1 VAL1
A 2 VAL2
A 4 VAL3
B 2 VAL4
B 4 VAL5
B 5 VAL6
And I would like to obtain this output:
COL1 COL2 COL3
--------------------
A 1 VAL1
A 2 VAL2
A 3 NULL
B 2 VAL4
B 3 NULL
B 4 VAL6
Logic:
with the smallest COL2 value for each partition of COL1, take the following 3 numbers and, if the combination COL1 and COL2 present in the first table, show COL3 and NULL otherwise.
Your question is a good example of what PARTITIONED OUTER JOIN was created for: DBFiddle
with top3 as (
select *
from (
select
col1, col2, col3
,min(col2)over(partition by col1) min_col2
,col2 - min(col2)over(partition by col1) + 1 as rn
from t
)
where col2 < min_col2 + 3
)
select
top3.col1
,r3.n as col2
,top3.col3
from
top3
partition by (col1)
right join
(select level n from dual connect by level<=3) r3
on r3.n=top3.rn;
As you can see, the first step is to get top3 and then just use partition by (col1) right join r3, where r3 is just generator of 3 rows.
Results:
COL1 COL2 COL3
----- ---------- ----
A 1 VAL1
A 2 VAL2
A 3
B 1 VAL4
B 2
B 3 VAL5
6 rows selected.
Note, this approach allows you to scan your table just once!
Let's see. Here is the table
select * from t order by col1, col2;
COL1 COL2 COL3
----- ---------- -----
A 1 VAL1
A 2 VAL2
A 4 VAL3
B 2 VAL4
B 4 VAL5
B 5 VAL6
6 rows selected
and now let's try to apply the described logic
with offsets as
(select level - 1 offset from dual connect by level <= 3),
smallest_col2 as
(select col1, min(col2) min_col2 from t group by col1)
select sc2.col1, sc2.min_col2 + o.offset col2, t.col3
from smallest_col2 sc2
cross join offsets o
left join t
on t.col1 = sc2.col1
and t.col2 = sc2.min_col2 + o.offset
order by 1, 2;
COL1 COL2 COL3
----- ---------- -----
A 1 VAL1
A 2 VAL2
A 3
B 2 VAL4
B 3
B 4 VAL5
6 rows selected
Use a recursive CTE to get the COL2s from the min of each COL1 up to the next 2 and then a left join to the table:
WITH cte(COL1, COL2, max_col2) AS (
SELECT COL1, MIN(COL2), MIN(COL2) + 2
FROM tablename
GROUP BY COL1
UNION ALL
SELECT COL1, COL2 + 1, max_col2
FROM cte
WHERE COL2 < max_col2
)
SELECT c.COL1, c.COL2, t.COL3
FROM cte c LEFT JOIN tablename t
ON t.COL1 = c.COL1 AND t.COL2 = c.COL2
ORDER BY c.COL1, c.COL2
See the demo.
The partitioned outer join, already demonstrated in Sayan's answer, is probably the best approach for that part of the assignment (data densification).
For the first part, in Oracle 12.1 and higher you can use the match_recognize clause:
select col1, col2, col3
from this_table
match_recognize(
partition by col1
order by col2
measures col2 - a.col2 + 1 as rn
all rows per match
pattern ( ^ a b* )
define b as col2 <= a.col2 + 2
)
partition by (col1)
right outer join
(select level as rn from dual connect by level <= 3) using (rn)
;
Another solution with the "recursive WITH clause"
With rws_numbered (COL1, COL2, COL3, rn) as (
select COL1, COL2, COL3
, row_number()over(order by col1, col3) rn
from Your_table
)
, cte ( COL1, COL2, COL3, rn ) as (
select COL1, COL2, COL3, rn
from rws_numbered
where rn = 1
union all
select
t.COL1
, case when t.col1 = c.col1 then c.col2 + 1 else t.col2 end COL2
, t.COL3
, t.rn
from rws_numbered t
join cte c
on c.rn + 1 = t.rn
)
select COL1, COL2, case when exists (select null from Your_table t where t.COL1 = cte.COL1 and t.COL2 = cte.COL2) then COL3 else null end COL3
from cte
order by 1, 2
;
db<>fiddle

UNPIVOT - DB2 SQL

I have data like below
ROW_ID Col0 Col1 Col2 Col3
1 05/22/2020 123 ABC 1
2 05/12/2020 DEF 1 2
3 06/13/2020 PRR N1 4
I am looking for the output where data will transformed very little and then will be un-pivoted as shown below
ROW_ID COLUMN_NAME VALUE
1 Col0 05/22/2020
1 Drv_Col0 May-2020
1 Col1 123
1 Col2 ABC
1 Col3 1
1 Sum_Col3 3
2 Col0 05/12/2020
2 Drv_Col0 May-2020
2 Col1 DEF
2 Col2 1
2 Col3 2
2 Sum_Col3 3
3 Col0 06/13/2020
3 Drv_Col0 Jun-2020
3 Col1 PRR
3 Col2 N1
3 Col3 4
3 Sum_Col3 4
You can use a lateral join. Assuming that the columns all have the same type:
select t.row_id, v.*
from t cross join lateral
(values ('Col0', col0),
('Drv_Col0', to_char(col0, 'MON-YYYY'),
('Col1', col1),
('Col2', col2),
('Col3', col3),
('Sum_Col3', ???)
) v(column_name, value);
Note: You may need to cast the columns so they are all strings.
The question does not specify how sum_col3 is defined and the definition is not obvious. But some expression can go there.

Filter in SQL on distinct values after grouping

I have a dataset like
col1 col2 col3
A x 1
A x 2
A x 3
B y 4
B -y 5
B y 6
C -z 7
C z 8
C -z 9
D t 10
D t 11
D t 12
how can i pick out just the groups from col1 that have distinct values in col2? So A,D in this case.
something like
select * from table t1
where (select count(distinct col2)
from table t2
where t1.col1 = t2.col1) > 1
but more optimized?
If all you need is the column col1 you can group by col1 and set the condition in the HAVING clause:
SELECT col1
FROM tablename
GROUP BY col1
HAVING COUNT(DISTINCT col2) = 1;
If you want all the rows from the table use the above query with the operator IN:
SELECT *
FROM tablename
WHERE col1 IN (
SELECT col1
FROM tablename
GROUP BY col1
HAVING COUNT(DISTINCT col2) = 1
)
You can use group by and having:
select col1
from t
group by col1
having min(col2) <> max(col2);

Sqlite insert both even and odd rows in one expression

I am using sqlite3 and I have a sqlite table which has somewhat duplicated/overlapping columns. To illustrate:
No Col1 Col2 Col3 Col4
row1 1 1 1 2 2
row2 2 1 1 3 3
row3 3 2 2 4 4
row4 4 2 2 5 5
Col1 and Col2 stores the same information, however, Col3 and Col4 has different information.
I want to condense the rows into one row like this:
No Col1 Col2 Col3 Col4 Col3.2 Col4.2
row1 1 1 1 2 2 3 3
row3 3 2 2 4 4 5 5
I have created a new table with the columns, and was able to select the odd rows.
INSERT INTO [Table] ( No, Col1, Col2, Col3, Col4
)
SELECT No, Col1, Col2, Col3, Col4
FROM [Table]
WHERE ([No] % 2) = 1
ORDER BY [No];
The result table would be something like:
No Col1 Col2 Col3 Col4 Col3.2 Col4.2
row1 1 1 1 2 2 null null
row3 3 2 2 4 4 null null
Now I am not sure how to insert the even values into the new table. Using similar expressions only insert more rows. Is it possible to do this INSERT INTO expression in one sentence? Or how do I update the new table?
Just join the table with itself based on the following condition. It'll even work if the No column has gaps:
SELECT o.No, o.Col1, o.Col2, o.Col3, o.Col4, e.Col3, e.Col4
FROM t AS o
INNER JOIN t AS e ON o.Col1 = e.Col1
AND o.Col2 = e.Col2
AND o.No < e.No
Use pivoting logic with aggregation:
SELECT
MIN(No) AS No,
MAX(CASE WHEN No % 2 = 1 THEN Col1 END) AS Col1,
MAX(CASE WHEN No % 2 = 1 THEN Col2 END) AS Col2,
MAX(CASE WHEN No % 2 = 1 THEN Col3 END) AS Col3,
MAX(CASE WHEN No % 2 = 1 THEN Col4 END) AS Col4,
MAX(CASE WHEN No % 2 = 0 THEN Col1 END) AS Col1_2,
MAX(CASE WHEN No % 2 = 0 THEN Col2 END) AS Col2_2,
MAX(CASE WHEN No % 2 = 0 THEN Col3 END) AS Col3_2,
MAX(CASE WHEN No % 2 = 0 THEN Col4 END) AS Col4_2
FROM yourTable
GROUP BY
(No-1) / 2;
Demo
Another approach, using window functions added in sqlite 3.25:
CREATE TABLE table2(no INTEGER PRIMARY KEY, col1, col2, col3, col4, "col3.2", "col4.2");
INSERT INTO table2
SELECT *
FROM (SELECT no, col1, col2, col3, col4, lead(col3) OVER win, lead(col4) OVER win
FROM table1
WINDOW win AS (ORDER BY no))
WHERE no % 2 = 1;
which gives
SELECT * FROM table2;
no col1 col2 col3 col4 col3.2 col4.2
---------- ---------- ---------- ---------- ---------- ---------- ----------
1 1 1 2 2 3 3
3 2 2 4 4 5 5

selection based on certain condition

select col1, col2, col3 from tab1
rownum col1 col2 col3
1 1 10 A
2 1 15 B
3 1 0 A
4 1 0 C
5 2 0 B
6 3 20 C
7 3 0 D
8 4 10 B
9 5 0 A
10 5 0 B
Output required is
col1 col2 col3
1 10 A
1 15 B
2 0 B
3 20 C
4 10 B
5 0 A
5 0 B
col1 and col2 are my lookup/joining columns columns, if col2 is having "non zero" data then I need to ignore/filter record with 0 (in above example I need to filter record rownum 3 4 and 7) If col2 is not having any data other than "non zero" in that case only select record with 0 (in above example col1 with value 1 and 5).
I m trying to write sql for this. Hope I have mentioned requirement clearly, please let me know if you need anything more from my side. Seem to have gone blank in this case.
Database - Oracle 10g
SELECT col1,
col2,
col3
FROM (SELECT col1,
col2,
col3,
sum(col2) OVER (PARTITION BY col1) sum_col2
FROM tab1)
WHERE ( ( sum_col2 <> 0
AND col2 <> 0)
OR sum_col2 = 0)
If col2 can be negative and the requirement is that the sum of col2 has "non-zero" data then the above is OK, however, if it is the requirement that any col2 value has "non-zero" data then it should be changed to:
SELECT col1,
col2,
col3
FROM (SELECT col1,
col2,
col3,
sum(abs(col2)) OVER (PARTITION BY col1) sum_col2
FROM tab1)
WHERE ( ( sum_col2 <> 0
AND col2 <> 0)
OR sum_col2 = 0)
SELECT t1.*
FROM tab1 t1
JOIN (SELECT "col1", MAX("col2") AS max2
FROM tab1
GROUP BY "col1") t2
ON t1."col1" = t2."col1"
WHERE ((max2 = 0 AND "col2" = 0)
OR
(max2 != 0 AND "col2" != 0))
ORDER BY "rownum"
DEMO