Создать вставку, используя выражение Alphabet - PullRequest
0 голосов
/ 05 февраля 2020

У меня есть алфавитный столбец, подобный этому CTE:

;with 
cte_tally as
(
select row_number() over (order by (select 1)) as n 
from sys.all_columns
)
select 
  char(n) as alpha
from 
  cte_tally
where
  (n > 64 and n < 91) ;
go

Мой вопрос: как я могу получить диапазон внутри этого алфавита, я имею в виду, если я создаю хранимую процедуру, которая получает два параметра

@FirstLetter CHAR(1)
,@FinalLetter CHAR(2)

Значение FirstLetter равно "C" Значение FinalLetter равно "F"

Сначала я хочу узнать, сколько пробелов находится между C и F

И, наконец, Я хочу создать что-то вроде WHILE функции, используя эти пробелы и все буквы между C и F (включая C и F)

DECLARE @NumberOfSpaces INT
        ,@FinalFlag INT = 1 ;
SET @NumberOfSpaces = spacesavailable

WHILE(@FinalFlag < NumberOfSpaces)
BEGIN
INSERT INTO .... VALUES (CONCAT(TEST - @FirstLetter))
END

и сделать это, когда все буквы находятся между C и F (включая C и F)

1 Ответ

1 голос
/ 05 февраля 2020

Чтобы создать диапазон букв, вы можете использовать rangeAB (для этого и для множества других вещей.) Обратите внимание:

DECLARE 
  @firstLetter CHAR(1) = 'C', 
  @lastLetter  CHAR(1) = 'F';

SELECT letter = CHAR(r.N1)
FROM   core.rangeAB(ASCII(@firstLetter),ASCII(@lastLetter),1,1) AS r;

Возвращает:

letter
------
C
D
E
F

Если я правильно понимаю, что вам нужно, вы можете вычислить «пробелы» следующим образом:

DECLARE 
  @firstLetter CHAR(1) = 'C', 
  @lastLetter  CHAR(1) = 'F';

SELECT Spaces = ASCII(@lastLetter)-ASCII(@firstLetter); 

Это будет означать, что между C & есть 3 пробела F . Это может быть спорным.

Диапазон AB DDL:

CREATE FUNCTION core.rangeAB
(
  @Low  BIGINT, -- (start) Lowest  number in the set
  @High BIGINT, -- (stop)  Highest number in the set
  @Gap  BIGINT, -- (step)  Difference between each number in the set
  @Row1 BIT     -- Base: 0 or 1; should RN begin with 0 or 1?
)
/****************************************************************************************
[Purpose]:
 Creates a lazy, in-memory, forward-ordered sequence of up to 531,441,000,000 integers
 starting with @Low and ending with @High (inclusive). RangeAB is a pure, 100% set-based
 alternative to solving SQL problems using iterative methods such as loops, cursors and
 recursive CTEs. RangeAB is based on Itzik Ben-Gan's getnums function for producing a
 sequence of integers and uses logic from Jeff Moden's fnTally function which includes a
 parameter for determining if the "row-number" (RN) should begin with 0 or 1.

 I wanted to use the name "Range" because it functions and performs almost identically to
 the Range function built into Python and Clojure. RANGE is a reserved SQL keyword so I 
 went with "RangeAB". Functions/Algorithms developed using rangeAB can be easilty ported
 over to Python, Clojure or any other programming language that leverages a lazy sequence.
 The two major differences between RangeAB and the Python/Clojure versions are:
   1. RangeAB is *Inclusive* where the other two are *Exclusive". range(0,3) in Python and
      Clojure return [0 1 2], core.rangeAB(0,3) returns [0 1 2 3].
   2. RangeAB has a fourth Parameter (@Row1) to determine if RN should begin with 0 or 1.

[Author]:
 Alan Burstein

[Compatibility]: 
 SQL Server 2008+

[Syntax]:
 SELECT r.RN, r.OP, r.N1, r.N2
 FROM   core.rangeAB(@Low,@High,@Gap,@Row1) AS r;

[Parameters]:
 @Low  = BIGINT; represents the lowest  value for N1.
 @High = BIGINT; represents the highest value for N1.
 @Gap  = BIGINT; represents how much N1 and N2 will increase each row. @Gap is also the 
                 difference between N1 and N2.
 @Row1 = BIT;    represents the base (first) value of RN. When @Row1 = 0, RN begins with 0,
                 when @row = 1 then RN begins with 1.

[Returns]:
 Inline Table Valued Function returns:
 RN = BIGINT; a row number that works just like T-SQL ROW_NUMBER() except that it can 
      start at 0 or 1 which is dictated by @Row1. If you need the numbers: 
      (0 or 1) through @High, then use RN as your "N" value, ((@Row1=0 for 0, @Row1=1),
      otherwise use N1.
 OP = BIGINT; returns the "finite opposite" of RN. When RN begins with 0 the first number 
      in the set will be 0 for RN, the last number in will be 0 for OP. When returning the
      numbers 1 to 10, 1 to 10 is retrurned in ascending order for RN and in descending 
      order for OP.
      Given the Numbers 1 to 3, 3 is the opposite of 1, 2 the opposite of 2, and 1 is the
      opposite of 3. Given the numbers -1 to 2, the opposite of -1 is 2, the opposite of 0
      is 1, and the opposite of 1 is 0.
      The best practie is to only use OP when @Gap > 1; use core.O instead. Doing so will
      improve performance by 1-2% (not huge but every little bit counts)      
 N1 = BIGINT; This is the "N" in your tally table/numbers function. this is your *Lazy* 
      sequence of numbers starting at @Low and incrimenting by @Gap until the next number
      in the sequence is greater than @High.
 N2 = BIGINT; a lazy sequence of numbers starting @Low+@Gap and incrimenting by @Gap. N2
      will always be greater than N1 by @Gap. N2 can also be thought of as:
      LEAD(N1,1,N1+@Gap) OVER (ORDER BY RN)

[Dependencies]:
 N/A

[Developer Notes]:
 1.  core.rangeAB returns one billion rows in exactly 90 seconds on my laptop:
     4X 2.7GHz CPU's, 32 GB - multiple versions of SQL Server (2005-2019)       
 2.  The lowest and highest possible numbers returned are whatever is allowable by a 
     bigint. The function, however, returns no more than 531,441,000,000 rows (8100^3). 
 3.  @Gap does not affect RN, RN will begin at @Row1 and increase by 1 until the last row
     unless its used in a subquery where a filter is applied to RN.
 4.  @Gap must be greater than 0 or the function will not return any rows.
 5.  Keep in mind that when @Row1 is 0 then the highest RN value (ROWNUMBER) will be the 
     number of rows returned minus 1
 6.  If you only need is a sequential set beginning at 0 or 1 then, for best performance
     use the RN column. Use N1 and/or N2 when you need to begin your sequence at any 
     number other than 0 or 1 or if you need a gap between your sequence of numbers. 
 7.  Although @Gap is a bigint it must be a positive integer or the function will
     not return any rows.
 8.  The function will not return any rows when one of the following conditions are true:
       * any of the input parameters are NULL
       * @High is less than @Low 
       * @Gap is not greater than 0
     To force the function to return all NULLs instead of not returning anything you can
     add the following code to the end of the query:

       UNION ALL 
       SELECT NULL, NULL, NULL, NULL
       WHERE NOT (@High&@Low&@Gap&@Row1 IS NOT NULL AND @High >= @Low AND @Gap > 0)

     This code was excluded as it adds a ~5% performance penalty.
 9.  There is no performance penalty for sorting by RN ASC; there is a large performance 
     penalty, however for sorting in descending order. If you need a descending sort the
     use OP in place of RN then sort by rn ASC. 
 10. When setting the @Row1 to 0 and sorting by RN you will see that the 0 is added via
     MERGE JOIN concatination. Under the hood the function is essentially concatinating
     but, because it's using a MERGE JOIN operator instead of concatination the cost 
     estimations are needlessly high. You can circumvent this problem by changing:
     ORDER BY core.rangeAB.RN to: ORDER BY ROW_NUMBER() OVER (ORDER BY (SELECT NULL))

*** Best Practices ***
--===== 1. Using RN (rownumber)
 -- (1.1) The best way to get the numbers 1,2,3...@High (e.g. 1 to 5):
 SELECT r.RN
 FROM   core.rangeAB(1,5,1,1) AS r;

 -- (1.2) The best way to get the numbers 0,1,2...@High (e.g. 0 to 5):
 SELECT r.RN
 FROM   core.rangeAB(0,5,1,0) AS r;

--===== 2. Using OP for descending sorts without a performance penalty
 -- (2.1) Best Practice for getting the numbers 5,4,3,2,1 (5 to 1):
 SELECT   r.OP
 FROM     core.rangeAB(1,5,1,1) AS r 
 ORDER BY R.RN;

 -- (2.2) Best Practice for getting the numbers 5,4,3,2,1,0 (5 to 0):
 SELECT   r.OP 
 FROM     core.rangeAB(0,5,1,0) AS r
 ORDER BY r.RN ASC;

 -- (2.3) (ADVANCED) - Ex 2.2. (above) but with better query plan estimations (compare both)
 SELECT   r.OP 
 FROM     core.rangeAB(0,5,1,0) AS r
 ORDER BY ROW_NUMBER() OVER (ORDER BY (SELECT NULL));
 -- This will leverage concatination operator instead of a merge join union;
 -- This will not improve performance but the exection plan will include better estimations
;
 -- (2.4) (ADVANCED) The BEST way (leveraging core.O)
 SELECT      o.OP
 FROM        core.rangeAB(0,5,1,0) AS r
 CROSS APPLY core.O(0,5,r.RN)      AS o
 ORDER BY    ROW_NUMBER() OVER (ORDER BY (SELECT NULL));
 -- Note that core.rangeAB.Op is best when there are gaps (@Gap > 1)

--===== 3. Using N1
 -- (3.1) To begin with numbers other than 0 or 1 use N1 (e.g. -3 to 3):
 SELECT r.N1
 FROM   core.rangeAB(-3,3,1,1) AS r;

 -- (3.2) ROW_NUMBER() is built in. If you want a ROW_NUMBER() include RN:
 SELECT r.RN, r.N1
 FROM   core.rangeAB(-3,3,1,1) AS r;

 -- (3.3) If you wanted a ROW_NUMBER() that started at 0 you would do this:
 SELECT r.RN, r.N1
 FROM   core.rangeAB(-3,3,1,0) AS r;

 -- (3.4) Ex 3.3. Guaranteed ORDER BY without a sort in the execution plan
 SELECT   r.RN, r.N1
 FROM     core.rangeAB(-3,3,1,0) AS r
 ORDER BY r.RN;

 -- (3.5) Ex 3.4. But with better cost estimations (similar to ex 2.4)
 SELECT   r.RN, r.N1
 FROM     core.rangeAB(-3,3,1,0) AS r
 ORDER BY    ROW_NUMBER() OVER (ORDER BY (SELECT NULL));

--===== 4. Using N2 and @Gap
 -- (4.1) To get 0,10,20,30...100, set @Low to 0, @High to 100 and @Gap to 10:
 SELECT r.N1
 FROM   core.rangeAB(0,100,10,1) AS r;

 -- (4.2) Adding N2
   -- Note that N2=N1+@Gap; this allows you to create a sequence of ranges.
   -- For example, to get (0,10),(10,20),(20,30).... (90,100):
 SELECT r.N1, r.N2
 FROM  core.rangeAB(0,90,10,1) AS r;

 -- (4.3) Remember that a rownumber is included and it can begin at 0 or 1:
 SELECT r.RN, r.N1, r.N2
 FROM   core.rangeAB(0,90,10,1) AS r;

[Examples]:
--===== 1. Generating Sample data (using rangeAB to create "dummy rows")
 -- The query below will generate 10,000 ids and random numbers between 50,000 and 500,000
 SELECT
   someId    = r.RN,
   someNumer = ABS(CHECKSUM(NEWID())%450000)+50001 
 FROM core.rangeAB(1,10000,1,1) AS r;

--===== 2. Create a series of dates; rn is 0 to include the first date in the series
 DECLARE @StartDate DATE = '20180101', @enddate DATE = '20180131';

 SELECT r.RN, calDate = DATEADD(dd, r.RN, @StartDate)
 FROM   core.rangeAB(1, DATEDIFF(dd,@StartDate,@enddate),1,0) AS r;
 GO

--===== 3. Splitting (tokenizing) a string with fixed sized items
 -- given a delimited string of identifiers that are always 7 characters long
 DECLARE @String VARCHAR(1000) = 'A601225,B435223,G008081,R678567';

 SELECT
   itemNumber = r.RN, -- item's ordinal position 
   itemIndex  = r.N1, -- item's position in the string (it's CHARINDEX value)
   item       = SUBSTRING(@String, r.N1, 7) -- item (token)
 FROM core.rangeAB(1, LEN(@String), 8,1) AS r;
 GO

--===== 4. Splitting (tokenizing) a string with random delimiters
 DECLARE @String VARCHAR(1000) = 'ABC123,999F,XX,9994443335';

 SELECT
   itemNumber = ROW_NUMBER() OVER (ORDER BY r.RN), -- item's ordinal position 
   itemIndex  = r.N1+1, -- item's position in the string (it's CHARINDEX value)
   item       = SUBSTRING
               (
                 @String,
                 r.N1+1,
                 ISNULL(NULLIF(CHARINDEX(',',@String,r.N1+1),0)-r.N1-1,8000)
               ) -- item (token)
 FROM  core.rangeAB(0,DATALENGTH(@String),1,1) AS r
 WHERE SUBSTRING(@String,r.N1,1) = ',' OR r.N1 = 0;
 -- logic borrowed from: http://www.sqlservercentral.com/articles/Tally+Table/72993/

--===== 5. Grouping by a weekly intervals
 -- 5.1. how to create a series of start/end dates between @StartDate & @endDate
 DECLARE @StartDate DATE = '1/1/2015', @endDate DATE = '2/1/2015';
 SELECT 
   WeekNbr   = r.RN,
   WeekStart = DATEADD(DAY,r.N1,@StartDate), 
   WeekEnd   = DATEADD(DAY,r.N2-1,@StartDate)
 FROM core.rangeAB(0,datediff(DAY,@StartDate,@EndDate),7,1) AS r;
 GO

 -- 5.2. LEFT JOIN to the weekly interval table

 DECLARE @StartDate DATETIME = '1/1/2015', @endDate DATETIME = '2/1/2015';
 BEGIN
   -- sample data 
   DECLARE @loans TABLE (loID INT, lockDate DATE);
   INSERT  @loans 
   SELECT r.RN, DATEADD(DD, ABS(CHECKSUM(NEWID())%32), @StartDate)
   FROM   core.rangeAB(1,50,1,1) AS r;

   -- solution 
   SELECT 
     WeekNbr   = r.RN,
     WeekStart = dt.WeekStart, 
     WeekEnd   = dt.WeekEnd,
     total     = COUNT(l.lockDate)
   FROM core.rangeAB(0,datediff(DAY,@StartDate,@EndDate),7,1) AS r
   CROSS APPLY (VALUES (
     CAST(DATEADD(DAY,r.N1,@StartDate) AS DATE), 
     CAST(DATEADD(DAY,r.N2-1,@StartDate) AS DATE))) dt(WeekStart,WeekEnd)
   LEFT JOIN @loans l ON l.LockDate BETWEEN  dt.WeekStart AND dt.WeekEnd
   GROUP BY  r.RN, dt.WeekStart, dt.WeekEnd ;
 END;

--===== 6. Identify the first vowel and last vowel in a along with their positions
 DECLARE @String VARCHAR(200) = 'This string has vowels';

 BEGIN
   SELECT TOP(1) Position = r.RN, Letter = SUBSTRING(@String,r.RN,1)
   FROM     core.rangeAB(1,LEN(@String),1,1) AS r
   WHERE    SUBSTRING(@String,r.RN,1) LIKE '%[aeiou]%'
   ORDER BY r.RN;

   -- To avoid a sort in the execution plan we'll use OP instead of RN
   SELECT TOP(1) position = r.OP, letter = SUBSTRING(@String,r.OP,1)
   FROM     core.rangeAB(1,LEN(@String),1,1) AS r
   WHERE    SUBSTRING(@String,r.RN,1) LIKE '%[aeiou]%'
   ORDER BY r.RN;
 END;

-----------------------------------------------------------------------------------------
[Revision History]:
 Rev 00 - 20140518 - Initial Development - AJB
 Rev 01 - 20151029 - Added 65 rows. Now L1=465; 465^3=100.5M. Updated comments - AJB
 Rev 02 - 20180613 - Complete re-design including opposite number column (op)
 Rev 03 - 20180920 - Added additional CROSS JOIN to L2 for 530B rows max - AJB
 Rev 04 - 20190306 - Added inline aliasing function(f): 
                     f.R=(@High-@Low)/@Gap, f.N=@Gap+@Low - AJB
 Rev 05 - 20191122 - Developed this "core" version for open source distribution;
                     updated notes and did some final code clean-up 
*****************************************************************************************/
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH
L1(N) AS 
(
  SELECT 1
  FROM (VALUES
   ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),
   ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),
   ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),
   ($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),($),
   ($),($)) T(N) -- 90 values
),
L2(N)      AS (SELECT 1 FROM L1 a CROSS JOIN L1 b CROSS JOIN L1 c),
iTally(RN) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) FROM L2 a CROSS JOIN L2 b)
SELECT r.RN, r.OP, r.N1, r.N2
FROM
(
  SELECT
    RN = 0,
    OP = (@High-@Low)/@Gap,
    N1 = @Low,
    N2 = @Gap+@Low
  WHERE @Row1 = 0
  UNION ALL   -- (@High-@Low)/@Gap+1:
  SELECT TOP (ABS((ISNULL(@High,0)-ISNULL(@Low,0))/ISNULL(@Gap,0)+ISNULL(@Row1,1)))
    RN = i.RN,
    OP = (@High-@Low)/@Gap+(2*@Row1)-i.RN,
    N1 = (i.rn-@Row1)*@Gap+@Low,
    N2 = (i.rn-(@Row1-1))*@Gap+@Low
  FROM       iTally AS i
  ORDER BY   i.RN
) AS r
WHERE @High&@Low&@Gap&@Row1 IS NOT NULL AND @High >= @Low 
AND   @Gap > 0;
...