union all with queries that have a different number of columns - sql

I've run into a case where a sqlite query I'm expecting to return an error is actually succeeding and I was wondering if anyone could point out why this query is valid.
CREATE TABLE test_table(
k INTEGER,
v INTEGER
);
INSERT INTO test_table( k, v ) VALUES( 4, 5 );
SELECT * FROM(
SELECT * FROM(
SELECT k, v FROM test_table WHERE 1 = 0
)
UNION ALL
SELECT * FROM(
SELECT rowid, k, v FROM test_table
)
)
sqlfiddle of above
I would think that unioning two selects which have a different number of columns would return an error. If I remove the outermost SELECT * then I receive the error I'm expecting: SELECTs to the left and right of UNION ALL do not have the same number of result columns.

The answer to this seems to be straightforward: Yes, this is a quirk.
I'd like to demonstrate this with a short example. But beforehand, let's consult the documentation:
Two or more simple SELECT statements may be connected together to form
a compound SELECT using the UNION, UNION ALL, INTERSECT or EXCEPT
operator.
In a compound SELECT, all the constituent SELECTs must
return the same number of result columns.
So the documentations says very clearly that two SELECTs must provide the same number of columns. However, as you said, the outermost SELECT strangely avoids this 'limitation'.
Example 1
SELECT * FROM(
SELECT k, v FROM test_table
UNION ALL
SELECT k, v,rowid FROM test_table
);
Result:
k|v
4|5
4|5
The third column rowid gets simply omitted, as pointed out in the comments.
Example 2
We are only switching the order of the two select statements.
SELECT * FROM(
SELECT k, v, rowid FROM test_table
UNION ALL
SELECT k, v FROM test_table
);
Result
k|v|rowid
4|5|1
4|5|
Now, sqlite does not omit the column but add a null value.
Conclusion
This brings me to my conclusion, that sqlite simply handles the UNION ALL differently if it is processed as a subquery.
PS: If you are just using UNION it fails at any scenario.

UNION ALL will return the results with null values in the extra columns.
A basic UNION will fail because UNION without the ALL has to have the same number of columns from both tables.
So:
SELECT column1, column2 FROM table a
UNION ALL
SELECT column1, column2, column3 FROM table b
returns 3 columns with nulls in column 3.
and:
SELECT column1, column2 FROM table a
UNION
SELECT column1, column2, column3 FROM table b
should fail because the number of columns do not match.
In conclusion you could add a blank column to the UNION so that you are selecting 3 columns from each table and it would still work.
EX:
SELECT column1, column2, '' AS column3 FROM table a
UNION
SELECT column1, column2, column3 FROM table b

If your second query has less number of columns, you can do this:
select col1, col2, col3, col4, col5
from table A
union all
select col1, col2, col3, col4, NULL as col5,
from table B
Instead of NULL, one can also use some string constant - 'KPI' as col5.

Related

Insert multiple rows into a table from two different tables in oracle?

I'm trying to insert multiple rows into my table using select but I'm getting not enough values error.
My query:
Insert into c(x, y) select * from a union all select * from d;
table a and b contains 2 records each and table c has one record.
try like below by specifying both column names
Insert into c(x, y)
select col1,col2 from a
union all
select col1,col2 from d
for union all both tables have the same number of colums and their data type also need to be same
List the columns explicitly:
Insert into c (x, y)
select col1, col2
from a
union all
select col1, col2
from d;
If one of tables has only one column, then use a placeholder for the value:
Insert into c (x, y)
select col1, col2
from a
union all
select col1, NULL
from d;

How to create table with multiple rows and columns using only SELECT clause (i.e. using SELECT without FROM clause)

