SQL-Variable für die Liste der Ganzzahlen


174

Ich versuche, die SQL-Berichte eines anderen zu debuggen, und habe die zugrunde liegende Berichtsabfrage in ein Abfragefenster von SQL 2012 gestellt.

Einer der Parameter, nach denen der Bericht fragt, ist eine Liste von Ganzzahlen. Dies wird im Bericht über ein Dropdown-Feld mit Mehrfachauswahl erreicht. Die zugrunde liegende Abfrage des Berichts verwendet diese Ganzzahlliste in der whereKlausel, z

select *
from TabA
where TabA.ID in (@listOfIDs)

Ich möchte die Abfrage, die ich debugge, nicht ändern, kann jedoch nicht herausfinden, wie auf dem SQL Server eine Variable erstellt wird, die diese Art von Daten zum Testen enthält.

z.B

declare @listOfIDs int
set listOfIDs  = 1,2,3,4

Es gibt keinen Datentyp, der eine Liste von Ganzzahlen enthalten kann. Wie kann ich die Berichtsabfrage auf meinem SQL Server mit denselben Werten wie der Bericht ausführen?


1
Ich weiß, dass ich TVP Table Value Parmeter zum Einfügen von Daten verwendet habe, aber jetzt sicher, ob es in einem Where verwendet werden kann. Folge?
Paparazzo

2
gut formulierte Frage. +1
RayLoveless

Antworten:


224

Tabellenvariable

declare @listOfIDs table (id int);
insert @listOfIDs(id) values(1),(2),(3);    

select *
from TabA
where TabA.ID in (select id from @listOfIDs)

oder

declare @listOfIDs varchar(1000);
SET @listOfIDs = ',1,2,3,'; --in this solution need put coma on begin and end

select *
from TabA
where charindex(',' + CAST(TabA.ID as nvarchar(20)) + ',', @listOfIDs) > 0

2
Vielen Dank dafür, aber ich muss die Art und Weise, wie die Variable in der Abfrage gelesen wird, neu schreiben. Ich muss es gleich halten.
ErickTreetops

3
Was ist, wenn Sie die IDs nicht kennen und diese aus einer Abfrage stammen? Beispiel: SET @AddressIDs = (SELECT ID FROM address WHERE Account = 1234)Diese Abfrage gibt mehrere IDs zurück und es wird eine Fehlermeldung angezeigt, dass die Unterabfrage mehr als ein Ergebnis zurückgegeben hat und dies nicht zulässig ist. Gibt es überhaupt eine Variable zu erstellen, die ein Array speichert, wenn IDs aus einer Unterabfrage stammen?
Rafael Moreira

1
Ich habe die zweite Option ausprobiert und sie funktioniert für weniger Datensätze. Wenn ich die Anzahl der IDs erhöhe, wird der TimeOut-Fehler angezeigt. Vielleicht behindert die Besetzung die Aufführung.
Benutzer M

Nicht die Antwort auf die ursprüngliche Frage, aber es ist die Antwort auf eine, die ich nicht gestellt habe, also bin ich gut. Ich übergebe eine Liste <int> als Parameter, möchte aber eine lokale Variable zum Testen in SSMS erstellen. Es ist mörderisch.
Wade Hatler

Tolle Antwort, hat mir viel Zeit gespart
Alfredo A.

35

Angenommen, die Variable ist ähnlich wie:

CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)

Und die gespeicherte Prozedur verwendet es in dieser Form:

ALTER Procedure [dbo].[GetFooByIds]
    @Ids [IntList] ReadOnly
As 

Sie können die IntList erstellen und die Prozedur folgendermaßen aufrufen:

Declare @IDs IntList;
Insert Into @IDs Select Id From dbo.{TableThatHasIds}
Where Id In (111, 222, 333, 444)
Exec [dbo].[GetFooByIds] @IDs

Oder wenn Sie die IntList selbst bereitstellen

DECLARE @listOfIDs dbo.IntList
INSERT INTO @listofIDs VALUES (1),(35),(118);

17

Sie haben Recht, es gibt keinen Datentyp in SQL-Server, der eine Liste von Ganzzahlen enthalten kann. Sie können jedoch eine Liste von Ganzzahlen als Zeichenfolge speichern.

DECLARE @listOfIDs varchar(8000);
SET @listOfIDs = '1,2,3,4';

Sie können die Zeichenfolge dann in separate Ganzzahlwerte aufteilen und in eine Tabelle einfügen. Ihr Verfahren könnte dies bereits tun.

Sie können auch eine dynamische Abfrage verwenden, um das gleiche Ergebnis zu erzielen:

DECLARE @SQL nvarchar(8000);

SET @SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + @listOfIDs + ')';
EXECUTE (@SQL);

Danke, müsste aber nochmal eine Abfrage ändern, die ich nicht darf.
ErickTreetops

2
Wenn jemand dies verwendet, beachten Sie bitte, dass dies sehr anfällig für SQL-Injection sein kann, wenn @listOfIDs ein von einem Benutzer bereitgestellter Zeichenfolgenparameter ist. Abhängig von Ihrer Architektur Ihrer App kann dies ein Problem sein oder auch nicht.
Rogala

@Rogala Einverstanden, Benutzer müssen nach Bedarf ihre eigenen Hygienemaßnahmen durchführen.
Möoz

