Loop inside SELECT in SQL Server query - sql

I'm trying to get a few columns using a function F(a,b,x) from SQL Server for say 20 values. Basically it's like
SELECT
col1, col2, F(a,b,1), F(a,b,2), ... F(a,b,20)
FROM
table
Is that possible to use a loop to SELECT F(a,b,#i) where 0 < #i < 21?
Thanks!

There is no "looping" in a SQL statement. But, you can come close by doing:
select col1, col2, n.n, f(a, b, n.n)
from table t cross join
(select 1 as n union all select 2 union all select 3 . . .
select 20
) n;
The exact syntax depends on the database you are using. There are also ways to generate numbers, once again, depending on the database.
This generates 20 rows for each row in the table, rather than 20 columns. The results can be pivoted if you really need them in columns.

Related

Why does RANDOM() in a SQLite CTE JOIN behave differently to other RDBMSs?

RANDOM() values in a Common Table Expression (CTE) join aren't behaving as expected in SQLite.
SQL:
WITH
tbl1(n) AS (SELECT 1 UNION ALL SELECT 2),
tbl2(n, r) AS (SELECT n, RANDOM() FROM tbl1)
SELECT * FROM tbl2 t1 CROSS JOIN tbl2 t2;
Sample SQLite results:
n r n r
1 7058971975145008000 1 8874103142384122000
1 1383551786055205600 2 8456124381892735000
2 2646187515714600000 1 7558324128446983000
2 -1529979429149869800 2 7003770339419606000
The random numbers in each column are all different. But a CROSS JOIN repeats rows - so I expected 2 pairs of the same number in each column - which is the case in PostgreSQL, Oracle 11g and SQL Server 2014 (when using a row-based seed).
Sample PostgreSQL / Oracle 11g / SQL Server 2014 results:
n r n r
1 0.117551110684872 1 0.117551110684872
1 0.117551110684872 2 0.221985165029764
2 0.221985165029764 1 0.117551110684872
2 0.221985165029764 2 0.221985165029764
Questions
Can the behaviour in SQLite be explained? Is it a bug?
Is there a way for Table B in a CTE (based on Table A in the same CTE) to have an additional column of randomly generated numbers, which will remain fixed when used in a JOIN?
Your question is rather long and rambling -- not a single question. But, it is interesting and I learned something.
This statement is not true:
SQL Server assigns a random seed to the RAND() function: When used in
a SELECT, it is only seeded once rather than for each row.
SQL Server has the concept of run-time constant functions. These are functions that are pulled from the compiled query and executed once per expression at the beginning of the query. The most prominent examples are getdate() (and related date/time functions) and rand().
You can readily see this if you run:
select rand(), rand()
from (values (1), (2), (3)) v(x);
Each column has the same values, but the values between the columns are different.
Most databases -- including SQLite -- have the more intuitive interpretation of rand()/random(). (As an personal note, a "random" function that returns the same value on each row is highly counter-intuitive.) Each time it is called you get a different value. For SQL Server, you would typically use an expression using newid():
select rand(), rand(), rand(checksum(newid()))
from (values (1), (2), (3)) v(x);
As for your second question, it appears that SQLite materializes recursive CTEs. So this does what you want:
WITH tbl1(n) AS (
SELECT 1 UNION ALL SELECT 2
),
tbl2(n, r) AS (
SELECT n, RANDOM()
FROM tbl1
union all
select *
from tbl2
where 1=0
)
SELECT *
FROM tbl2 t1 CROSS JOIN tbl2 t2;
I have seen no documentation that this is the case, so use at your own risk. Here is a DB-Fiddle.
And, for the record, this seems to work in SQL Server as well. I just learned something!
EDIT:
As suggested in the comment, the materialization may not always happen. It does seem to apply to two references at the same level:
WITH tbl1(n) AS (
SELECT 1 UNION ALL SELECT 2),
tbl2(n, r) AS (
SELECT n, RANDOM()
FROM tbl1
union all
select *
from tbl2
where 1=0
)
SELECT t2a.r, count(*)
FROM tbl2 t2a left JOIN
tbl2 t2b
on t2a.r = t2b.r
GROUP BY t2a.r;

SQL Logic: Finding Non-Duplicates with Similar Rows

I'll do my best to summarize what I am having trouble with. I never used much SQL until recently.
Currently I am using SQL Server 2012 at work and have been tasked with trying to find oddities in SQL tables. Specifically, the tables contain similar information regarding servers. Kind of meta, I know. So they each share a column called "DB_NAME". After that, there are no similar columns. So I need to compare Table A and Table B and produce a list of records (servers) where a server is NOT listed in BOTH Table A and B. Additionally, this query is being ran against an exception list. I'm not 100% sure of the logic to best handle this. And while I would love to get something "extremely efficient", I am more-so looking at something that just plain works at the time being.
SELECT *
FROM (SELECT
UPPER(ta.DB_NAME) AS [DB_Name]
FROM
[CMS].[dbo].[TABLE_A] AS ta
UNION
SELECT
UPPER(tb.DB_NAME) AS [DB_Name]
FROM
[CMS].[dbo].[TABLE_B] as tb
) AS SQLresults
WHERE NOT EXISTS (
SELECT *
FROM
[CMS].[dbo].[TABLE_C_EXCEPTIONS] as tc
WHERE
SQLresults.[DB_Name] = tc.DB_NAME)
ORDER BY SQLresults.[DB_Name]
One method uses union all and aggregation:
select ab.*
from ((select upper(name) as name, 'A' as which
from CMS.dbo.TABLE_A
) union all
(select upper(name), 'B' as which
from CMS.dbo.TABLE_B
)
) ab
where not exists (select 1
from CMS.dbo.TABLE_C_EXCEPTION e
where upper(e.name) = ab.name
)
having count(distinct which) <> 2;
SQL Server is case-insensitive by default. I left the upper()s in the query in case your installation is case sensitive.
Here is another option using EXCEPT. I added a group by in each half of the union because it was not clear in your original post if DB_NAME is unique in your tables.
select DatabaseName
from
(
SELECT UPPER(ta.DB_NAME) AS DatabaseName
FROM [CMS].[dbo].[TABLE_A] AS ta
GROUP BY UPPER(ta.DB_NAME)
UNION ALL
SELECT UPPER(tb.DB_NAME) AS DatabaseName
FROM [CMS].[dbo].[TABLE_B] as tb
GROUP BY UPPER(tb.DB_NAME)
) x
group by DatabaseName
having count(*) < 2
EXCEPT
(
select DN_Name
from CMS.dbo.TABLE_C_EXCEPTION
)

Returning the lowest integer not in a list in SQL

Supposed you have a table T(A) with only positive integers allowed, like:
1,1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18
In the above example, the result is 10. We always can use ORDER BY and DISTINCT to sort and remove duplicates. However, to find the lowest integer not in the list, I came up with the following SQL query:
select list.x + 1
from (select x from (select distinct a as x from T order by a)) as list, T
where list.x + 1 not in T limit 1;
My idea is start a counter and 1, check if that counter is in list: if it is, return it, otherwise increment and look again. However, I have to start that counter as 1, and then increment. That query works most of the cases, by there are some corner cases like in 1. How can I accomplish that in SQL or should I go about a completely different direction to solve this problem?
Because SQL works on sets, the intermediate SELECT DISTINCT a AS x FROM t ORDER BY a is redundant.
The basic technique of looking for a gap in a column of integers is to find where the current entry plus 1 does not exist. This requires a self-join of some sort.
Your query is not far off, but I think it can be simplified to:
SELECT MIN(a) + 1
FROM t
WHERE a + 1 NOT IN (SELECT a FROM t)
The NOT IN acts as a sort of self-join. This won't produce anything from an empty table, but should be OK otherwise.
SQL Fiddle
select min(y.a) as a
from
t x
right join
(
select a + 1 as a from t
union
select 1
) y on y.a = x.a
where x.a is null
It will work even in an empty table
SELECT min(t.a) - 1
FROM t
LEFT JOIN t t1 ON t1.a = t.a - 1
WHERE t1.a IS NULL
AND t.a > 1; -- exclude 0
This finds the smallest number greater than 1, where the next-smaller number is not in the same table. That missing number is returned.
This works even for a missing 1. There are multiple answers checking in the opposite direction. All of them would fail with a missing 1.
SQL Fiddle.
You can do the following, although you may also want to define a range - in which case you might need a couple of UNIONs
SELECT x.id+1
FROM my_table x
LEFT
JOIN my_table y
ON x.id+1 = y.id
WHERE y.id IS NULL
ORDER
BY x.id LIMIT 1;
You can always create a table with all of the numbers from 1 to X and then join that table with the table you are comparing. Then just find the TOP value in your SELECT statement that isn't present in the table you are comparing
SELECT TOP 1 table_with_all_numbers.number, table_with_missing_numbers.number
FROM table_with_all_numbers
LEFT JOIN table_with_missing_numbers
ON table_with_missing_numbers.number = table_with_all_numbers.number
WHERE table_with_missing_numbers.number IS NULL
ORDER BY table_with_all_numbers.number ASC;
In SQLite 3.8.3 or later, you can use a recursive common table expression to create a counter.
Here, we stop counting when we find a value not in the table:
WITH RECURSIVE counter(c) AS (
SELECT 1
UNION ALL
SELECT c + 1 FROM counter WHERE c IN t)
SELECT max(c) FROM counter;
(This works for an empty table or a missing 1.)
This query ranks (starting from rank 1) each distinct number in ascending order and selects the lowest rank that's less than its number. If no rank is lower than its number (i.e. there are no gaps in the table) the query returns the max number + 1.
select coalesce(min(number),1) from (
select min(cnt) number
from (
select
number,
(select count(*) from (select distinct number from numbers) b where b.number <= a.number) as cnt
from (select distinct number from numbers) a
) t1 where number > cnt
union
select max(number) + 1 number from numbers
) t1
http://sqlfiddle.com/#!7/720cc/3
Just another method, using EXCEPT this time:
SELECT a + 1 AS missing FROM T
EXCEPT
SELECT a FROM T
ORDER BY missing
LIMIT 1;

Subquery without temporary tables

I'm having a problem which I wanted to solve with temporary tables, however, I discovered this is not really a good approach in Oracle.
I issue a select command from multiple tables and get Result1. I then want to combine columns 2,3 into 1 column which contains the unique values from columns 2,3 e.g.
Select distinct(col2) from Result1
UNION
Select distinct(col3) from Result1
As Result2
I then want to use the values from Result2 in a subquery. An easy way to do this last part to make the query above part of my Where clause as a subquery, but again I don't know how to reference Result1. So what I want is:
Select * from xyz where col in
(Select distinct(col2) from Result1
UNION
Select distinct(col3) from Result1)
What is the best way to combine the results of these queries in Oracle without a temp table?
First, your query can be written as:
Select *
from xyz
where col in (Select col2 from Result1 union all
Select col3 from Result1
)
The in automatically handles duplicates in this case. This is better written as the following, using with for result1:
with result1 as (
<your query here>
)
select *
from xyz
where exists (select 1 from result1 r where xyz.col1 = r.col2 or xyz.col1 = r.col3);
Does that solve your problem?

How to put four numbers from four tables into a 2 by 2 table?

I am doing an analysis using SAS. Each time after the analysis, I will get four tables. Table11, Table12 , Tabe21 and Table22. Each table only has one number. I would like to put these four numbers(from four tables) into a 2x2 matrix. How can I do that using SAS proc sql?
For example, when I have 1 (the only number in table 1), 2 (the only number in table 2), 3 (the only number in table 3), 4 (the only number in table 4), I would like to produce anther table that has a 2 by 2 table with these four numbers inside.
A totally hacky, terrible way to do this might look something like this:
SELECT (SELECT num FROM table1), (SELECT num FROM table2)
UNION ALL
SELECT (SELECT num FROM table3), (SELECT num FROM table4)
You may have to add a "dummy" table in there after each part of the union (like SELECT ... FROM sysibm.sysdummy1 in DB2).
I've never used SAS, so I don't know the specifics. Maybe that (standard SQL) solution will work for you. But like I said, it's terribly hackish, so there's probably a better way to do that.
Probably with equivalent execution plan (using implicit cross join):
SELECT table11.num, table12.num
FROM table11, table12
UNION ALL
SELECT table21.num, table22.num
FROM table21, table22
Clearly you want to go with the job-security technique.
with
t11(r, c, num) as (SELECT 1, 1, num from table11),
t12(r, c, num) as (SELECT 1, 2, num from table12),
t21(r, c, num) as (SELECT 2, 1, num from table21),
t22(r, c, num) as (SELECT 2, 2, num from table22)
select
coalesce(t11.num, t21.num) as col1,
coalesce(t12.num, t22.num) as col2
from
(t11 cross join t12)
full outer join
(t21 cross join t22)
on t11.r = t21.r or t12.r = t22.r
Or if you prefer to not waste the values in column c, this might be better.
case
when t11.r * t11.c = t12.c - t12.r
then t11.num else t21.num
end as col1,
case
when t21.r * t22.r * t22.c - t21.c = t21.r + t21.c + t22.r + t22.c
then t22.num else t12.num
end as col2
You have received some good points/solutions above, however you may want to try to automate some checking for the existence of each data set prior to doing the joining. Something like the following:
data c12;
c12=32;
run;
data c21;
c21=40;
run;
data c22;
c22=12;
run;
%sysfunc(ifc(%sysfunc(exist(c11)),
proc print data = a; run;,
data _null_; file print; put 'Dataset "c11: was not created'; run;))