Using INSERT with CTE - sql

For a somewhat complex SQL script I need the following mapping:
WITH days_mapping AS (SELECT 1 AS day
UNION ALL
SELECT 2 AS day
UNION ALL
...
SELECT 31 AS day)
Is there any way to create the same mapping but without manually writing a SELECT and UNION ALL for every single number/day that should be in this mapping? I was thinking of doing an INSERT in a WHILE loop instead of the SELECT but I don't know how or if it is even possible to do that with common table expressions.

You can use a recursive CTE:
with days_mapping as (
select 1 as day
union all
select day + 1
from days_mapping
where day < 31
)
select *
from days_mapping;
Here is a db<>fiddle.
Note: If you have more than 100 rows being generating, you need to use option (maxrecursion 0) at the end of the query.

Related

How to restrict recursive CTE row count

I have a function use to generate a record id, I want to use CTE to get batch of record id.
Now the recursive CTE like below
with T as (
select
dbo.Ufn_GetRecordId() AS recordId
union all
SELECT
dbo.Ufn_GetRecordId() AS recordId
FROM T
)select * from T
OPTION (MaxRecursion 0);
However, this query will not terminate. How restrict the count of CTE?(e.g. if I only need 3 rows in T)
You can try something like below. Idea taken from SQL Server: How to limit CTE recursion to rows just recursivly added?
with T as (
select
dbo.Ufn_GetRecordId() AS recordId, 1 as testnum
union all
SELECT
dbo.Ufn_GetRecordId() AS recordId, testnum + 1
FROM T
WHERE testnum < 3
)select * from T
OPTION (MaxRecursion 0);
This will restrict to 3 returned rows.
This is a fairly standard way of generating N rows with a recursive CTE.
WITH T
AS (SELECT 1 AS Dummy
UNION ALL
SELECT Dummy + 1
FROM T
WHERE Dummy < 3)
SELECT dbo.Ufn_GetRecordId() AS RecordId
FROM T;
If you need to generate more than 100 numbers then you'll need OPTION (MAXRECURSION 0) (or some suitable value instead of 0).

Selecting a sequence in SQL

There seems to be a few blog posts on this topic but the solutions really are not so intuitive. Surely there's a "Canonical" way?
I'm using Teradata SQL.
How would I select
A range of number
A date range
E.g.
SELECT 1:10 AS Nums
SELECT 1-1-2010:5-1-2014 AS Dates1
The result would be 10 rows (1 - 10) in the first SELECT query and ~(365 * 3.5) rows in the second?
The "canonical" way to do this in SQL is using recursive CTEs, which the more recent versions of Teradata support.
For your first example:
with recursive nums(n) as (
select 1 as n
union all
select n + 1
from nums
where n < 10
)
select *
from nums;
You can do something similar for dates.
EDIT:
You can also do this by using row_number() and an existing table:
with nums(n) as (
select n
from (select row_number() over (order by col) as n
from ExstingTable t
) t
where n <= 10
)
select *
from nums;
ExistingTable is just any table with enough rows. The best choice of col is the primary key.
with digits(n) as (
select 1 as n 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 union all select 10
)
select *
from digits;
If your version of Teradata supports multiple CTEs, you can build on the above:
with digits(n) as (
select 1 as n 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 union all select 10
),
nums(n) as (
select d1.n*100 + d2.n*10 + d3.n
from digits d1 cross join digits d2 cross join digits d3
)
select *
from nums;
In Teradata you can use the existing sys_calendar to get those dates:
SELECT calendar_date
FROM sys_calendar.CALENDAR
WHERE calendar_date BETWEEN DATE '2010-01-01' AND DATE '2014-05-01';
Note:
DATE '2010-01-01' is the only recommended way to write a date in Teradata
There's probably another custom calendar for the specific business needs of your company, too. Everyone will have access rights to it.
You might also use this for the range of numbers:
SELECT day_of_calendar
FROM sys_calendar.CALENDAR
WHERE day_of_calendar BETWEEN 1 AND 10;
But you should check Explain to see if the estimated number of rows is correct. sys_calendar is a kind of template and day_of_calendar is a calculated column, so no statistics exists on that and Explain will return an estimated number of 14683 (20 percent of the number of rows in that table) instead of 10. If you use it in additional joins the optimizer might do a bad plan based on that totally wrong number.
Note:
If you use sys_calendar you are limited to a maximum of 73414 rows, dates between 1900-01-01 and 2100-12-31 and numbers between 1 and 73414, your business calendar might vary.
Gordon Linoff's recursive query is not really efficient in Teradata, as it's a sequential row-by-row processing in a parallel database (each loop is an "all-AMPs step" in Explain) and the optimizer doesn't know how many rows will be returned.
If you need those ranges regularly you might consider creating a numbers table, I usually got one with a million rows or I use my calendar with the full range of 10000 years :-)
--DROP TABLE nums;
CREATE TABLE nums(n INT NOT NULL PRIMARY KEY CHECK (n BETWEEN 0 AND 999999));
INSERT INTO Nums
WITH cte(n) AS
(
SELECT day_of_calendar - 1
FROM sys_calendar.CALENDAR
WHERE day_of_calendar BETWEEN 1 AND 1000
)
SELECT
t1.n +
t2.n * 1000
FROM cte t1 CROSS JOIN cte t2;
COLLECT STATISTICS COLUMN(n) ON Nums;
The COLLECT STATS is the most important step to get correct estimates.
Now it's a simple
SELECT n FROM nums WHERE n BETWEEN 1 AND 10;
There's also a nice UDF on GitHub for creating sequences which is easy to use:
SELECT DATE '2010-01-01' + SEQUENCE
FROM TABLE(gen_sequence(0,DATE '2014-05-01' - DATE '2010-01-01')) AS t;
SELECT SEQUENCE
FROM TABLE(gen_sequence(1,10)) AS t;
But it's usually hard to convince your DBA to install any C-UDFs and the number of rows returned is unknown again.
sequence 1 to 10
sel sum (1) over (ROWS UNBOUNDED PRECEDING) as seq_val
from sys_calendar.CALENDAR
qualify row_number () over (order by 1)<=10

