I have table called PhoneNumbers with columns Phone and Range as below
here in the phone column i have a phone numbers and in range column i have a range of values i need the phone numbers to be included.For the first phone number 9125678463 I need to include the phone numbers till the range 9125678465 ie (9125678463,9125678464,9125678465).Similarly for other phone numbers too.here is the sample destination table should look like
How can i write the sql to get this?
Thanks in advance
I have a solution which goes a classic way BUT: it does not need recursions and it does not need any loops! And it works even if your range has length of 3 or 5, or whatever...
first i create a table with numbers (from 1 to 1 million in this example - you can adopt this in TOP () clause):
SELECT TOP (1000000) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO dbo.Numbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX idx_numbers ON dbo.Numbers(n)
;
if you have that table it's pretty simple:
;WITH phonenumbers
AS
(
SELECT phone,
[range],
CAST(RIGHT(phone,LEN([range])) AS INT) AS number_to_increase,
CAST(LEFT(phone,LEN(phone)-LEN([range])) + REPLICATE('0',LEN([range])) AS BIGINT) AS base_number
FROM PhoneNumbers
)
SELECT p.base_number + num.n
FROM phonenumbers p
INNER JOIN dbo.Numbers num ON num.n BETWEEN p.number_to_increase AND p.[range]
You don't have to use a CTE like here - it's just to see a bit clearer what the idea behind this approach is. Maybe this suits for you
You can use CTE like this:
;WITH CTE (PhoneNumbers, [Range], i) AS (
SELECT CAST(Phone AS bigint), [Range], CAST(1 AS bigint)
FROM yourTable
UNION ALL
SELECT CAST(PhoneNumbers + 1 AS bigint), [Range], i + 1
FROM CTE
WHERE (PhoneNumbers + 1) % 10000 <= [Range]
)
SELECT PhoneNumbers
FROM CTE
ORDER BY PhoneNumbers
Here is one example of using a tally table. In my system I have that set of ctes as a view so I never have to write it again.
if OBJECT_ID('tempdb..#PhoneNumbers') is not null
drop table #PhoneNumbers;
create table #PhoneNumbers
(
Phone char(10)
, Range smallint
)
insert #PhoneNumbers
select 9135678463, 8465 union all
select 3279275678, 5679 union all
select 6372938103, 8105;
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select *
from #PhoneNumbers p
join cteTally t on t.N >= RIGHT(Phone, 4) and t.N <= Range
order by p.Phone
One more approach:
--Creating dummy table
select '9999991234' phone, '1237' rang into #tbl
union
select '9999995689', '5692'
SELECT [phone] low
,(CAST(9999995689/10000 AS bigINT) * 10000 + [Rang]) high
into #tbl1
FROM #tbl
--Creating 'numbrs' to have numbers between 0 & 9999 i.e. max range
select (rn-1)rn
into #numbrs
from
(select row_number() over (partition by null order by A.object_id) rn from sys.objects A
cross join sys.objects B)A
where rn between 0 and 9999
select (low + rn)phn from #numbrs cross join #tbl1
where (low + rn) between low and high
Related
I have the following T-SQL, used to generate some random values:
DECLARE #cnt INT = 0;
WHILE #cnt < 100
BEGIN
select
Random_String =
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
from
(select x='0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+') a
SET #cnt = #cnt + 1;
END;
This works well enough, except every string is returned as, what looks like, an independent result set.
Is there a way to refactor that query to return every value as one row in the same result set?
Environment is MS SQL Server 2008.
Thanks!
The best way to do this kind of thing is to forget about looping in t-sql. Using a numbers or tally table is a much better way to go about this.
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a cross join E1 b), --10E+2 or 100 rows
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E2
)
select
Random_String =
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
from
(select x='0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+') a
cross join cteTally t
where t.N < = 100
Here's a way to do it using a tally table:
;With Tally (N) As
(
Select 0 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
), Numbers (N) As
(
Select Row_Number() Over (Order By A.N) Num
From Tally A -- 10
Cross Join Tally B -- 100
Cross Join Tally C -- 1000
Cross Join Tally D -- 10000
Cross Join Tally E -- 100000
), LookupString (X) As
(
Select '0123456789ABCDEFGHJKLMNPQRSTUWXYZ%#-=+'
)
Select Random_String = substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)+
substring(x,(abs(checksum(newid()))%36)+1,1)
From LookupString
Cross Join Numbers
Where N <= 100
I have a table that ranges from 1-100000 but there are gaps in the ids where items have been deleted. I want a SQL statement that will return me a list of all unused ids in the table so I can get a list of items that were deleted.
I want the list but randomizing the list is a bonus actually. I think it can be done with a rand function...
I'd like to keep it ansi SQL if possible to maintain portability but if not, then that's ok...
You can do this with a use of Tally Table.
Create our sample data.
CREATE TABLE #ids(
id INT IDENTITY(1, 1)
)
SET IDENTITY_INSERT #ids ON
--Insert 100,000 rows
INSERT INTO #ids(id)
SELECT TOP 100000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM sys.columns a
CROSS JOIN sys.columns b
SET IDENTITY_INSERT #ids OFF;
-- Randomly delete 1000 rows
WITH cte AS(
SELECT TOP 1000 id
FROM #ids
ORDER BY NEWID()
)
DELETE FROM cte
Using a Tally Table, create a list of numbers from 1 - 100,000. Then use NOT EXISTS to get the unused ids. To randomize the list, add on ORDER BY NEWID() clause.
DECLARE #min INT = 1,
#max INT = 100000
;WITH E1(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
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b),
Tally(N) AS(
SELECT TOP(#max) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM E8
)
SELECT N
FROM Tally t
WHERE NOT EXISTS(
SELECT 1 FROM #ids WHERE id = t.N
)
ORDER BY NEWID() -- Sorts the result in a random order
I have a table with data like.
ItemCode
1000
1002
1003
1020
1060
I'm trying to write a SQL statement to get the minimum number (ItemCode) that is NOT in this table and it should be able to get the next lowest number once the previous minimum order ID has been inserted in the table but also skip the numbers that are already in the DB. I only want to get 1 result each time the query is run.
So, it should get 1001 as the first result based on the table above. Once the ItemCode = 1001 has been inserted into the table, the next result it should get should be 1004 because 1000 to 1003 already exist in the table.
Based on everything I have seen online, I think, I have to use a While loop to do this. Here is my code which I'm still working on.
DECLARE #Count int
SET #Count= 0
WHILE Exists (Select ItemCode
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
and convert(int,ItemCode) <= '1060')
Begin
SET #COUNT = #COUNT + 1
select MIN(ItemCode) + #Count
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
and convert(int,ItemCode) <= '1060'
END
I feel like there has to be an easier way to accomplish this. Is there a way for me to say...
select the minimum number between 1000 and 1060 that doesn't exist in table X
EDIT: Creating a new table isn't an option in my case
Final Edit: Thanks guys! I got it. Here is my final query that returns exactly what I want. I knew I was making it too complicated for no reason!
With T0 as ( select convert(int,ItemCode) + row_number() over (order by convert(int,ItemCode)) as ItemCode
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
And convert(int,ItemCode) <= '1060')
Select MIN(convert(varchar,ItemCode)) as ItemCode
from T0
where convert(int,ItemCode) Not in (Select convert(int,ItemCode)
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
and convert(int,ItemCode) <= '1060');
This should do the thing. Here you are generating sequantial number for rows, then comparing each row with next row(done by joining condition), and filtering those rows only where difference is not 1, ordering by sequence and finally picking the top most.
;with c as(select id, row_number() over(order by id) rn)
select top 1 c1.id + 1 as NewID
from c as c1
join c as c2 on c1.rn + 1 = c2.rn
where c2.id - c1.id <> 1
order by c1.rn
You could use row_number() to produce sequential values for each row, and then look for the first row where the row_number() doesn't match the value stored in the table. My SQL Server installation isn't working at the moment and SQL Fiddle seems to be down too, so I wrote this without being able to test it, but something like this should work:
declare #lowerBound int = 1000;
declare #upperBound int = 1060;
declare #x table ([id] int);
insert #x values (1000), (1002), (1003), (1020), (1060);
with [SequenceCTE] as
(
select
[id],
[seq] = (#lowerBound - 1) + row_number() over (order by [id])
from
#x
)
select top 1
[seq]
from
[SequenceCTE]
where
[seq] != [id] and
[seq] <= #upperBound;
EDIT: Here is a SQL Fiddle that demonstrates this approach. I don't know why the site wasn't working for me before. It doesn't seem to like my declare statements for some reason so I hard-coded the bounds instead, but hopefully it still gets the idea across.
You can do this using a Tally Table. Check this article by Jeff Moden for reference.
Basically, you want to generate numbers from #start to #end. That's where the Tally Table comes in. It will be used in the numbers generation. When you have your numbers, you just have to check for the minimum value that does not exist in your table.
SQL Fiddle
DECLARE #start INT = 1000
DECLARE #end INT = 1060
;WITH E1(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
)
,E2(N) AS(SELECT 1 FROM E1 a, E1 b)
,E4(N) AS(SELECT 1 FROM E2 a, E2 b)
,Tally(N) AS(
SELECT TOP(#end - #start + 1) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) - 1 FROM E4
)
SELECT
MIN(#start + t.N)
FROM Tally t
WHERE
#start + t.N <= #end
AND NOT EXISTS(
SELECT 1
FROM OITM
WHERE CAST(ItemCode AS INT) = #start + t.N
)
Here is another query that uses sys.columns to generate the Tally Table:
;WITH Tally(N) AS(
SELECT TOP(#end - #start + 1) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) - 1
FROM sys.columns a
--CROSS JOIN sys.columns b
)
SELECT
MIN(#start + t.N)
FROM Tally t
WHERE
#start + t.N <= #end
AND NOT EXISTS(
SELECT 1
FROM OITM
WHERE CAST(ItemCode AS INT) = #start + t.N
)
I'm working on an app that should find 26-letter code in char(26) column out of 5,760,000 rows. I need to know how long it's going to take. I'm using MS SQL Server 2012 Express.
I have a database which has only one table, myTable:
Idcolumn integer
CodeColumn char(26)
DateAndTimeColumn datetime
Column 'CodeColumn' has an index.
IdColumn is simply integer ID.
CodeColumn has "00592098715648275649283746" format (this is an example).
DateAndTimeColumn is a timestamp.
I would like to populate this table with data to do some tests and to find out how long it is going to take to get an answer from the database. I don't know how to write proper tsql statement to populate my table with 5,760,000 rows. Especially that second column is very long. How can I populate the table to get my table populated?
Let's say the data should be like this when I use statement
SELECT IdColumn, CodeColumn, DateAndTimeColumn FROM myTable;
Output:
1 00000000000000000000000001 2014-11-19 15:46:50.843
2 00000000000000000000000002 2014-11-19 15:46:54.310
3 00000000000000000000000003 2014-11-19 15:46:56.060
and so on ... till 5,760,000 rows.
How can I do that?
;WITH Numbers AS
(
SELECT TOP (5760000)
IdColumn = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
CROSS JOIN sys.all_objects AS s3
)
INSERT INTO dbo.YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM Numbers;
Here is another way to do this using Lamak's excellent example. The only difference is this will create a 10 million row cte with zero reads. When you use sys.all_objects it can get extremely slow because of all the I/O.
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b),
E4(N) AS (SELECT 1 FROM E2 a, E2 b),
E6(N) AS (SELECT 1 from E4 a, E2 b, E1 c),
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E6
)
INSERT INTO dbo.YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM cteTally
where cteTally.N <= 5760000
Very similar to Lamak answer, this will not depends on your database structure:
;WITH Numbers AS
(
SELECT 1 AS id UNION SELECT 2 UNION SELECT 3 UNION
SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION
SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION
SELECT 10
),
Joins AS
(
SELECT TOP (5760000)
IdColumn = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[id]))
FROM Numbers AS s1 --10
CROSS JOIN Numbers AS s2 --100
CROSS JOIN Numbers AS s3 --1.000
CROSS JOIN Numbers AS s4 --10.000
CROSS JOIN Numbers AS s5 --100.000
CROSS JOIN Numbers AS s6 --1.000.000
CROSS JOIN Numbers AS s7 --10.000.000
)
INSERT INTO #YourTable
SELECT IdColumn,
RIGHT(REPLICATE('0',26)+CONVERT(VARCHAR(26),IdColumn),26) CodeColumn,
GETDATE() DateAndTimeColumn
FROM Joins;
It just generate a list of numbers from 1 to 10 and then it crossjoins all the way to 10^7.
Simplifying my problem down - I have 6-digit field which assigns numbers to customers starting from 1 and ending to 999999. Most numbers are sequentially assigned, but numbers can be assigned manually by users, and this feature has been used in an unpredicatable pattern throughout the range.
We now need to identify numbers that have not been assigned (easy) - and then convert this into a number of ranges (seems complex).
For example given the following numbers have been assigned
1,2,3,4,5,
1001,1002,1003,1004,1005,
999101,999102,999103,999104,999105
I need a resulting set of ranges like
Start End
6 1000
1006 999100
999106 999999
My thinking so far is this is probably too complex to write in queries - and best achieved by looping from 1 to 999999, and adding ranges to a temp table.
Interested to hear ideas as I can imagine there are a few approaches. I'm using SQL Server 2008 R2. This is a one-off exercise so even a non-SQL solution might be appropriate, if this were for example easily done in Excel.
Try this
declare #t table (num int)
insert #t values (2),(3),(6),(7),(9),(10),(11)
select
MIN(number) as rangestart,
MAX(number) as rangeend
from
(
select *,
ROW_NUMBER() over (order by number) -
ROW_NUMBER() over (order by num,number) grp
from
(
select number from master..spt_values where type='p' and number between 1 and 15
) numbers
left join #t t
on numbers.number = t.num
) v
where num is null
group by grp
Reference : gaps and islands by itzik ben-gan
To create a numbers query upto 999999
select p1.number + p2.number * 2048 as number
from
(select * from master..spt_values where type='p' ) p1,
(select * from master..spt_values where type='p' and number<489) p2
where p1.number + p2.number * 2048 <=999999
declare #t table (num int)
insert #t values
(2),(3),(4),(5),
(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
;with cte as
(
select num,(ROW_NUMBER() OVER(ORDER BY num)) + 1 as idx from #t
union
select 0 [num],1 [idx] --start boundary
union
select 1000000 [num],COUNT(num) + 2 [idx] from #t --end boundary
)
select c1.num + 1 [Start], c2.num - 1 [End]
from cte c1
inner join cte c2 on c2.idx = c1.idx + 1
where c2.num != c1.num + 1
create table #temp (id int)
insert into #temp (id)
values (1),(2),(3),(1000),(1001),(1002),(2000)
--drop table #temp
with cte as
(
select *, ROW_NUMBER() over(order by id) as rn
from #temp a
)
select a.id + 1, b.id - 1
from cte a join cte b on a.rn = b.rn - 1 and a.id <> b.id -1
it wont include tail ranges, like 2001-9999
Here is SQLFiddle demo
select
case when max(n1)=0 then 1 else max(n1)end,
case when max(n2)=0 then 999999 else max(n2)end
from
(
select t.n+1 as n1,0 n2,
row_number() over(order by t.n)
+isnull((select 0 from t where n=1),1)
rn
from t
left join t t2 on t.n+1=t2.n
where t2.n is null
union all
select 0 n1, t.n-1 as n2 ,
row_number() over(order by t.n) rn
from t
left join t t2 on t.n-1=t2.n
where t2.n is null
and t.n>1
) t3
group by rn
declare #t table(id int)
insert #t values
(1),(2),(3),(4),(5),(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
select t1.id+1 [start], coalesce(t3.[end], 999999) [end]
from #t t1
left join #t t2 on t1.id +1 = t2.id
cross apply
(select min(id)-1 [end] from #t where t1.id < id
) t3
where t2.id is null
if you have a table called "kh" for example with a column "myval" which is your list of integers you could try this SELECT.
SELECT MAX(t1.myval+1) AS 'StartRange',t3.myval-1 AS 'EndRange'
FROM kh t1, kh t3
WHERE t1.myval+1 NOT IN (SELECT myval FROM kh t2 ORDER BY myval)
AND t3.myval-1 NOT IN (SELECT myval FROM kh t4 ORDER BY myval)
AND t1.myval < t3.myval
GROUP BY t3.myval
ORDER BY StartRange