1
@ Möoz Ich empfehle, Ihrer Antwort eine Notiz hinzuzufügen, um dies widerzuspiegeln. Nicht jeder weiß es und sie kopieren und fügen einfach Lösungen ein, ohne über die Konsequenzen nachzudenken. Dynamisches SQL ist SEHR gefährlich und ich vermeide es wie diese Plage.
Rogala

@ Möoz Auch ich habe deine Antwort nicht abgelehnt, aber was nimmst du dir bitte ein paar Minuten und schau dir meine Antwort an? Die STRING_SPLIT-Funktion ist ziemlich süß, und ich denke, Sie werden alles hinter sich haben !!
Rogala

6

Für SQL Server 2016+ und Azure SQL Database wurde die Funktion STRING_SPLIT hinzugefügt, die eine perfekte Lösung für dieses Problem darstellt. Hier ist die Dokumentation: https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Hier ist ein Beispiel:

/*List of ids in a comma delimited string
  Note: the ') WAITFOR DELAY ''00:00:02''' is a way to verify that your script 
        doesn't allow for SQL injection*/
DECLARE @listOfIds VARCHAR(MAX) = '1,3,a,10.1,) WAITFOR DELAY ''00:00:02''';

--Make sure the temp table was dropped before trying to create it
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;

--Create example reference table
CREATE TABLE #MyTable
([Id] INT NOT NULL);

--Populate the reference table
DECLARE @i INT = 1;
WHILE(@i <= 10)
BEGIN
    INSERT INTO #MyTable
    SELECT @i;

    SET @i = @i + 1;
END

/*Find all the values
  Note: I silently ignore the values that are not integers*/
SELECT t.[Id]
FROM #MyTable as t
    INNER JOIN 
        (SELECT value as [Id] 
        FROM STRING_SPLIT(@listOfIds, ',')
        WHERE ISNUMERIC(value) = 1 /*Make sure it is numeric*/
            AND ROUND(value,0) = value /*Make sure it is an integer*/) as ids
    ON t.[Id] = ids.[Id];

--Clean-up
DROP TABLE #MyTable;

Das Ergebnis der Abfrage ist 1,3

~ Prost


4

Am Ende kam ich zu dem Schluss, dass ich die Werte nicht in Variablen speichern konnte, ohne die Funktionsweise der Abfrage zu ändern. Ich habe den SQL-Profiler verwendet, um die Werte abzufangen, und sie dann fest in die Abfrage codiert, um zu sehen, wie es funktioniert. Es gab 18 dieser ganzzahligen Arrays und einige enthielten über 30 Elemente.

Ich denke, dass MS / SQL einige zusätzliche Datentypen in die Sprache einführen muss. Arrays sind weit verbreitet und ich verstehe nicht, warum Sie sie nicht in einem gespeicherten Prozess verwenden konnten.


6
SQL Server benötigt keine Arrays, wenn es tabellenwertige Parameter und Variablen enthält.
John Saunders

Wir wissen also, dass die Abfrage eine Liste von Ganzzahlen verwendet (die von einem Array an sie übergeben werden?). Was ich nicht verstehe, ist, wie Ihre Abfrage sie verwendet hat, ohne eine der in den Antworten angegebenen Methoden zu verwenden. Geben Sie mehr Kontext und wir können Ihnen weiterhelfen.
Möoz

2

Sie können dies nicht so tun, aber Sie können die gesamte Abfrage ausführen, indem Sie sie in einer Variablen speichern.

Beispielsweise:

DECLARE @listOfIDs NVARCHAR(MAX) = 
    '1,2,3'

DECLARE @query NVARCHAR(MAX) = 
    'Select *
     From TabA
     Where TabA.ID in (' + @listOfIDs + ')'

Exec (@query)

2
Beachten Sie, wie in einem vorherigen Kommentar erwähnt, dass dies abhängig von der Implementierung dieser Art von Lösung für SQL-Injection anfällig sein kann, wenn @listOfIDs ein von einem Benutzer angegebener Parameter ist.
Rogala

2

In SQL gibt es eine neue Funktion, die aufgerufen wird, string_splitwenn Sie eine Liste von Zeichenfolgen verwenden. Ref Link STRING_SPLIT (Transact-SQL)

DECLARE @tags NVARCHAR(400) = 'clothing,road,,touring,bike'
SELECT value
FROM STRING_SPLIT(@tags, ',')
WHERE RTRIM(value) <> '';

Sie können diese Abfrage inwie folgt übergeben:

SELECT *
  FROM [dbo].[yourTable]
  WHERE (strval IN (SELECT value FROM STRING_SPLIT(@tags, ',') WHERE RTRIM(value) <> ''))

1
Sieht plausibel aus, aber leider nur ab SQL Server 2016.
AnotherFineMess

0

Ich benutze das :

1-Deklarieren Sie eine temporäre Tabellenvariable im Skript Ihres Gebäudes:

DECLARE @ShiftPeriodList TABLE(id INT NOT NULL);

2-Zuordnung zur temporären Tabelle:

IF (SOME CONDITION) 
BEGIN 
        INSERT INTO @ShiftPeriodList SELECT ShiftId FROM [hr].[tbl_WorkShift]
END
IF (SOME CONDITION2)
BEGIN
    INSERT INTO @ShiftPeriodList
        SELECT  ws.ShiftId
        FROM [hr].[tbl_WorkShift] ws
        WHERE ws.WorkShift = 'Weekend(VSD)' OR ws.WorkShift = 'Weekend(SDL)'

END

3-Verweisen Sie auf die Tabelle, wenn Sie sie in einer WHERE-Anweisung benötigen:

INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM @ShiftPeriodList)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.