Can you define "literal" tables in SQL? - sql

Is there any SQL subquery syntax that lets you define, literally, a temporary table?
For example, something like
SELECT
MAX(count) AS max,
COUNT(*) AS count
FROM
(
(1 AS id, 7 AS count),
(2, 6),
(3, 13),
(4, 12),
(5, 9)
) AS mytable
INNER JOIN someothertable ON someothertable.id=mytable.id
This would save having to do two or three queries: creating temporary table, putting data in it, then using it in a join.
I am using MySQL but would be interested in other databases that could do something like that.

I suppose you could do a subquery with several SELECTs combined with UNIONs.
SELECT a, b, c, d
FROM (
SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d
UNION ALL
SELECT 5 , 6, 7, 8
) AS temp;

You can do it in PostgreSQL:
=> select * from (values (1,7), (2,6), (3,13), (4,12), (5,9) ) x(id, count);
id | count
----+-------
1 | 7
2 | 6
3 | 13
4 | 12
5 | 9
http://www.postgresql.org/docs/8.2/static/sql-values.html

In Microsoft T-SQL 2008 the format is:
SELECT a, b FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10) ) AS MyTable(a, b)
I.e. as Jonathan mentioned above, but without the 'table' keyword.
See:
FROM (T-SQL MSDN docs) (under derived-table), and
Table Value Constructor (T-SQL MSDN docs)

