Variabile SQL per contenere l'elenco di numeri interi


175

Sto cercando di eseguire il debug dei rapporti SQL di qualcun altro e ho inserito la query dei rapporti sottostanti in una finestra di query di SQL 2012.

Uno dei parametri richiesti dal rapporto è un elenco di numeri interi. Ciò si ottiene sul report tramite una casella a discesa multi-selezione. La query sottostante del report utilizza questo elenco di numeri interi nella whereclausola es

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

Non voglio modificare la query di cui sto eseguendo il debug, ma non riesco a capire come creare una variabile su SQL Server in grado di contenere questo tipo di dati per testarlo.

per esempio

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

Non esiste un tipo di dati che può contenere un elenco di numeri interi, quindi come posso eseguire la query di report sul mio SQL Server con gli stessi valori del report?


1
So di aver utilizzato il parametro TVP Table Value Parmeter per inserire i dati, ma ora sono sicuro che possano essere utilizzati in un punto. Continuazione?
paparazzo,

2
domanda ben formulata. +1
RayLoveless,

Risposte:


224

Variabile di tabella

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

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

o

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
Grazie per quello, ma ancora una volta mi richiede di riscrivere il modo in cui la variabile viene letta nella query. Devo mantenerlo lo stesso.
ErickTreetops,

3
Cosa succede se non sai quali sono gli ID e che provengono da una query? Esempio: SET @AddressIDs = (SELECT ID FROM address WHERE Account = 1234)questa query restituirà più ID e viene visualizzato un errore che indica che la sottoquery ha restituito più di un risultato e che non è consentito. Esiste un modo per creare una variabile che memorizzerà un array se ID da una sottoquery?
Rafael Moreira,

1
Ho provato la seconda opzione e funziona con un numero inferiore di record. Quando aumento il numero di ID, viene visualizzato l'errore TimeOut. Forse il cast sta ostacolando la performance.
Utente M

Non è la risposta alla domanda originale, ma è la risposta a una che non ho chiesto, quindi sto bene. Passo un Elenco <int> come parametro, ma voglio creare una variabile locale per il test in SSMS. È un assassino.
Wade Hatler,

Ottima risposta, mi ha fatto risparmiare un sacco di tempo
Alfredo A.

35

Supponendo che la variabile sia simile a:

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

E la Stored Procedure la sta usando in questo modulo:

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

È possibile creare IntList e chiamare la procedura in questo modo:

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

O se stai fornendo l'IntList tu stesso

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

17

Hai ragione, non esiste un tipo di dati in SQL Server che può contenere un elenco di numeri interi. Ma quello che puoi fare è memorizzare un elenco di numeri interi come stringa.

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

È quindi possibile dividere la stringa in valori interi separati e inserirli in una tabella. La tua procedura potrebbe già farlo.

Puoi anche utilizzare una query dinamica per ottenere lo stesso risultato:

DECLARE @SQL nvarchar(8000);

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

Grazie, ma di nuovo avrebbe bisogno di modificare una query che non mi è permesso.
ErickTreetops,

2
Se qualcuno lo utilizza, tenere presente che potrebbe essere molto vulnerabile all'iniezione SQL se @listOfIDs è un parametro stringa fornito da un utente. A seconda dell'architettura dell'app, questo potrebbe o non potrebbe essere un problema.
Rogala,

@Rogala D'accordo, gli utenti dovranno fare i propri servizi igienico-sanitari come richiesto.
Möoz,

1
@ Möoz Raccomando di aggiungere una nota alla tua risposta per riflettere questo. Non tutti lo sanno, e semplicemente copiano e incollano soluzioni senza pensare alle conseguenze. Dynamic SQL è MOLTO pericoloso, e lo evito come quella piaga.
Rogala,

@ Möoz Inoltre, non ho votato in giù la tua risposta, ma cosa ti prego, prenditi qualche minuto e controlla la mia risposta? La funzione STRING_SPLIT è piuttosto dolce, e penso che ci sarai TUTTO !!
Rogala,

6

Per SQL Server 2016+ e Azure SQL Database, è stata aggiunta la funzione STRING_SPLIT che sarebbe una soluzione perfetta per questo problema. Ecco la documentazione: https://docs.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql

Ecco un esempio:

/*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;

Il risultato della query è 1,3

~ Saluti


4

Alla fine sono giunto alla conclusione che senza modificare il funzionamento della query non sono riuscito a memorizzare i valori in variabili. Ho usato il profiler SQL per catturare i valori e poi li ho codificati nella query per vedere come ha funzionato. C'erano 18 di questi array interi e alcuni avevano oltre 30 elementi in essi.

Penso che sia necessario che MS / SQL introduca alcuni tipi di dati adizionali nella lingua. Le matrici sono abbastanza comuni e non vedo perché non potresti usarle in un proc memorizzato.


6
SQL Server non necessita di array, quando ha parametri e variabili con valori di tabella.
John Saunders,

1
Quindi, ciò che sappiamo è che la query utilizza un elenco di numeri interi (passati ad esso da un array?). Quello che non capisco è come la tua query li stava usando senza utilizzare uno dei metodi indicati nelle risposte. Fornisci un po 'più di contesto e possiamo aiutarti ulteriormente.
Möoz,

2

Non puoi farlo in questo modo, ma puoi eseguire l'intera query memorizzandola in una variabile.

Per esempio:

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

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

Exec (@query)

2
Come affermato in un commento precedente, a seconda di come si implementa questo tipo di soluzione, tenere presente che ciò potrebbe essere vulnerabile all'iniezione SQL se @listOfIDs è un parametro fornito da un utente.
Rogala,

2

C'è una nuova funzione in SQL chiamata string_splitse stai usando un elenco di stringhe. Ref Link STRING_SPLIT (Transact-SQL)

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

puoi passare questa query income segue:

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

1
Sembra plausibile ma sfortunatamente solo SQL Server 2016 in poi.
AnotherFineMess,

0

Io lo uso questo:

1-Dichiara una variabile della tabella temporanea nello script dell'edificio:

DECLARE @ShiftPeriodList TABLE(id INT NOT NULL);

2-Allocare alla tabella temporanea:

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-Riferimento alla tabella quando è necessaria in un'istruzione WHERE:

INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM @ShiftPeriodList)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.