2017-02-09 2 views
0

Rückgabe Sagen Sie SQL-Tabelle haben folgende:Erstellen effiziente MS SQL-Funktion gleichmäßig aufgeteilt Bereiche

-- create temp table 
CREATE TABLE [tempNums] 
(
    id INT NOT NULL, 
    somedate datetime NULL 
) 
GO 

mit einigen Daten (siehe unten für tempSplitStringToInts Definition):

-- with date 
INSERT INTO [tempNums] 
    SELECT id, GETUTCDATE() 
    FROM [tempSplitStringToInts] ('1,2,3,5,10,100,101,102,103,233,1001,5002,5003,5005,5007,5010',',') 
GO 

-- without date 
INSERT INTO [tempNums] 
    SELECT id, NULL 
    FROM [tempSplitStringToInts] ('6,7,8,150,151,152,153,433,2001,2002,2003,2005,3007,10010',',') 
GO 

Wie Sie bauen die BETTER/FASTER-Funktion, die die Anzahl der Bereiche annimmt, und ein Flag-Bit als Eingabe und gibt eine Tabelle mit Bereichswerten zurück?

So etwas wie dies funktioniert zum Beispiel, ist aber langsam für sehr große Tabellen:

-- create range function 
CREATE FUNCTION [tempFnGetIdRanges] 
(
    @apps INT, 
    @has_date BIT 
) 
RETURNS @ret TABLE 
(
    RangeNum INT, 
    MinNum INT, 
    MaxNum INT 
) 
AS 
BEGIN 

    DECLARE @i INT = 0; 
    DECLARE @count INT; 
    DECLARE @min INT; 
    DECLARE @max INT = 0; 

    IF @has_date = 1 
    BEGIN 
     SELECT @count = COUNT(id) 
      FROM [tempNums] 
      WHERE somedate IS NOT NULL 
    END 
    ELSE 
    BEGIN 
     SELECT @count = COUNT(id) 
      FROM [tempNums] 
      WHERE somedate IS NULL 

    END 

    DECLARE @top INT = @count/@apps; 

    WHILE @i<@apps 
    BEGIN 

     IF @[email protected] 
     BEGIN 
      -- on last get reminder 
      SET @top = @top + @apps 
     END 

     IF @has_date = 1 
     BEGIN  
      SELECT @min = MIN(id), @max = MAX(id) 
      FROM 
      (
       SELECT TOP (@top) id 
       FROM [tempNums] 
       WHERE somedate IS NOT NULL 
        AND id > @max 
       ORDER BY id 
      ) XX 
     END 
     ELSE 
     BEGIN 
      SELECT @min = MIN(id), @max = MAX(id) 
      FROM 
      (
       SELECT TOP (@top) id 
       FROM [tempNums] 
       WHERE somedate IS NULL 
        AND id > @max 
       ORDER BY id 
      ) XX 
     END 


     INSERT INTO @ret VALUES(@i, @min, @max) 

     SET @i = @i + 1; 
     CONTINUE 
    END 

    RETURN 
END 
GO 

Also, wenn Sie laufen folgende:

SELECT * FROM [tempFnGetIdRanges](4, 0) 
SELECT * FROM [tempFnGetIdRanges](4, 1) 

Ergebnis für die erste Aussage:

RangeNum MinNum MaxNum 
0   6  8 
1   150  152 
2   153  2001 
3   2002 10010 

Ergebnis für zweite Aussage:

RangeNum MinNum MaxNum 
0   1  5 
1   10  102 
2   103  5002 
3   5003 5010 

Split-Funktion (als Referenz, aber nicht der Sinn dieser Frage):

-- create split string function 
CREATE FUNCTION [tempSplitStringToInts] (@SourceString VARCHAR(MAX) , @delimeter VARCHAR(10)) 
RETURNS @IntList TABLE 
    (
    id INT 
    ) 
AS 
BEGIN 
IF RIGHT(@SourceString, LEN(@delimeter))<> @delimeter 
    BEGIN 
     SELECT @SourceString = @SourceString + @delimeter 
    END 

DECLARE @LocalStr VARCHAR(MAX) 
DECLARE @start INT 
DECLARE @end INT 
SELECT @start = 1 
SELECT @end = CHARINDEX (@delimeter , @SourceString , @start) 

WHILE @end > 0 
    BEGIN 
     SELECT @LocalStr = SUBSTRING (@SourceString , @start , @end - @start) 
     IF LTRIM(RTRIM(@LocalStr)) <> '' 
      BEGIN 
       INSERT @IntList (id) VALUES (CAST(@LocalStr AS INT)) 
      END 
     SELECT @start = @end + LEN(@delimeter) 
     SELECT @end = CHARINDEX (@delimeter , @SourceString , @start) 
    END 
    RETURN 
END 
GO 

