2012-04-24 11 views
47

Ich habe die folgende SQL-Abfrage:SQL Server für jede Schleife

DECLARE @MyVar datetime = '1/1/2010'  
SELECT @MyVar 

Dieses natürlich gibt '1/1/2010'.

Was ich tun möchte, ist eine Liste der Daten haben, sagen:

1/1/2010 
2/1/2010 
3/1/2010 
4/1/2010 
5/1/2010 

Dann möchte ich FÜR JEDES durch die Zahlen und die SQL-Abfrage ausführen.

So etwas wie (Pseudo-Code):

List = 1/1/2010,2/1/2010,3/1/2010,4/1/2010,5/1/2010 

For each x in List 
do 
    DECLARE @MyVar datetime = x 

    SELECT @MyVar 

zurückkommen würde also: -

1/1/2010 2/1/2010 3/1/2010 4/1/2010 5/1/2010

Ich möchte, dass die Daten als ein Resultset zurückgegeben werden, nicht mehrere Resultsets, so dass ich am Ende der Abfrage möglicherweise eine Art Union verwenden muss, so dass jede Iteration der Schleife auf der nächste .

bearbeiten

Ich habe eine große Abfrage, die eine ‚to date‘ Parameter akzeptiert, muss ich es 24-mal laufen, jedes Mal mit einem bestimmten bisher die ich brauche, um liefern zu können (diese Datumsangaben werden dynamisch sein. Ich möchte vermeiden, meine Abfrage 24 Mal zu wiederholen, wenn die Gewerkschaftsmitglieder sich ihnen anschließen, als ob ich zurückkommen und zusätzliche Spalten hinzufügen müsste, was sehr zeitaufwendig wäre. Hier

+9

Können Sie erklären, warum Sie dies tun müssen? 95% der Zeit, wenn Sie eine Schleifenstruktur in tSQL benötigen, tun Sie es wahrscheinlich falsch. – JohnFx

+2

Warum erstellen Sie keine Tabelle, in der Sie die Daten angeben können, für die Sie sie ausführen möchten. Es gibt fast sicher einen besseren Weg, dies zu tun, als durch fest codierte konstante Werte zu schauen. – JohnFx

+0

Die Daten in Ihrem Beispiel sind sequenziell nach Monat. Ist das eine Regel, oder müssen Sie in der Lage sein, für einen beliebigen Zeitraum zu laufen? Gibt es einen Grund, warum Sie die große Abfrage nicht so bearbeiten können, dass sie einen Datumsbereich oder eine Datumsgruppe anstelle eines einzelnen Datums verwendet? Wenn Sie unbedingt Iterationen durchlaufen müssen (gegen den guten Ratschlag oben), dann sollten Sie einen Cursor in Betracht ziehen. – JAQFrost

Antwort

51

SQL in erster Linie eine Reihe orientierte Sprache ist - es ist generell eine schlechte Idee, eine Schleife in es zu benutzen.

with cte as 
(select 1 i union all 
select i+1 i from cte where i < 5) 
select dateadd(d, i-1, '2010-01-01') from cte 
+4

Die maximale Stufe von "i" ist auf 100 begrenzt, was der maximalen Rekursionsgrenze entspricht. Versuchen Sie '... aus CTE wo i <= 101' Oder erhöhen Sie die Rekursionsbegrenzung durch' OPTION (MAXRECURSION 500) ' – guneysus

26

ist eine Option mit einer Tabelle Variable:

DECLARE @MyVar TABLE(Val DATETIME) 
DECLARE @I INT, @StartDate DATETIME 
SET @I = 1 
SET @StartDate = '20100101' 

WHILE @I <= 5 
BEGIN 
    INSERT INTO @MyVar(Val) 
    VALUES(@StartDate) 

    SET @StartDate = DATEADD(DAY,1,@StartDate) 
    SET @I = @I + 1 
END 
SELECT * 
FROM @MyVar 

Sie das gleiche mit einer temporären Tabelle kann:

CREATE TABLE #MyVar(Val DATETIME) 
DECLARE @I INT, @StartDate DATETIME 
SET @I = 1 
SET @StartDate = '20100101' 

WHILE @I <= 5 
BEGIN 
    INSERT INTO #MyVar(Val) 
    VALUES(@StartDate) 

    SET @StartDate = DATEADD(DAY,1,@StartDate) 
    SET @I = @I + 1 
END 
SELECT * 
FROM #MyVar 

sollten Sie sagen uns, was Ihr Hauptziel ist es, wie gesagt Bei @JohnFx könnte dies wahrscheinlich auf eine andere (effizientere) Art und Weise geschehen.

+0

Ich brauche Daten nicht ganze Zahlen, siehe editierte Frage. Würde eine WHILE-Schleife mit Daten arbeiten? – JsonStatham

+0

@SelectDistinct - Ja, es sollte gut funktionieren. Ich änderte meine Antwort auf Rückgabedaten statt Ints – Lamak

+1

Mark Bannisters Antwort ist weniger Code und effizienter. Es gibt keinen Grund, eine While-Schleife für so etwas zu verwenden. – Sorpigal

13

Sie könnten eine variable Tabelle wie folgt verwenden:

declare @num int 

set @num = 1 

declare @results table (val int) 

while (@num < 6) 
begin 
    insert into @results (val) values (@num) 
    set @num = @num + 1 
end 

select val from @results 
+0

+1 das hat gut funktioniert. –

6

zu Diese Art hängt davon ab, was Sie wollen mit den Ergebnissen tun:

In diesem Fall könnte ein ähnliches Ergebnis einer rekursiven CTE erreicht werden unter Verwendung. Wenn Sie gerade hinter den Zahlen sind, wäre eine Set-basierte Option eine numbers table - die für alle möglichen Dinge nützlich ist.

Für MSSQL 2005+ können Sie eine rekursive CTE verwenden, um eine Tabelle Zahlen inline zu generieren:

;WITH Numbers (N) AS (
    SELECT 1 UNION ALL 
    SELECT 1 + N FROM Numbers WHERE N < 500 
) 
SELECT N FROM Numbers 
OPTION (MAXRECURSION 500) 
+0

Interessant, dass Sie dies als" set-based option "betrachten (das Wort' RECURSION' ist ein bisschen von einem verschenken!) – onedaywhen

+0

@onedaywhen - dort gibt es keinen Widerspruch. Ein rekursiver CTE * ist * satzbasiert. Das Ankerelement (SELECT 1) wird auf S [0] gesetzt, das dann UNION ALL mit n weiteren Sätzen (SELECT 1 + N FROM S [n - 1]) wird, bis eine leere Menge angetroffen wird. Das Ergebnis ist die UNION der Mengen S [0] bis S [n]. Das gesagt - ich persönlich bevorzuge einen physischen Tisch, da es effizienter ist. –

+0

[Joe Celkos Gedanken zu diesem Thema] (http://www.simple-talk.com/sql/t-sql-programming/procedural,-semi-procedural-and-declarative-programming-part-ii/): es macht den semiprozeduralen Programmierer gut, ein [rekursives] CTE zu benutzen ... Aber Rekursion ist eigentlich eine prozedurale Technik. Es ist auch teuer, weil es wirklich ein Cursor unter der Decke ist "- ich sage nicht, dass er es ist korrekt, aber zeigt, dass die Position nicht so klar ist, wie Sie es haben würden. Auch keine Kritik an deinen Kommentaren. Wie gesagt, ich finde es interessant, ohne selbst starke Ansichten zu diesem Thema zu haben :) – onedaywhen

5
declare @counter as int 
set @counter = 0 
declare @date as varchar(50) 
set @date = cast([email protected] as varchar)+'/01/2013' 
while(@counter < 12) 
begin 
select cast([email protected] as varchar)+'/01/2013' as date 
set @counter = @counter + 1 
end 
1
[CREATE PROCEDURE [rat].[GetYear] 

AS 
BEGIN 

-- variable for storing start date 
Declare @StartYear as int 
-- Variable for the End date 
Declare @EndYear as int 

-- Setting the value in strat Date 
select @StartYear = Value from rat.Configuration where Name = 'REPORT_START_YEAR'; 

-- Setting the End date 
select @EndYear = Value from rat.Configuration where Name = 'REPORT_END_YEAR'; 


-- Creating Tem table 
    with [Years] as 
    (
     --Selecting the Year 
     select @StartYear [Year] 
     --doing Union 
     union all 
     -- doing the loop in Years table 
     select Year+1 Year from [Years] where Year < @EndYear 
    ) 
    --Selecting the Year table 
selec] 
1

Off natürlich eine alte Frage. Aber ich habe eine einfache Lösung, wo keine Looping, CTE, Tabelle Variablen usw. benötigt werden.

DECLARE @MyVar datetime = '1/1/2010'  
SELECT @MyVar 

SELECT DATEADD (DD,NUMBER,@MyVar) 
FROM master.dbo.spt_values 
WHERE TYPE='P' AND NUMBER BETWEEN 0 AND 4 
ORDER BY NUMBER