Display record even if it doesn't exist - sql

So I have this table
Col1 Col2 Col3
A 34 X
B 43 L
A 36 L
Now if I query
select * from Table1 where col1 in ('A','B','C')
I am expecting something like
Col1 Col2 Col3
A 34 X
B 43 L
A 36 L
C - -
Is it possible ?
P.S: the - in row C are just to show that the column is empty.

You could create a nested table schema object type:
create type T_List1 as table of varchar2(100);
And then construct your query as follows:
select s.column_value as col1
, nvl(to_char(t.col2), '-') as col2
, nvl(col3, '-') as col3
from Table1 t
right join table(T_List1('A', 'B', 'C')) s
on (t.col1 = s.column_value)
Example:
-- sample of data from your question
with Table1(Col1, Col2, Col3) as(
select 'A', 34, 'X' from dual union all
select 'B', 43, 'L' from dual union all
select 'A', 36, 'L' from dual
) -- actual query
select s.column_value as col1
, nvl(to_char(t.col2), '-') as col2
, nvl(col3, '-') as col3
from Table1 t
right join table(T_List1('A', 'B', 'C')) s --< here list your values
on (t.col1 = s.column_value) -- as you would using `IN` clause
Result:
COL1 COL2 COL3
------------------------
A 36 L
A 34 X
B 43 L
C - -
SQLFiddle Demo

To do this you can use a driver table that has all the values you want returned in it ie:
col1
A
B
C
D
E
Then LEFT JOIN to your table.
SELECT *
FROM driver d
LEFT JOIN Table1 t
ON d.col1 = t.col1
WHERE d.col1 in ('A','B','C')

If you don't want to create an extra nested table type as in Nicholas Krasnov's answer or don't want to create a separate temporary table with A,B,C rows then just create a driving table with with clause:
with driving_table(col) AS
(
select 'A' from dual
union
select 'B' from dual
union
select 'C' from dual
)
select dt.col as col1
, nvl(to_char(t.col2), '-') as col2
, nvl(col3, '-') as col3
from Table1 t
right join driving_table dt
on (t.col1 = dt.col)
http://sqlfiddle.com/#!4/112ef/2

Related

Using a SQL join to combine data rows from 4 tables without repeating row id's as depicted in the desired results table shown below

Table A
Table B
Table C
Table D
Desired results
Thank you in advance for any assistance provided to achieve the desired results in this case. My goal is to stitch multiple tables together in this manner assuming my database is not able to support the use os a FULL JOIN Statement.
You can emulate a full join with union all and conditional aggregation:
select
id,
coalesce(max(case when idx = 1 then col end), '0') col1,
coalesce(max(case when idx = 2 then col end), '0') col2,
coalesce(max(case when idx = 3 then col end), '0') col3,
coalesce(max(case when idx = 4 then col end), '0') col4
from (
select id, 1 idx, col1 col from table1
union all select id, 2, col2 from table2
union all select id, 3, col3 from table3
union all select id, 4, col4 from table4
) t
group by id
order by id
UNION ALL the tables, put each column in right position. GROUP BY the result.
select id, max(col1), max(col2), max(col3), max(col4)
from
(
select id, col1, null as col2, null as col3, null as col4 from tableA
union all
select id, null, col2, null, null from tableB
union all
select id, null, null, col3, null from tableC
union all
select id, null, null, null, col4 from tableD
) dt
group by id
If you need '0' in the result instead of the null values, use COALESCE()
select id, coalesce(max(col1),'0'), ...
You can use UNION ALL to get 1 result set that has all 4 combined.
Then from that you can group by the ID.
And use conditional aggregation to pivot the COL values on the table sources.
SELECT ID
, MAX(CASE WHEN Src = 1 THEN Col ELSE '0' END) AS COL1
, MAX(CASE WHEN Src = 2 THEN Col ELSE '0' END) AS COL2
, MAX(CASE WHEN Src = 3 THEN Col ELSE '0' END) AS COL3
, MAX(CASE WHEN Src = 4 THEN Col ELSE '0' END) AS COL4
FROM
(
SELECT ID, 1 AS Src, COL1 AS Col
FROM TableA
UNION ALL
SELECT ID, 2, COL2
FROM TableB
UNION ALL
SELECT ID, 3, COL3
FROM TableC
UNION ALL
SELECT ID, 4, COL4
FROM TableD
) q
GROUP BY ID
ORDER BY ID
This will do it.
Select
coalesce(Col1, '0') as Col1,
coalesce(Col2, '0') as Col2,
coalesce(Col3, '0') as Col3,
coalesce(Col4, '0') as Col4
(
select ID from TableA union
select ID from TableB union
select ID from TableC union
select ID from TableD
) TbLID
left join TableA on TableA.ID = TbLID.ID
left join TableB on TableB.ID = TbLID.ID
left join TableC on TableC.ID = TbLID.ID
left join TableD on TableD.ID = TbLID.ID