I know that in SQL Server, one can use SELECT clause without FROM clause, and create a table with one row and one column
SELECT 1 AS n;
But I was just wondering, is it possible to use SELECT clause without FROM clause, to create
a table with one column and multiple rows
a table with multiple columns and one row
a table with multiple columns and multiple rows
I have tried many combinations such as
SELECT VALUES(1, 2) AS tableName(n, m);
to no success.
You can do it with CTE and using union(Use union all if you want to display duplicates)
Rextester Sample for all 3 scenarios
One Column and multiple rows
with tbl1(id) as
(select 1 union all
select 2)
select * from tbl1;
One row and multiple columns
with tbl2(id,name) as
(select 1,'A')
select * from tbl2;
Multiple columns and multiple rows
with tbl3(id,name) as
(select 1,'A' union all
select 2,'B')
select * from tbl3;
-- One column, multiple rows.
select 1 as ColumnName union all select 2; -- Without FROM;
select * from ( values ( 1 ), ( 2 ) ) as Placeholder( ColumnName ); -- With FROM.
-- Multiple columns, one row.
select 1 as TheQuestion, 42 as TheAnswer; -- Without FROM.
select * from ( values ( 1, 42 ) ) as Placeholder( TheQuestion, TheAnswer ); -- With FROM.
-- Multiple columns and multiple rows.
select 1 as TheQuestion, 42 as TheAnswer union all select 1492, 12; -- Without FROM.
select * from ( values ( 1, 2 ), ( 2, 4 ) ) as Placeholder( Column1, Column2 ); -- With FROM.
You can do all that by using UNION keyword
create table tablename as select 1 as n,3 as m union select 2 as n,3 as m
In Oracle it will be dual:
create table tablename as select 1 as n,3 as m from dual union select 2 as n,3 as m from dual
You can use UNION operator:
CREATE TABLE AS SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;
The UNION operator selects only distinct values by default. To allow duplicate values you can use UNION ALL.
The column names in the result-set are usually equal to the column names in the first SELECT statement in the UNION.
try this:
--1) a table with one column and multiple rows
select * into tmptable0 from(
select 'row1col1' as v1
union all
select 'row2col1' as v1
) tmp
--2) a table with multiple columns and one row
select 'row1col1' as v1, 'row1col2' as v2 into tmptable1
--3) a table with multiple columns and multiple rows
select * into tmptable2 from(
select 'row1col1' as v1, 'row1col2' as v2
union all
select 'row2col1' as v2, 'row2col2' as v2
) tmp
One can create a view and later can query it whenever required
-- table with one column and multiple rows
create view vw1 as
(
select 'anyvalue' as col1
union all
select 'anyvalue' as col1
)
select * from vw1
-- table with multiple columns and multiple rows
create view vw2 as
(
select 'anyvalue1' as col1, 'anyvalue1' as col2
union all
select 'anyvalue2' as col1, 'anyvalue2' as col2
)
select * from vw2

Assign row numbers to a set of results retrieved via a PL-SQL Stored Procedure

I have a stored procedure which performs a number of select statements, and joins the results using a UNION, e.g.
OPEN myItems FOR
SELECT column1, columns2 ... FROM table1 WHERE myCondition
UNION
SELECT column1, column2 ... FROM table2 WHERE myCondition
UNION
SELECT column1, column 2....
END
and so on.
I need to be able to uniquely identify the data being returned, and so I was hoping to be able to assign a row number to the rows that are returned.
I imagine there's an easy way to do this, but unfortunately I haven't been able to find it!
Thanks
SELECT ROW_NUMBER() OVER (ORDER BY column1) AS row_num, t.*
FROM (
SELECT column1, columns2 ... FROM table1 WHERE myCondition
UNION
SELECT column1, column2 ... FROM table2 WHERE myCondition
UNION
SELECT column1, column 2....
) t;

UNION operation with the same table