In standard SQL (SQL 2003 - see http://savage.net.au/SQL/) you can use:
INSERT INTO SomeTable(Id, Count) VALUES (1, 7), (2, 6), (3, 13), ...
With a bit more chasing, you can also use:
SELECT * FROM TABLE(VALUES (1,7), (2, 6), (3, 13), ...) AS SomeTable(Id, Count)
Whether these work in MySQL is a separate issue - but you can always ask to get it added, or add it yourself (that's the beauty of Open Source).

I found this link Temporary Tables With MySQL
CREATE TEMPORARY TABLE TempTable ( ID int, Name char(100) ) TYPE=HEAP;
INSERT INTO TempTable VALUES( 1, "Foo bar" );
SELECT * FROM TempTable;
DROP TABLE TempTable;

Since MariaDB v10.3.3 and MySQL v8.0.19 you can now do exactly that!
See docs: MariaDB, MySQL
MariaDB:
WITH literaltable (id,count) AS (VALUES (1,7),(2,6),(3,13),(4,12),(5,9))
SELECT MAX(count) AS max,COUNT(*) AS count FROM literaltable
I used a WITH here because MariaDB doesn't supply nice column names for VALUES .... You can use it in a union without column names:
SELECT 1 AS id,7 AS count UNION ALL VALUES (2,6),(3,13),(4,12),(5,9) ORDER BY count DESC
And although the docs don't appear to mention it, you can even use it as a top-level query:
VALUES (1,7),(2,6),(3,13),(4,12),(5,9) ORDER BY 2 DESC
The actual column names are in fact the just first row of values, so you can even do this (though it's inelegant, and you can run into duplicate column name errors):
SELECT MAX(`7`) AS max,COUNT(*) AS count FROM (VALUES (1,7),(2,6),(3,13),(4,12),(5,9)) literaltable
MySQL:
I don't have an instance of MySQL v8.0.19 to test against right now, but according to the docs either of these should work:
SELECT MAX(column_1) AS max,COUNT(*) AS count FROM (VALUES ROW(1,7), ROW(2,6), ROW(3,13), ROW(4,12), ROW(5,9)) literaltable
SELECT MAX(data) AS max,COUNT(*) AS count FROM (VALUES ROW(1,7), ROW(2,6), ROW(3,13), ROW(4,12), ROW(5,9)) literaltable(id,data)
Unlike MariaDB, MySQL supplies automatic column names column_0, column_1, column_2, etc., and also supports renaming all of a subquery's columns when referencing it.
I'm not sure, but this dev worklog page seems to suggest that MySQL has also implemented the shorter sytax (omitting "ROW", like MariaDB), or that they will in the near future.

In a word, yes. Even better IMO if your SQL product supports common table expressions (CTEs) i.e. easier on the eye than using a subquery plus the same CTE can be used multiple times e.g. this to 'create' a sequence table of unique integers between 0 and 999 in SQL Server 2005 and above:
WITH Digits (nbr) AS
(
SELECT 0 AS nbr UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9
),
Sequence (seq) AS
(
SELECT Units.nbr + Tens.nbr + Hundreds.nbr
FROM Digits AS Units
CROSS JOIN Digits AS Tens
CROSS JOIN Digits AS Hundreds
)
SELECT S1.seq
FROM Sequence AS S1;
except you'd actually do something useful with the Sequence table e.g. parse the characters from a VARCHAR column in a base table.
HOWEVER, if you are using this table, which consists only of literal values, multiple time or in multiple queries then why not make it a base table in the first place? Every database I use has a Sequence table of integers (usually 100K rows) because it is so useful generally.

CREATE TEMPORARY TABLE ( ID int, Name char(100) ) SELECT ....
Read more at : http://dev.mysql.com/doc/refman/5.0/en/create-table.html
( near the bottom )
This has the advantage that if there is any problem populating the table ( data type mismatch ) the table is automatically dropped.
An early answer used a FROM SELECT clause. If possible use that because it saves the headache of cleaning up the table.
Disadvantage ( which may not matter ) with the FROM SELECT is how large is the data set created. A temporary table allows for indexing which may be critical. For the subsequent query. Seems counter-intuitive but even with a medium size data set ( ~1000 rows), it can be faster to have a index created for the query to operate on.

Related

SQL Snowflake - Put an SQL list / array into a column

Based on a specific project architecture, I have a LIST ('Bob', 'Alice') that I want to SELECT as a column (and do a specific JOIN afterwards).
Right now, I did :
SELECT *
FROM TABLE(flatten(input => ('Bob', 'Alice'))) as v1
But this resulted in one row / two columns, and I need one column / two rows (to do the JOIN).
Same if I use :
select * from (values ('Bob', 'Alice'))
The basic idea would be to PIVOT, however, the list may be of arbitrary length so I can't manually list all column names in PIVOT query...
Also I can't use the following (which would work) :
select * from (values ('Bob'), ('Alice'))
because I inherit the list as a string and can't modify it on the fly.
If you have a fixed set of values that you are wanting to JOIN against, and looking at some of the SQL you have tried the correct form to use VALUES is:
select * from (values ('Bob'), ('Alice'));
or
select * from values ('Bob'), ('Alice');
if you have a exist array you can FLATTEN it like for first example
SELECT v1.value::text
FROM TABLE(flatten(input => array_construct('Bob', 'Alice'))) as v1;
V1.VALUE::TEXT
Bob
Alice
or if you have a string "Bob, Alice" then SPLIT_TO_TABLE
SELECT trim(v1.value)
FROM TABLE(split_to_table('Bob, Alice', ',')) as v1;
If the input is provided as ('Bob','Alice') then STRTOK_SPLIT_TO_TABLE could be used:
SELECT table1.value
FROM table(strtok_split_to_table($$('Bob','Alice')$$, '(),''')) AS table1;
Output:

Is there some equivalent to [ WHERE col IN (%) ] in SQLite?

I have an SQLite query where I need the ability to get rows where a certain column (A, below) can either be anything (aka wildcard) or 1 of several options.
This works for wildcard or single values, but not multiple:
SELECT * FROM table WHERE A LIKE "%"
SELECT * FROM table WHERE A LIKE "1"
This works for single or multiple values, but not wildcard:
SELECT * FROM table WHERE A IN (1)
SELECT * FROM table WHERE A IN (1, 2, 3)
What I would want to be able to do is something like this:
SELECT * FROM table WHERE A IN (%)
I could use an if statement to choose between the first two options, except I actually have a lot of columns where I need to do this, more like:
SELECT * FROM table WHERE A IN (1, 2, 3) AND B IN (4, 5, 6) AND C IN (%) AND D IN (7, 8)
So nested "if" loops get pretty unwieldy.
So, is there a way to use a wildcard with WHERE IN or a way to have multiple possibilities in WHERE LIKE?
If column is defined as NOT NULL you could use:
SELECT * FROM table WHERE A IN (A);
--for not nullable column it's equivalent of 1=1
DBFiddle Demo

how does a SQL query (Pro*C) be able to find set of values that are not present in a db table

I have a C/C++ program with embedded Oracle SQL calls in proc (11.2+ version). I have a list of values (int) as an array. I would like to check through a SQL query (as proc code) which values are not in a DB table. For example, say, I have values: 10, 20, 30, 40, 50 stored in an array and my DB table t1 has column col1 that has 10, 20, 40. So,
Select col1 from t1
will provide me:
10
20
40
So, I am looking for the excluded values, i.e. 30, 50.
Can I do this through an embedded Pro*C SQL query?
My list is quite large and the DB table has many values.
You need to set up your values in a table. Here is a typical way what will work in most databases:
with list as (
select 10 as n union all select 20 union all select 30 union all
select 40 union all select 50
)
select l.*
from list l
where not exists (select 1 from t1 where col1 = l.n);
The syntax might vary depending on the database (from dual, a subquery instead of a CTE), but the idea is the same.
In Oracle you could use a collection as an alternative to creating a CTE/inline view using unioned queries:
select l.column_value as missing
from table(sys.odcinumberlist(10, 20, 30, 40, 50)) l
where not exists (select 1 from t1 where col1 = l.column_value);
MISSING
----------
20
30
50
SQL Fiddle.
Here sys.odcinumberlist is a built-in collection type, to save you creating your own table type.
Aside from being slightly easier to type the values in like that (IMO), you can also use this approach from a client application by passing the values as an array, which can be useful. There are examples here for OCI (C/C++) or here for Java.
SELECT regexp_substr('10|20|30|40|50', '[^|]+', 1, ROWNUM) var
FROM dual
CONNECT BY LEVEL <= length(regexp_replace('10|20|30|40|50', '[^|]', '')) + 1
MINUS
SELECT col1 FROM t1;

Select distinct values from multiple columns in same table

I am trying to construct a single SQL statement that returns unique, non-null values from multiple columns all located in the same table.
SELECT distinct tbl_data.code_1 FROM tbl_data
WHERE tbl_data.code_1 is not null
UNION
SELECT tbl_data.code_2 FROM tbl_data
WHERE tbl_data.code_2 is not null;
For example, tbl_data is as follows:
id code_1 code_2
--- -------- ----------
1 AB BC
2 BC
3 DE EF
4 BC
For the above table, the SQL query should return all unique non-null values from the two columns, namely: AB, BC, DE, EF.
I'm fairly new to SQL. My statement above works, but is there a cleaner way to write this SQL statement, since the columns are from the same table?
It's better to include code in your question, rather than ambiguous text data, so that we are all working with the same data. Here is the sample schema and data I have assumed:
CREATE TABLE tbl_data (
id INT NOT NULL,
code_1 CHAR(2),
code_2 CHAR(2)
);
INSERT INTO tbl_data (
id,
code_1,
code_2
)
VALUES
(1, 'AB', 'BC'),
(2, 'BC', NULL),
(3, 'DE', 'EF'),
(4, NULL, 'BC');
As Blorgbeard commented, the DISTINCT clause in your solution is unnecessary because the UNION operator eliminates duplicate rows. There is a UNION ALL operator that does not elimiate duplicates, but it is not appropriate here.
Rewriting your query without the DISTINCT clause is a fine solution to this problem:
SELECT code_1
FROM tbl_data
WHERE code_1 IS NOT NULL
UNION
SELECT code_2
FROM tbl_data
WHERE code_2 IS NOT NULL;
It doesn't matter that the two columns are in the same table. The solution would be the same even if the columns were in different tables.
If you don't like the redundancy of specifying the same filter clause twice, you can encapsulate the union query in a virtual table before filtering that:
SELECT code
FROM (
SELECT code_1
FROM tbl_data
UNION
SELECT code_2
FROM tbl_data
) AS DistinctCodes (code)
WHERE code IS NOT NULL;
I find the syntax of the second more ugly, but it is logically neater. But which one performs better?
I created a sqlfiddle that demonstrates that the query optimizer of SQL Server 2005 produces the same execution plan for the two different queries:
If SQL Server generates the same execution plan for two queries, then they are practically as well as logically equivalent.
Compare the above to the execution plan for the query in your question:
The DISTINCT clause makes SQL Server 2005 perform a redundant sort operation, because the query optimizer does not know that any duplicates filtered out by the DISTINCT in the first query would be filtered out by the UNION later anyway.
This query is logically equivalent to the other two, but the redundant operation makes it less efficient. On a large data set, I would expect your query to take longer to return a result set than the two here. Don't take my word for it; experiment in your own environment to be sure!
try something like SubQuery:
SELECT derivedtable.NewColumn
FROM
(
SELECT code_1 as NewColumn FROM tbl_data
UNION
SELECT code_2 as NewColumn FROM tbl_data
) derivedtable
WHERE derivedtable.NewColumn IS NOT NULL
The UNION already returns DISTINCT values from the combined query.
Union is applied wherever the row data required is similar in terms of type, values etc. It doesnt matter you have column in the same table or the other to retrieve from as the results would remain the same ( in one of the above answers already mentioned though).
As you didn't wanted duplicates theres no point using UNION ALL and use of distinct is simply unnecessary as union gives distinct data
Can create a view would be best choice as view is a virtual representation of the table. Modifications could be then done neatly on that view created
Create VIEW getData AS
(
SELECT distinct tbl_data.code_1
FROM tbl_data
WHERE tbl_data.code_1 is not null
UNION
SELECT tbl_data.code_2
FROM tbl_data
WHERE tbl_data.code_2 is not null
);
Try this if you have more than two Columns:
CREATE TABLE #temptable (Name1 VARCHAR(25),Name2 VARCHAR(25))
INSERT INTO #temptable(Name1, Name2)
VALUES('JON', 'Harry'), ('JON', 'JON'), ('Sam','harry')
SELECT t.Name1+','+t.Name2 Names INTO #t FROM #temptable AS tSELECT DISTINCT ss.value FROM #t AS t
CROSS APPLY STRING_SPLIT(T.Names,',') AS ss

Oracle Select numbers from an IN clause

I'm looking for the best way to select numbers directly from an in clause.
Basically like:
SELECT * FROM (2,6,1,8);
That doesn't work. I can do it this way:
SELECT Lv FROM ( SELECT Level LV
FROM DUAL
CONNECT BY Level < 20)
WHERE Lv IN (2,6,1,8);
But that seems to be a bit clunky. Is there a more elegant way?
You can do
select column_value from table(sys.dbms_debug_vc2coll(1,2,3,4,5));
but that actually returns a varchar2. You can create your own TYPE and use that
create type tab_num is table of number;
/
select column_value from table(tab_num(1,2,3,4,5));
It's also worth looking at the MODEL clause. It looks complicated, but it is very good at generating data
SELECT x from dual
MODEL DIMENSION BY (1 AS z) MEASURES (1 x)
RULES ITERATE (7) (x[ITERATION_NUMBER]=ITERATION_NUMBER+1)
It's more elegant if you materialize an auxiliary numbers table:
SELECT num FROM numbers WHERE num IN (2,6,1,8);
And this is also useful when combined with another table.
For instance, I've had a case where I needed to populate large configuration tables with changes from piecewise results:
Big SP or Excel sheet or report identifies missing cost centers in config gives a large set of results which need to be inserted with varying data in some groups.
Paste partial results into a individual comma separated lists:
INSERT INTO {stuff}
SELECT {stuff}, 130 as line_item
FROM numbers
WHERE numbers.num IN ({pasted a section of results})
INSERT INTO {stuff}
SELECT {stuff}, 135 as line_item
FROM numbers
WHERE numbers.num IN ({pasted another section of results})
If you don't explicitly need the IN clause, you could use UNION:
select 2 from dual
union
select 6 from dual
union
select 1 from dual
union
select 8 from dual
There is a more elegant variant to INSERT multiple rows into a table:
INSERT ALL
INTO table (col) VALUES ('a')
INTO table (col) VALUES ('b')
INTO table (col) VALUES ('c')
SELECT * FROM dual;
But I don't know a way to do that for a SELECT.