Generating a sequence in sql server - sql-server-2005

I am working on a function that will take a low number and a high number as paramaters and returns a table containing everything between (and including).
I know I could use a cursor and increment a variable adding it to a scope based table every iteration, but I would prefer to avoid a cursor if possible. Does anyone else have a suggestion for a way to do this? (As i'm typing this im thinking possibly a CTE, which I will go investigate).

Yes, you can use a recursive CTE to do this. For example to generate numbers between 10 and 20 inclusive:
WITH f AS
(
SELECT 10 AS x
UNION ALL
SELECT x + 1 FROM f WHERE x < 20
)
SELECT * FROM f

Just create an indexed permanent auxiliary numbers table and be done with it. This will out perform any other method.
See Jeff Moden's answer here for more details and a script to populate such a table. if for some reason that isn't an option this should beat the recursive CTE according to the performance tests in the linked answer.
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT N FROM cteTally
WHERE N BETWEEN 10 AND 20

Related

SQL get only unique combination of two columns

I have table with:
A B
1 2
2 1
and i trying using sql command to get only one combination
A B
1 2
how can i do that?
A canonical way in standard SQL is:
select a, b
from t
where a < b
union all
select a, b
from t
where a > b and not exists (select 1 from t t2 where t2.a = t.b and t2.b = t.a);
Note that this assumes no duplicates or equal values. You can easily handle these using select distinct and <= comparisons. In my experience, this problem often arises when there are at most two rows per pair.
This preserves the original values. So, if you start with:
1 2
5 4
You will get that in the result set.
If you don't care about ordering, then many databases support least()/greatest():
select least(a, b) as a, greatest(a, b) as b
from t
group by least(a, b), greatest(a, b);
You can do the same thing with case expressions. Or, more simply as:
select distinct least(a, b) as a, greatest(a, b) as b
from t;

Availability in a reservation database

I have a database for hotel reservation with tables: room, customer, reservation (id, id_room, id_customer_ arrive_date, departure_date, ...).
When I select a room in my app I need to view a calendar widget with days red colored if in that day the room is busy.
I need a way to retrieve a list of busy days for a room,month,year combination.
My idea is to create a new table from previous with columns: date,day,month,year,room,is_busy and then query it.
SELECT day FROM new_table WHERE month=m AND year=y AND room=r AND is_busy=1
The problem is to update the new table every time.
Is there a simple way?
You can try this script. It will return busy dates for each room for next 255 days from now.
SELECT * INTO reservation
FROM (VALUES (1, 1, '2016-07-03','2016-07-06'),(2, 2, '2016-07-10','2016-07-15'))
a(CustomerID, RoomID, arrive_date, departure_date);
GO
;WITH Pass0 as (select 1 as C union all select 1),
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),
FutureDates(FutureDates) as (SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1, CAST(GETDATE() AS DATE)) FROM Pass3)
SELECT r.RoomID, f.FutureDates as BusyDate
FROM reservation as r
INNER JOIN FutureDates as f
ON f.FutureDates >= r.arrive_date and f.FutureDates < r.departure_date;

Create a Range From n to 1 in SQL