How can I aggregate the columns with other data?

As a part of output from my joins in SP is like :
Col1 col2
A 1
B 1
C 2
C 1
I have another table that has all the possible values in Col1 (A,B,C,D,E,F) [The values are not known and as best practice I don't want to hardcode these values. ]
Expected output in SQL is as shown below
Col1 1 2
A Yes No/Null
B Yes No
C Yes Yes
D No No
E No No
F No No
Appreciate the help I have relatively less experience in SQL and would appreciate if someone could help me understand how I can achieve this.
You can do JOIN with conditional aggregation :
with cte as (
< query >
)
select c.col1,
max(case when t1.col2 = 1 then 'Yes' end),
max(case when t1.col2 = 2 then 'Yes' end)
from cte c LEFT JOIN
table t1
on t1.col1 = c.col1
group by c.col1;
Following SQL pivot query can be used if you can use '1/0' instead of 'YES/NO'
select
*
from (
select
letters.col1, mydata.col2
from (
select 'A' as col1 union all select 'B' union all select 'C' union all select 'D' union all select 'E' union all select 'F'
) letters
left join (
select 'A' as col1, 1 as col2 union all
select 'B' as col1, 1 as col2 union all
select 'C' as col1, 2 as col2 union all
select 'C' as col1, 1 as col2
) mydata
on mydata.col1 = letters.col1
) data
PIVOT (
count( col2 )
FOR col2
IN (
[1],[2]
)
) PivotTable

Select distinct values based on multiple column from table

I am having below dummy table
select * from (
select 'A' as col1, 'B' as col2 from dual
union
select 'B' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'C' as col2 from dual
union
select 'C' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'D' as col2 from dual
)a
which will give output as below
col1 col2
A B
A C
A D
B A
C A
I wants to find the distinct values from that table like below
col1 col2
A B
A C
A D
first row can be A B or B A same as second can be A C or C A
Is it possible??
We got the solution for above problem which is below
select distinct least(col1, col2), greatest(col1, col2)
from the_table;
but if there is more than 2 column, then i wouldn't work
Let us assume the below scenario
Input
col1 col2 col3
A B E
A C E
A D E
B A F
C A E
Output
col1 col2 col3
A B E
A D E
B A F
C A E
then what would be the possible solution ?
Here is one method:
select col1, col2
from t
where col1 <= col2
union all
select col1, col2
from t
where col1 > col2 and
not exists (select 1 from t t2 where t2.col1 = t.col2 and t2.col2 = t.col1);
The following will work for Oracle and Postgres:
select distinct least(col1, col2), greatest(col1, col2)
from the_table;
Online example: http://rextester.com/BZXC69735
select DISTINCT * from (
select 'A' as col1, 'B' as col2 from dual
union
select 'B' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'C' as col2 from dual
union
select 'C' as col1, 'A' as col2 from dual
union
select 'A' as col1, 'D' as col2 from dual
)a
select col1, col2 from t where col1 <= col2
union
select col2, col1 from t where col1 > col2

Apply the distinct on 2 fields and also fetch the unique data for each columns

According to some weird requirement, i need to select the record where all the output values in both the columns should be unique.
Input looks like this:
col1 col2
1 x
1 y
2 x
2 y
3 x
3 y
3 z
Expected Output is:
col1 col2
1 x
2 y
3 z
or
col1 col2
1 y
2 x
3 z
I tried applying the distinct on 2 fields but that returns all the records as overall they are distinct on both the fields. What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Please let me know if this is even possible and if yes, how to go about it.
Great problem! Armunin has picked up on the deeper structural issue here, this is a recursive enumerable problem description and can only be resolved with a recursive solution - base relational operators (join/union/etc) are not going to get you there. As Armunin cited, one approach is to bring out the PL/SQL, and though I haven't checked it in detail, I'd assume the PL/SQL code will work just fine. However, Oracle is kind enough to support recursive SQL, through which we can build the solution in just SQL:
-- Note - this SQL will generate every solution - you will need to filter for SOLUTION_NUMBER=1 at the end
with t as (
select 1 col1, 'x' col2 from dual union all
select 1 col1, 'y' col2 from dual union all
select 2 col1, 'x' col2 from dual union all
select 2 col1, 'y' col2 from dual union all
select 3 col1, 'x' col2 from dual union all
select 3 col1, 'y' col2 from dual union all
select 3 col1, 'z' col2 from dual
),
t0 as
(select t.*,
row_number() over (order by col1) id,
dense_rank() over (order by col2) c2_rnk
from t),
-- recursive step...
t1 (c2_rnk,ids, str) as
(-- base row
select c2_rnk, '('||id||')' ids, '('||col1||')' str
from t0
where c2_rnk=1
union all
-- induction
select t0.c2_rnk, ids||'('||t0.id||')' ids, str||','||'('||t0.col1||')'
from t1, t0
where t0.c2_rnk = t1.c2_rnk+1
and instr(t1.str,'('||t0.col1||')') =0
),
t2 as
(select t1.*,
rownum solution_number
from t1
where c2_rnk = (select max(c2_rnk) from t1)
)
select solution_number, col1, col2
from t0, t2
where instr(t2.ids,'('||t0.id||')') <> 0
order by 1,2,3
SOLUTION_NUMBER COL1 COL2
1 1 x
1 2 y
1 3 z
2 1 y
2 2 x
2 3 z
You can use a full outer join to merge two numbered lists together:
SELECT col1, col2
FROM ( SELECT col1, ROW_NUMBER() OVER ( ORDER BY col1 ) col1_num
FROM your_table
GROUP BY col1 )
FULL JOIN
( SELECT col2, ROW_NUMBER() OVER ( ORDER BY col2 ) col2_num
FROM your_table
GROUP BY col2 )
ON col1_num = col2_num
Change ORDER BY if you require a different order and use ORDER BY NULL if you're happy to let Oracle decide.
What would be the result if another row of
col1 value as 1 and col2 value as xx ?
A single row is better in this case:
SELECT DISTINCT TO_CHAR(col1) FROM your_table
UNION ALL
SELECT DISTINCT col2 FROM your_table;
My suggestion is something like this:
begin
EXECUTE IMMEDIATE 'CREATE global TEMPORARY TABLE tmp(col1 NUMBER, col2 VARCHAR2(50))';
end;
/
DECLARE
cur_print sys_refcursor;
col1 NUMBER;
col2 VARCHAR(50);
CURSOR cur_dist
IS
SELECT DISTINCT
col1
FROM
ttable;
filtered sys_refcursor;
BEGIN
FOR rec IN cur_dist
LOOP
INSERT INTO tmp
SELECT
col1,
col2
FROM
ttable t1
WHERE
t1.col1 = rec.col1
AND t1.col2 NOT IN
(
SELECT
tmp.col2
FROM
tmp
)
AND t1.col1 NOT IN
(
SELECT
tmp.col1
FROM
tmp
)
AND ROWNUM = 1;
END LOOP;
FOR rec in (select col1, col2 from tmp) LOOP
DBMS_OUTPUT.PUT_LINE('col1: ' || rec.col1 || '|| col2: ' || rec.col2);
END LOOP;
EXECUTE IMMEDIATE 'DROP TABLE tmp';
END;
/
May still need some refining, I am especially not happy with the ROWNUM = 1 part.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tbl ( col1, col2 ) AS
SELECT 1, 'x' FROM DUAL
UNION ALL SELECT 1, 'y' FROM DUAL
UNION ALL SELECT 2, 'x' FROM DUAL
UNION ALL SELECT 2, 'y' FROM DUAL
UNION ALL SELECT 3, 'x' FROM DUAL
UNION ALL SELECT 3, 'y' FROM DUAL
UNION ALL SELECT 4, 'z' FROM DUAL;
Query 1:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)
Results:
| COL1 | COL2 |
|------|--------|
| 1 | x |
| 2 | y |
| 3 | z |
| 4 | (null) |
And to address the additional requirement:
What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Query 2:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
WHERE col2 NOT IN ( SELECT TO_CHAR( col1 ) FROM c1 )
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)

