J'ai besoin de passer un tableau de "id" à une procédure stockée, pour supprimer toutes les lignes de la table SAUF les lignes qui correspondent aux id dans le tableau.
Comment puis-je le faire de la manière la plus simple qui soit ?
J'ai besoin de passer un tableau de "id" à une procédure stockée, pour supprimer toutes les lignes de la table SAUF les lignes qui correspondent aux id dans le tableau.
Comment puis-je le faire de la manière la plus simple qui soit ?
Si vous utilisez Sql Server 2008 ou une version plus récente, vous pouvez utiliser un paramètre évalué par une table (TVP) au lieu de sérialiser et de désérialiser les données de votre liste chaque fois que vous voulez les transmettre à une procédure stockée.
Commençons par créer un schéma simple qui nous servira de terrain de jeu :
CREATE DATABASE [TestbedDb]
GO
USE [TestbedDb]
GO
/* First, setup the sample program's account & credentials*/
CREATE LOGIN [testbedUser] WITH PASSWORD=N'µ×?
?S[°¿Q¥½q?_I¼Ð)3õl%dv', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=ON
GO
CREATE USER [testbedUser] FOR LOGIN [testbedUser] WITH DEFAULT_SCHEMA=[dbo]
GO
EXEC sp_addrolemember N'db_owner', N'testbedUser'
GO
/* Now setup the schema */
CREATE TABLE dbo.Table1 ( t1Id INT NOT NULL PRIMARY KEY );
GO
INSERT INTO dbo.Table1 (t1Id)
VALUES
(1),
(2),
(3),
(4),
(5),
(6),
(7),
(8),
(9),
(10);
GO
Avec notre schéma et nos données d'exemple en place, nous sommes maintenant prêts à créer notre procédure stockée TVP :
CREATE TYPE T1Ids AS Table (
t1Id INT
);
GO
CREATE PROCEDURE dbo.FindMatchingRowsInTable1( @Table1Ids AS T1Ids READONLY )
AS
BEGIN
SET NOCOUNT ON;
SELECT Table1.t1Id FROM dbo.Table1 AS Table1
JOIN @Table1Ids AS paramTable1Ids ON Table1.t1Id = paramTable1Ids.t1Id;
END
GO
Une fois notre schéma et notre API en place, nous pouvons appeler la procédure stockée TVP à partir de notre programme comme suit :
// Curry the TVP data
DataTable t1Ids = new DataTable( );
t1Ids.Columns.Add( "t1Id",
typeof( int ) );
int[] listOfIdsToFind = new[] {1, 5, 9};
foreach ( int id in listOfIdsToFind )
{
t1Ids.Rows.Add( id );
}
// Prepare the connection details
SqlConnection testbedConnection =
new SqlConnection(
@"Data Source=.\SQLExpress;Initial Catalog=TestbedDb;Persist Security Info=True;User ID=testbedUser;Password=letmein12;Connect Timeout=5" );
try
{
testbedConnection.Open( );
// Prepare a call to the stored procedure
SqlCommand findMatchingRowsInTable1 = new SqlCommand( "dbo.FindMatchingRowsInTable1",
testbedConnection );
findMatchingRowsInTable1.CommandType = CommandType.StoredProcedure;
// Curry up the TVP parameter
SqlParameter sqlParameter = new SqlParameter( "Table1Ids",
t1Ids );
findMatchingRowsInTable1.Parameters.Add( sqlParameter );
// Execute the stored procedure
SqlDataReader sqlDataReader = findMatchingRowsInTable1.ExecuteReader( );
while ( sqlDataReader.Read( ) )
{
Console.WriteLine( "Matching t1ID: {0}",
sqlDataReader[ "t1Id" ] );
}
}
catch ( Exception e )
{
Console.WriteLine( e.ToString( ) );
}
/* Output:
* Matching t1ID: 1
* Matching t1ID: 5
* Matching t1ID: 9
*/
Il existe probablement un moyen moins pénible de le faire en utilisant une API plus abstraite, comme Entity Framework. Cependant, je n'ai pas le temps de le vérifier par moi-même pour le moment.
Je pense que c'est la meilleure façon de faire. Je suis choqué que tu n'aies pas eu de vote pour ça. Donc je t'en ai donné un.
Pour la réutilisation, je pense que c'est de loin la meilleure approche. Prenons l'exemple suivant d'une application courante dans le monde réel : J'ai une recherche, qui génère un ensemble de résultats comprenant une clé primaire unique. Je souhaite ensuite transmettre cet ensemble de résultats à une autre opération (par exemple, récupérer des informations supplémentaires pour une feuille de calcul Excel). Avec cette méthode, je peux facilement récupérer une liste d'identifiants à partir du jeu de résultats initial et passer la liste dans ma procédure stockée de seconde phase qui accepte un paramètre unique de "@IDs T1IDs READONLY". Ainsi, j'évite la lourde charge de la recherche initiale.
Utilisez une procédure stockée :
EDIT : Un complément pour sérialiser List (ou n'importe quoi d'autre) :
List<string> testList = new List<int>();
testList.Add(1);
testList.Add(2);
testList.Add(3);
XmlSerializer xs = new XmlSerializer(typeof(List<int>));
MemoryStream ms = new MemoryStream();
xs.Serialize(ms, testList);
string resultXML = UTF8Encoding.UTF8.GetString(ms.ToArray());
Le résultat (prêt à être utilisé avec le paramètre XML) :
<?xml version="1.0"?>
<ArrayOfInt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<int>1</int>
<int>2</int>
<int>3</int>
</ArrayOfInt>
POSTE ORIGINAL :
Passage de XML comme paramètre :
<ids>
<id>1</id>
<id>2</id>
</ids>
CREATE PROCEDURE [dbo].[DeleteAllData]
(
@XMLDoc XML
)
AS
BEGIN
DECLARE @handle INT
EXEC sp_xml_preparedocument @handle OUTPUT, @XMLDoc
DELETE FROM
YOURTABLE
WHERE
YOUR_ID_COLUMN NOT IN (
SELECT * FROM OPENXML (@handle, '/ids/id') WITH (id INT '.')
)
EXEC sp_xml_removedocument @handle
+1 C'est une excellente solution si vos données se trouvent déjà dans une structure XML, mais ce n'est pas le cas si vous ajoutez les frais de création du XML côté client.
Comme RolandTumble l'a mentionné, cela nécessitera de convertir mon tableau de données d'entrée (liste) en XML, donc je pense que ce n'est pas la meilleure solution dans mon cas.
Vous pourriez utiliser Serialize pour le faire... Il est plus fiable d'utiliser le XML que de diviser les chaînes de caractères...
C'est la meilleure source :
http://www.sommarskog.se/arrays-in-sql.html
créer une fonction de fractionnement en utilisant le lien, et l'utiliser comme suit :
DELETE YourTable
FROM YourTable d
LEFT OUTER JOIN dbo.splitFunction(@Parameter) s ON d.ID=s.Value
WHERE s.Value IS NULL
Je préfère l'approche des tableaux de chiffres
Voici un code basé sur le lien ci-dessus qui devrait le faire pour vous...
Avant d'utiliser ma fonction, vous devez configurer une table "auxiliaire", vous ne devez le faire qu'une seule fois par base de données :
CREATE TABLE Numbers
(Number int NOT NULL,
CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
SET @x=@x+1
INSERT INTO Numbers VALUES (@x)
END
Utilisez cette fonction pour diviser votre chaîne de caractères, qui ne tourne pas en boucle et est très rapide :
CREATE FUNCTION [dbo].[FN_ListToTable]
(
@SplitOn char(1) --REQUIRED, the character to split the @List string on
,@List varchar(8000) --REQUIRED, the list to split apart
)
RETURNS
@ParsedList table
(
ListValue varchar(500)
)
AS
BEGIN
/**
Takes the given @List string and splits it apart based on the given @SplitOn character.
A table is returned, one row per split item, with a column name "ListValue".
This function workes for fixed or variable lenght items.
Empty and null items will not be included in the results set.
Returns a table, one row per item in the list, with a column name "ListValue"
EXAMPLE:
----------
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B')
returns:
ListValue
-----------
1
12
123
1234
54321
6
A
*
|||
B
(10 row(s) affected)
**/
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
INSERT INTO @ParsedList
(ListValue)
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT @SplitOn + @List + @SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = @SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
RETURN
END --Function FN_ListToTable
vous pouvez utiliser cette fonction comme une table dans une jointure :
SELECT
Col1, COl2, Col3...
FROM YourTable
INNER JOIN dbo.FN_ListToTable(',',@YourString) s ON YourTable.ID = s.ListValue
voici votre suppression :
DELETE YourTable
FROM YourTable d
LEFT OUTER JOIN dbo.FN_ListToTable(',',@Parameter) s ON d.ID=s.ListValue
WHERE s.ListValue IS NULL
@KM : Pourquoi le maximum de 8000 caractères ? Peut-on utiliser des chaînes de caractères plus longues que cela ?
Vous pouvez faire en sorte que le paramètre d'entrée soit de type varchar(max), mais assurez-vous que votre table Numbers comporte suffisamment de lignes. J'utilise simplement 8000, car je ne divise jamais de longues chaînes de caractères. Vous pouvez consulter cette autre réponse de ma part : stackoverflow.com/questions/4227552/ où j'ai mis à jour la fonction de fractionnement pour utiliser une fonction de table et j'ai également amélioré la génération de la table Numbers.
Vous pouvez utiliser une table temporaire dont la procédure stockée s'attend à ce qu'elle existe. Cela fonctionnera sur les anciennes versions de SQL Server, qui ne prennent pas en charge le XML, etc.
CREATE TABLE #temp
(INT myid)
GO
CREATE PROC myproc
AS
BEGIN
DELETE YourTable
FROM YourTable
LEFT OUTER JOIN #temp T ON T.myid=s.id
WHERE s.id IS NULL
END
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
2 votes
Ne s'agit-il pas d'une duplication de stackoverflow.com/questions/114504/ ?
3 votes
@John Saunders, il y a beaucoup de questions sur le serveur sql "passer un tableau comme paramètre". Cependant, celle-ci a une particularité : elle supprime toutes les lignes, sauf la partie de la question concernant les paramètres passés. Par conséquent, je ne pense pas qu'il s'agisse d'un doublon.
0 votes
John Saunders, je ne sais pas. J'ai fait une recherche mais je n'ai pas trouvé ce que je cherchais. Y a-t-il un problème avec cela ?
1 votes
Si vous faites un grand nombre d'appels de procédure et que vous devez construire une chaîne xml à chaque fois, vous n'êtes pas efficace et la méthode sql pure serait meilleure. si vous avez déjà la chaîne xml, ou si vous faites un petit nombre d'appels de procédure, le xml sera parfait.