Wie gesagt ich dies funktioniert, aber es ist langsam für sehr große Tabellen. Gibt es eine bessere Möglichkeit, tempFnGetIdRanges Funktion schreiben? Etwas natives zu SQL? Ich verwende MS SQL 2012, wenn das relevant ist.

+1

Sie haben hier bestimmte Leistungsanforderungen. Zuerst erstellen Sie Tabellenwertefunktionen, aber sie sind Tabellenfunktionen mit mehreren Anweisungen, die fast immer langsamer sind als selbst eine Skalarfunktion. Für den Leistungsvorteil einer Tabellenwertfunktion muss es eine einzelne Select-Anweisung und nichts mehr sein. Ich würde damit anfangen, den Splitter zu zerstören, den du hast. Es ist das Schlimmste des Schlimmsten. Es hat eine Schleife innerhalb einer Tabellenwertfunktion. Hier sind einige bessere Möglichkeiten dafür. http://sqlperformance.com/2012/07/t-sql-queries/split-strings –

+0

Großartig, danke für den Kommentar. Ich schaue mir bessere Splitterfunktionen an. – CrnaStena

+0

Was macht diese Hauptfunktion?Wenn HasDate = 1 ist, wird es nur in Gruppen aufgeteilt, aber wenn es null ist, macht die Ausgabe für mich keinen Sinn. Was ist die Logik dort? –

Antwort

1

Nicht wirklich sicher, was Ihre GetRanges-Funktion zu tun versucht, aber Sie brauchen definitiv keine Schleifen dafür. Diese Funktion gibt den gleichen Wert haben wie Sie, wenn Sie in HasDate als

1. passieren
create function GetRanges 
(
    @NumGroups int 
) returns table as return 

    with MyGroups as 
    (
     select NTILE(@NumGroups) over(order by t.id) as GroupNum 
      , t.id 
     from tempnums t 
    ) 

    select GroupNum 
     , MIN(id) as MinNum 
     , MAX(id) as MaxNum 
    from MyGroups 
    group by GroupNum 

--EDIT--

Nun, da ich sehe, Sie auf dem Laufenden zwei Sätze von Beispieldaten verstehe ich das Problem.

Hier ist, wie Sie dies anpassen können, um NULL oder NOT NULL in Somedate unterzubringen.

alter function GetRanges 
(
    @NumGroups int 
    , @HasDate bit 
) returns table as return 

    with MyGroups as 
    (
     select NTILE(@NumGroups) over(order by t.id) as GroupNum 
      , t.id 
     from tempnums t 
     where 
     (
      @HasDate = 1 
      AND 
      t.somedate is not null 
     ) 
     OR 
     (
      @HasDate = 0 
      AND 
      t.somedate is null 
     ) 
    ) 

    select GroupNum 
     , MIN(id) as MinNum 
     , MAX(id) as MaxNum 
    from MyGroups 
    group by GroupNum 

Das Problem, das ich sehe, ist, dass Sie nur 14 Zeilen, die NULL so nicht sicher, warum Sie die gewünschte Ausgabe, wie Sie es haben ist. Die Verwendung von NTILE führt aufgrund der Art und Weise, wie NTILE unebene Zeilen in Gruppen einfügt, zu leicht unterschiedlichen Ergebnissen in Ihren Beispieldaten.

+0

Die GetRanges-Funktion versucht, in jedem Bereich und für jeden Bereich MIN-, MAX-Nummern die gleiche Anzahl von Elementen (oder so nah wie möglich an derselben Zahl) zu erhalten. Also, wenn das Datum NULL ist, und für die folgenden IDs '6,7,8,150,151,152,153,433,2001,2002,2003,2005,3007,10010', brauche ich MIN und MAX für jeden Bereich. Wenn 4 Bereiche angefordert werden, wäre Bereich 1 6,7,8, wobei 6 min ist, 8 ist maximal, Bereich 2 wäre 150,151,152, wobei 150 min ist, 152 ist maximal, Bereich 3 wäre 153,433,2001 wo 433 ist min, 2001 ist max und last Range 4 wäre 2002,2003,2005,3007,10010 wobei 2002 min ist und 10010 ist max. – CrnaStena

+0

Warum 6,7 und 8? 151 - 153? Woher kommen diese anderen Zahlen? Von dem, was ich sehen kann, sind dies nur Zufallszahlen ohne Logik dahinter. Es muss eine Regel geben, die Sie nicht teilen. –

+0

Angenommen, Sie haben eine Tabelle in der DB, und die ID-Spalte ist die Identität, aber einige Zeilen werden gelöscht. Mein Beispiel ist willkürlich, aber ich habe versucht, zufällige Werte zu erstellen, um dies zu veranschaulichen. Ich kann kein Beispiel mit Tabellennamen und Werten in meiner Datenbank angeben. – CrnaStena

Verwandte Themen