I have a scenario where I need to query data in a single row as multiple columns,
Table format is as follows,
SAMPLE_TABLE [ID, REF_TAB_A,REF_TAB_B,REF_TAB_C]
I need REF_TAB_A,REF_TAB_B,REF_TAB_C values in a single column. What I did is use UNION ALL as follows,
SELECT REF_TAB_A FROM SAMPLE_TABLE
UNION ALL
SELECT REF_TAB_B FROM SAMPLE_TABLE
UNION ALL
SELECT REF_TAB_C FROM SAMPLE_TABLE
Is there any other way to do this?? What is the most efficient way to handle such a scenario??
(I'm using oracle 11g)
Thanks in advance.. :D
Using union all generally results in three scans of the table. An alternative approach is a little messier but should have just one scan:
SELECT (case when which = 'A' then REF_TAB_A
when which = 'B' then REF_TAB_B
when which = 'C' then REF_TAB_C
end)
FROM SAMPLE_TABLE cross join
(select 'A' as which from dual union all select 'B' from dual union all select 'C' from dual
) iter
You could alias the columns:
SELECT REF_TAB_A tab FROM SAMPLE_TABLE
UNION ALL
SELECT REF_TAB_B tab FROM SAMPLE_TABLE
UNION ALL
SELECT REF_TAB_C tab FROM SAMPLE_TABLE
What you really need to do, however, is to normalize your database. Whenever you have columns with repeating names like name1, name2, name3, namea, nameb, and namec, it is a sign that you want another table and a 1-many relationship between them.
CREATE TABLE tabs (
tab_id NUMBER PRIMARY KEY,
sample_table_id NUMBER,
tab VARCHAR2(255),
CONSTRAINT FK_sample_table
FOREIGN KEY(sample_table_id)
REFERENCES SAMPLE_TABLE(sample_table_id)
)
Now your query involves a simple JOIN.
SELECT
tab
FROM tabs t
JOIN SAMPLE_TABLE st ON t.sample_table_id = st.sample_table_id
WHERE
...
As an alternative to Gordon's CROSS JOIN trick Oracle has the UNPIVOT clause in the SELECT statement specifically for this situation.
Assuming this table:
create table tmp_test ( a number, b number, c number );
insert all
into tmp_test values (1,2,3)
into tmp_test values (4,5,6)
select * from dual;
The following query would do what you require:
select col
from tmp_test
unpivot ( col for i in (a,b,c) );
COL
----------
1
2
3
4
5
6
6 rows selected.
For this small example an explain plan indicates that using the inbuilt functionality would be more efficient but but test both options and see what's better.

Single SQL SELECT Returning multiple rows from one table row

We have a table which is of the form:
ID,Value1,Value2,Value3
1,2,3,4
We need to transform this into.
ID,Name,Value
1,'Value1',2
1,'Value2',3
1,'Value3',4
Is there a clever way of doing this in one SELECT statement (i.e without UNIONs)? The column names Value1,Value2 and Value3 are fixed and constant.
The database is oracle 9i.
Give a union a shot.
select ID, 'Value1' as Name, Value1 as Value from table_name union all
select ID, 'Value2', Value2 as Value from table_name union all
select ID, 'Value3', Value3 as Value from table_name
order by ID, Name
using union all means that the server won't perform a distinct (which is implicit in union operations). It shouldn't make any difference with the data (since your ID's should HOPEFULLY be different), but it might speed it up a bit.
This works on Oracle 10g:
select id, 'Value' || n as name,
case n when 1 then value1 when 2 then value2 when 3 then value3 end as value
from (select rownum n
from (select 1 from dual connect by level <= 3)) ofs, t
I think Oracle 9i had recursive queries? Anyway, I'm pretty sure it has CASE support, so even if it doesn't have recursive queries, you can just do "(select 1 from dual union all select 2 from dual union all select 3 from dual) ofs" instead. Abusing recursive queries is a bit more general- for Oracle. (Using unions to generate rows is portable to other DBs, though)
You can do it like this, but it's not pretty:
SELECT id,'Value 1' AS name,value1 AS value FROM mytable
UNION
SELECT id,'Value 2' AS name,value2 AS value FROM mytable
UNION
SELECT id,'Value 3' AS name,value3 AS value FROM mytable
Unioning three select statements should do the trick:
SELECT ID, 'Value1', Value1 AS Value
FROM TABLE
UNION
SELECT ID, 'Value2', Value2 AS Value
FROM TABLE
UNION
SELECT ID, 'Value3', Value3 AS Value
FROM TABLE
If you're using SQL Server 2005+ then you can use UNPIVOT
CREATE TABLE #tmp ( ID int, Value1 int, Value2 int, Value3 int)
INSERT INTO #tmp (ID, Value1, Value2, Value3) VALUES (1, 2, 3, 4)
SELECT
*
FROM
#tmp
SELECT
*
FROM
#tmp
UNPIVOT
(
[Value] FOR [Name] IN (Value1, Value2, Value3)
) uPIVOT
DROP TABLE #tmp
A UNION ALL, as others have suggested, is probably your best bet in SQL. You might also want to consider handling this in the front end depending on what your specific requirements are.
CTE syntax may be different for Oracle (I ran it in Teradata), but I only used CTE to provide test data, those 1 2 3 and 4. You can use temp table instead. The actual select statement is plain vanilla SQL and it will on any relational database.
For Sql Server, consider UNPIVOT as an alternative to UNION:
SELECT id, value, colname
FROM #temp t
UNPIVOT (Value FOR ColName IN (value1,value2,value3)) as X
This will return the column name as well. I'm unsure what the X is used for, but you can't leave it out.
Try this:
CTE creates a temp table with 4 values. You can run this as is in any database.
with TEST_CTE (ID) as
(select * from (select '1' as a) as aa union all
select * from (select '2' as b) as bb union all
select * from (select '3' as c) as cc union all
select * from (select '4' as d) as dd )
select a.ID, 'Value'|| a.ID, b.ID
from TEST_CTE a, TEST_CTE b
where b.ID = (select min(c.ID) from TEST_CTE c where c.ID > a.ID)
Here is the result set:
1 Value1 2
2 Value2 3
3 Value3 4
Enjoy!
Some afterthoughts.
^^^ CTE syntax may be different in Oracle. I could only run it in Teradata. You can substitute it with temp table or fix the syntax to make it Oracle compatible. The select statement is plain vanilla SQL that will work on any database.
^^^ Another thing to note. If ID field is numeric, you might need to cast it into CHAR in order to concatenate it with "Value".