I need to create a range number from 1 to n.
For example, the parameter is #StartingValue
#StartingValue int = 96
Then the result should be:
Number
-------------
96
95
94
93
92
ff.
1
Does anyone have an idea how to do this?
Thank you.
Use a Tally Table to generate the numbers:
DECLARE #N INT = 96
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally(N) AS(
SELECT TOP(#N) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E8
)
SELECT * FROM CteTally ORDER BY N DESC
Explanation taken from Jeff's article (linked above):
The CTE called E1 (as in 10E1 for scientific notation) is nothing more
than ten SELECT 1's returned as a single result set.
E2 does a CROSS JOIN of E1 with itself. That returns a single result
set of 10*10 or up to 100 rows. I say "up to" because if the TOP
function is 100 or less, the CTE's are "smart" enough to know that it
doesn't actually need to go any further and E4 and E8 won't even come
into play. If the TOP has a value of less than 100, not all 100 rows
that E2 is capable of making will be made. It'll always make just
enough according to the TOP function.
You can follow from there. E4 is a CROSS JOIN of E2 and will make up
to 100*100 or 10,000 rows and E8 is a CROSS JOIN of E4 which will make
more rows than most people will ever need. If you do need more, then
just add an E16 as a CROSS JOIN of E8 and change the final FROM clause
to FROM E16.
What's really amazing about this bad-boy is that is produces ZERO
READS. Absolutely none, nada, nil.
One simple method is a numbers table. For a reasonable number (up to the low thousands), you can use spt_values:
with numbers as (
select top 96 row_number() over (order by (select null)) as n
from t
)
. . .
Another method is a recursive CTE:
with numbers as (
select 96 as n
union all
select n - 1
from numbers
where num > 1
)
For larger values, you'll need to use the MAXRECURSION option.
And another way.
SELECT N.number FROM
master..spt_values N
WHERE
N.type = 'P' AND
N.number BETWEEN 1 AND 96
ORDER BY N.number DESC
More details on spt_values What is the purpose of system table master..spt_values and what are the meanings of its values?
Sequance of no's can be generated by following ways:
1. Using row_number by querying a large table and get the sequance.
2. Using system tables as you can see other people comments.
3. Using recursive CTE.
declare #maxValue int = 96
; WITH rangetest AS
(
SELECT MinValue = #maxValue
UNION ALL
SELECT MinValue = MinValue - 1
FROM rangetest
WHERE MinValue > 1
)
SELECT *
from rangetest
OPTION (MAXRECURSION 0)

SQL Query to fetch numbers in given steps between a range

I have a set of data like this:
MinNo: 2500
MaxNo: 2700
IncrementStep: 10
Between the minimum number and the maximum number a list of all possible numbers with the given step are to be listed as shown below:
2500
2510
2520
2530
2540
2550
2560
2570
2580
2590
2600
2610
2620
2630
2640
2650
2660
2670
2680
2690
2700
I'm aware that this can be achieved using a while loop. Kindly let me know if this can be done using a select query using Common Table Expressions (if needed). Thanks in advance.
You can use a numbers table (or master..spt_values).
declare #MinNo int
declare #MaxNo int
declare #IncrementStep int
set #MinNo = 2500
set #MaxNo = 2700
set #IncrementStep = 10
select #MinNo + Number * #IncrementStep
from master..spt_values
where type = 'P' and
number between 0 and (#MaxNo - #MinNo) / #IncrementStep
Or a recursive CTE
;with C as
(
select #MinNo as Num
union all
select Num + #IncrementStep
from C
where Num < #MaxNo
)
select Num
from C
See this useful function
CREATE FUNCTION [dbo].[Sequence](#min INT, #max INT, #step INT)
RETURNS #ret TABLE (id INT PRIMARY KEY)
AS
BEGIN
WITH numbers(id) as
(
SELECT #min id
UNION ALL
SELECT id+#step
FROM numbers
WHERE id < #max
)
INSERT #ret
SELECT id FROM Numbers
OPTION(MAXRECURSION 0)
RETURN
END
#Mikael Eriksson already mentioned a numbers table / tally table (search for it online, there are LOTS of possible uses, many DBAs always want a tally table to be present in any system they manage)
I just wanted to share one non-recursive CTE-based "tally table" solution that I saw online the other day, that I think it amazingly elegant for its huge range (4 thousand million logical rows) and general applicability without any database dependencies:
WITH
E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT *
FROM cteTally
WHERE N >= 2500
AND N <= 2700
AND N % 10 = 0
I found it here, but I don't know whether that's the original source of this CTE.
The nice thing about it is that you don't need to worry about min, max, or step size, and yet it performs very well in most (one-off) situations. That said, it should NOT be used in any frequently-called business process; Any physical indexed numbers table will always perform better!
EDIT: I just searched a little more for the source of this method (I had missed the stackoverflow link in the article I referenced), and apparently it's originally attributed to Itzik Ben-Gan, from the bottom of page 255 in a book titled "Inside Microsoft SQL Server 2005 - T-SQL Querying" (says Jeff Moden, who I implicitly trust).

Generate random SQL Server 2008 time test data

I am trying to generate a large data set which includes time datatype in SQL Server 2008. I already have some non-time data in a table so I'd like to keep the entire process in T-SQL and use an insert-into-select to get the partial data from one table and insert it into the next along with some generated data including the time.
I'd like a way to generate random time(7)s between two points, say a random time between 8:00 and 9:00. I've found some pre-2008 post but nothing that addresses SQL Server 2008's time type.
There are 86,400,000 milliseconds in a day, so you can get a random time value by doing this:
select dateadd(millisecond, cast(86400000 * RAND() as int), convert(time, '00:00'))
For your example where you want times between 8:00 and 9:00, there are 3,600,000 milliseconds in an hour, so modify the query like this.
select dateadd(millisecond, cast(3600000 * RAND() as int), convert(time, '08:00'))
In order to put in into your new table, you might either do a T-SQL loop with updates (s...l...o...w...), or do a SELECT INTO from your original table into a new table.
To generate 100 rows of test data you can use the below.
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b)
SELECT TOP 100 CAST(DATEADD(SECOND,ABS(CHECKSUM(NEWID()))%3600,'08:00') AS TIME)
FROM E32