SQL select rows with only a certain value in them

I have a table as such
Col 1 Col 2 Col 3
1 A 1
2 A 2
3 B 1
4 C 1
5 C 2
6 D 1
How do I only get unique rows which have Col 3 = 1?
I want to get rows 3 and 6 (Col 2 = B and D respectively). I do not want A nor C since they have Col 3 = 2 as well.
I've tried something along the lines of:
select col 2 from table group by col 2 having count(col 3) = 1
But that only brings up Col 2 for results so I'm uncertain if Col 3 contents = 1 or not.
EDIT: Sorry guys maybe I've not worded my question clearly. I want to get all of the rows of Col 2 which contain only Col 3 = 1 AND ONLY 1.
So if I tried WHERE Col 3= 1, it would return 4 rows because A has 1. But since A also has a row where Col 3 = 2, I do not want that, same for C. From this example table, I would want the end result to only show 2 rows, B and D.
My example table is an example, I actually have about 5000 rows to filter through, otherwise I'd do as you guys have suggested :)
SELECT col2
FROM your_table
GROUP BY col2
HAVING MAX(col3) = 1 AND MIN(Col3) = 1
Or
SELECT a.col2
FROM your_table a
WHERE a.col3=1 AND NOT EXISTS(SELECT *
FROM your_table b
WHERE a.col2=b.col2 AND b.col3<>1)
What you are probably looking for is WHERE clause.
SELECT * FROM YouTable WHERE col3 = 1 AND col2 in ('B','D');
;with T ([Col 1], [Col 2], [Col 3]) as
(
select 1, 'A', 1 union all
select 2, 'A', 2 union all
select 3, 'B', 1 union all
select 4, 'C', 1 union all
select 5, 'C', 2 union all
select 6, 'D', 1
)
select *
from T
left outer join
(
select distinct [Col 2]
from T
where [Col 3] <> 1
) as T2
on T.[Col 2] = T2.[Col 2]
where T.[Col 3] = 1 and
T2.[Col 2] is null
It's a bit hard to know exactly what you're trying to get, but this is my best guess:
SELECT * FROM theTable WHERE col2 NOT IN
(SELECT col2 FROM theTable WHERE col3 <> 1)
SELECT * FROM #temp t1
WHERE EXISTS
(
select Col2 from #Temp t2
WHERE t2.Col2 = t1.Col2
group by col2
having count(col3) = 1
)
tested with MS SQL2008 and the following (so if my answer is not the correct one it may halp others test theirs...):
CREATE TABLE #temp
(
Col1 INT,
Col2 CHAR(1),
Col3 INT
)
INSERT INTO #Temp
(Col1, Col2, Col3)
SELECT 1,'A',1
UNION
SELECT 2,'A',2
UNION
SELECT 3,'B', 1
UNION
SELECT 4,'C',1
UNION
SELECT 5,'C',2
UNION
SELECT 6,'D',1
SELECT * FROM #temp t1
WHERE EXISTS
(
select Col2 from #temp t2
WHERE t2.Col2 = t1.Col2
group by col2
having count(col3) = 1
)
DROP TABLE #temp