Using a SQL query, how can I select every date within a range?

I'm querying a support ticket database, and each ticket has a column for "date opened" and "date closed." Tickets frequently remain open for multiple days, so we need to be able to pull the number of tickets that are OPEN on each day.
For example, for 4/8/14, we need to know how many tickets were opened on 4/8, combined with the total number of unclosed tickets that were opened prior to 4/8 but remained still open at 12:00am on 4/8 (may or may not have been closed during or after 4/8).
This seems straightforward enough for a single date, but now I need to write a query that will pull a complete range of dates.
For example, we need to write a query that returns every date between 1/1/14 and 4/10/14 along with the total number of tickets open on each date (including dates on which 0 tickets were open).
Is this possible using only queries or subqueries, without using any stored procedures or temporary datatables?
We're currently pulling the data into Excel and calculating the date stats there, but Excel is not a scalable solution and we'd like to have SQL perform this work so we can migrate this report to SSRS (SQL Server Reporting Services) down the road.
Your question is not very clear to me, but with SQLServer 2005 or better (SQLServer 2008 or better for the type Date) you can create a calendar. This one the way to do it
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (SELECT N - 1 FROM days WHERE N < 13)
SELECT DISTINCT CAST(DATEADD(DAY, days.n,
DATEADD(MONTH, months.n, '20131231')
) AS date)
FROM months
CROSS JOIN days
ORDER BY 1
if you need more year just add a new cte accordingly
SQLFiddle
You can't do it, without having some sort of date table that contains a row for each date possible. Creating a date table is pretty easy, depending on your RDBMS and your requirements.

Simplest way to process multiple hardcoded values in SQL?

How would I do this in SQL Server? (I know it won't run as written but it illustrates the question better than I can explain)
SELECT SQRT(number) WHERE number IN (4,9,16,25)
It would return multiple rows of course
you can use table value constructor
select sqrt(number)
from (
values (4),(9),(16),(25)
) as T(number)
or use union all
select sqrt(number)
from (
select 4 union all
select 9 union all
select 16 union all
select 25
) as T(number)
sql fiddle demo
You could create a derived table:
SELECT SQRT(number)
FROM (
SELECT 4 AS number
UNION ALL SELECT 9
UNION ALL SELECT 16
UNION ALL SELECT 25
) A

SQL two tables with different month values. Getting one result set for all the months

If I have two tables like this:
Table1
Month
1
3
Table 2
Month
1
4
How do I get the following result set?
Result Set
Month
1
3
4
Use the UNION operator like this:
SELECT Month FROM Table1
UNION
SELECT Month FROM Table2
The SQL UNION operator combines two or more SELECT statements and produces a single result with unique values.
SELECT [Month]
FROM Table1
UNION
SELECT [Month]
FROM Table2
You will want to use a UNION to combine the results into a single set.
SELECT Month
FROM Table1
UNION
SELECT Month
FROM Table2
By default UNION only grabs distinct values, therefore your exact result. If you wanted duplicate values you could use UNION ALL which would show month 1 in your example twice.
Here is a good tutorial that shows the differences as well.
Use UNION:
SELECT month FROM table1 UNION SELECT month FROM table2
SELECT Month FROM Table 1 UNION Select Month FROM Table 2 should do the trick
Here is my solution
declare # temptable table( months varchar(10) )
inserto into #temptable(months)
select distinct Month from Table1
inserto into #temptable(months)
select distinct Month from Table2
select disticnt * from #temptable
Maybe there is a better way?