I am trying to create a masking policy with tagging:
CREATE OR REPLACE MASKING POLICY TAGS_MASKING
AS (val VARCHAR, col_name STRING) RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('ADMIN_ROLE') THEN val
WHEN CURRENT_ROLE() IN ('ANALYST_ROLE') AND (SELECT SYSTEM$GET_TAG('TAG_NAME', col_name , 'COLUMN') = 'PUBLIC') THEN val
WHEN CURRENT_ROLE() IN ('ANALYST_ROLE') AND (SELECT SYSTEM$GET_TAG('TAG_NAME', col_name , 'COLUMN') IN ('PROTECTED')) THEN '****MASKED****'
END;
Here, col_name is a string (e.g. 'mytable.col1'), so that I can assign this masking policy to any columns I want to. But when I used the following query to assign it to one column of one table, it failed:
ALTER TABLE IF EXISTS db.masking.mytable MODIFY COLUMN col1
SET MASKING POLICY TAGS_MASKING using (col1, 'mytable.col1');
The error message is:
Syntax error: unexpected "mytable.col1"
How should I figure this out? Thanks!
I have not found a way to parametrize the column name(passing it as optional second parameter) so I have used a differnt approach.
It automatically creates a masking policy per colum using Snowflake Scripting.
Setup:
CREATE OR REPLACE TAG TAG_NAME;
CREATE OR REPLACE TABLE mytable(col1 STRING);
ALTER TABLE mytable SET TAG TAG_NAME='PUBLIC';
INSERT INTO mytable(col1) VALUES ('Test');
SELECT * FROM mytable;
-- Test
Procedure:
CREATE OR REPLACE PROCEDURE test(schema_name STRING, tab_name STRING, col_name STRING)
RETURNS STRING
LANGUAGE SQL
AS
$$
DECLARE
sql_masking_policy STRING;
sql_alter_table STRING;
masking_policy_name STRING := CONCAT_WS('_', 'TAGS_MASKING_', SCHEMA_NAME, TAB_NAME, COL_NAME);
BEGIN
sql_masking_policy := '
CREATE OR REPLACE MASKING POLICY <masking_policy_name>
AS (val VARCHAR) RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN (''ADMIN_ROLE'') THEN val
WHEN CURRENT_ROLE() IN (''ANALYST_ROLE'') AND (SYSTEM$GET_TAG(''TAG_NAME'', ''<col_name>'', ''COLUMN'') = ''PUBLIC'') THEN val
WHEN CURRENT_ROLE() IN (''ANALYST_ROLE'') AND (SYSTEM$GET_TAG(''TAG_NAME'', ''<col_name>'', ''COLUMN'') IN (''PROTECTED'')) THEN ''****MASKED****''
END;';
sql_alter_table := 'ALTER TABLE IF EXISTS <tab_name> MODIFY COLUMN <col_name>
SET MASKING POLICY <masking_policy_name>;';
sql_masking_policy := REPLACE(sql_masking_policy, '<masking_policy_name>', :masking_policy_name);
sql_masking_policy := REPLACE(sql_masking_policy, '<col_name>', CONCAT_WS('.', schema_name, tab_name, col_name));
sql_alter_table := REPLACE(sql_alter_table, '<masking_policy_name>', :masking_policy_name);
sql_alter_table := REPLACE(sql_alter_table, '<tab_name>', CONCAT_WS('.', schema_name, tab_name));
sql_alter_table := REPLACE(sql_alter_table, '<col_name>', col_name);
EXECUTE IMMEDIATE :sql_masking_policy;
EXECUTE IMMEDIATE :sql_alter_table;
RETURN sql_masking_policy || CHR(10) || sql_alter_table;
END;
$$;
Call:
CALL test('public', 'mytable', 'col1');
Output:
CREATE OR REPLACE MASKING POLICY TAGS_MASKING__public_mytable_col1
AS (val VARCHAR) RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('ADMIN_ROLE') THEN val
WHEN CURRENT_ROLE() IN ('ANALYST_ROLE') AND (SYSTEM$GET_TAG('TAG_NAME', 'public.mytable.col1', 'COLUMN') = 'PUBLIC') THEN val
WHEN CURRENT_ROLE() IN ('ANALYST_ROLE') AND (SYSTEM$GET_TAG('TAG_NAME', 'public.mytable.col1', 'COLUMN') IN ('PROTECTED')) THEN '****MASKED****'
END;
ALTER TABLE IF EXISTS public.mytable MODIFY COLUMN col1 SET MASKING POLICY TAGS_MASKING__public_mytable_col1;
Check:
SHOW MASKING POLICIES;
Output:
Test of select using POLICY_CONTEXT:
execute using policy_context(current_role => 'PUBLIC')
AS
SELECT * FROM public.mytable;
-- NULL
execute using policy_context(current_role => 'ADMIN_ROLE')
AS
SELECT * FROM public.mytable;
-- Test
execute using policy_context(current_role => 'ANALYST_ROLE')
AS
SELECT * FROM public.mytable;
-- Test
ALTER TABLE mytable SET TAG TAG_NAME='PROTECTED';
execute using policy_context(current_role => 'ANALYST_ROLE')
AS
SELECT * FROM public.mytable;
-- ****MASKED****
The previous answer could be improved by using new feature: Tag-based Masking Policies:
A tag-based masking policy combines the object tagging and masking policy features to allow a masking policy to be set on a tag using an ALTER TAG command. When the data type in the masking policy signature and the data type of the column match, the tagged column is automatically protected by the conditions in the masking policy. This simplifies the data protection efforts because column data that should be protected no longer needs a masking policy manually applied to the column to protect the data. A column can be protected by a masking policy directly assigned to a column and a tag-based masking policy
SYSTEM$GET_TAG_ON_CURRENT_COLUMN:
SYSTEM$GET_TAG_ON_CURRENT_COLUMN( '<tag_name>' )
Returns the tag string value assigned to the column based upon the specified tag or NULL if a tag is not assigned to the specified column.
For this scenario:
CREATE OR REPLACE TAG TAG_NAME;
CREATE OR REPLACE MASKING POLICY TAGS_MASKING
AS (val VARCHAR) RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('ADMIN_ROLE') THEN val
WHEN CURRENT_ROLE() IN ('ANALYST_ROLE')
AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('TAG_NAME') = 'PUBLIC' THEN val
WHEN CURRENT_ROLE() IN ('ANALYST_ROLE')
AND SYSTEM$GET_TAG_ON_CURRENT_COLUMN('TAG_NAME') IN ('PROTECTED') THEN '****MASKED****'
END;
ALTER TAG TAG_NAME SET MASKING POLICY TAGS_MASKING;
Table:
CREATE OR REPLACE TABLE mytable(col1 STRING);
INSERT INTO mytable(col1) VALUES ('Test');
SELECT * FROM mytable;
-- COL1
-- Test
Assigning tag:
ALTER TABLE mytable ALTER COLUMN col1 SET TAG TAG_NAME='PROTECTED';
Role outside ADMIN_ROLE/ANALYST_ROLE:
SELECT * FROM mytable;
-- COL1
-- null
Switching to ANALYST_ROLE(column tagged with PROTECTED value):
USE ROLE ANALYST_ROLE;
SELECT * FROM mytable;
-- COL1
-- ****MASKED****
Related
In t-sql my dilemma is that I have to parse a potentially long string (up to 500 characters) for any of over 230 possible values and remove them from the string for reporting purposes. These values are a column in another table and they're all upper case and 4 characters long with the exception of two that are 5 characters long.
Examples of these values are:
USFRI
PROME
AZCH
TXJS
NYDS
XVIV. . . . .
Example of string before:
"Offered to XVIV and USFRI as back ups. No response as of yet."
Example of string after:
"Offered to and as back ups. No response as of yet."
Pretty sure it will have to be a UDF but I'm unable to come up with anything other than stripping ALL the upper case characters out of the string with PATINDEX which is not the objective.
This is unavoidably cludgy but one way is to split your string into rows, once you have a set of words the rest is easy; Simply re-aggregate while ignoring the matching values*:
with t as (
select 'Offered to XVIV and USFRI as back ups. No response as of yet.' s
union select 'Another row AZCH and TXJS words.'
), v as (
select * from (values('USFRI'),('PROME'),('AZCH'),('TXJS'),('NYDS'),('XVIV'))v(v)
)
select t.s OriginalString, s.Removed
from t
cross apply (
select String_Agg(j.[value], ' ') within group(order by Convert(tinyint,j.[key])) Removed
from OpenJson(Concat('["',replace(s, ' ', '","'),'"]')) j
where not exists (select * from v where v.v = j.[value])
)s;
* Requires a fully-supported version of SQL Server.
build a function to do the cleaning of one sentence, then call that function from your query, something like this SELECT Col1, dbo.fn_ReplaceValue(Col1) AS cleanValue, * FROM MySentencesTable. Your fn_ReplaceValue will be something like the code below, you could also create the table variable outside the function and pass it as parameter to speed up the process, but this way is all self contained.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION fn_ReplaceValue(#sentence VARCHAR(500))
RETURNS VARCHAR(500)
AS
BEGIN
DECLARE #ResultVar VARCHAR(500)
DECLARE #allValues TABLE (rowID int, sValues VARCHAR(15))
DECLARE #id INT = 0
DECLARE #ReplaceVal VARCHAR(10)
DECLARE #numberOfValues INT = (SELECT COUNT(*) FROM MyValuesTable)
--Populate table variable with all values
INSERT #allValues
SELECT ROW_NUMBER() OVER(ORDER BY MyValuesCol) AS rowID, MyValuesCol
FROM MyValuesTable
SET #ResultVar = #sentence
WHILE (#id <= #numberOfValues)
BEGIN
SET #id = #id + 1
SET #ReplaceVal = (SELECT sValue FROM #allValues WHERE rowID = #id)
SET #ResultVar = REPLACE(#ResultVar, #ReplaceVal, SPACE(0))
END
RETURN #ResultVar
END
GO
I suggest creating a table (either temporary or permanent), and loading these 230 string values into this table. Then use it in the following delete:
DELETE
FROM yourTable
WHERE col IN (SELECT col FROM tempTable);
If you just want to view your data sans these values, then use:
SELECT *
FROM yourTable
WHERE col NOT IN (SELECT col FROM tempTable);
We are deploying EF Core migrations using SQL scripts to our staging environment.
When running the SQL scripts having Stored procedures, Views and functions we ran into problems of unrecognized characters.
The solution was to parse the SQL Scripts to use Dynamic script by surrounding the CREATE or ALTER statements with EXEC('...').
Using powershell [regex]::replace we did this format but we still have an issue with single quotes inside these statements.
Below are the powershell scripts:
## Replace CREATE statements ( SP, View, Func )
$sql = [regex]::replace($sql, "BEGIN\s+(CREATE (?:PROCEDURE|VIEW|FUNCTION).+?)END;", "BEGIN`nEXEC('`$1');`nEND;", "ignorecase,singleline")
## Replace ALTER statements ( SP, View, Func )
$sql = [regex]::replace($sql, "BEGIN\s+(ALTER (?:PROCEDURE|VIEW|FUNCTION).+?)END;", "BEGIN`nEXEC('`$1');`nEND;", "ignorecase,singleline")
How can we extend these to escape every single quote with another single quote only for these statements?
I was looking to add another .replace() function to the $1 param but no luck.
Example SQL Script
IF NOT EXISTS(SELECT * FROM [TestHelper].[__EFMigrationsHistory] WHERE [MigrationId] = N'20191004135334_db_sp-CsvImport')
BEGIN
CREATE PROCEDURE [TestHelper].[CsvImportService]
#DvseImportId INT,
#Path NVARCHAR(255)
AS
BEGIN
-- DROP TEMP TABLE IF EXITSTS
DROP TABLE IF EXISTS [TestHelper].[_tmpDvseImportData]
-- CREATE TEMP TABLE
CREATE TABLE [TestHelper].[_tmpDvseImportData]
(
DataSupplierID INT
, DataSupplier NVARCHAR(255)
, ArticleNumber NVARCHAR(255)
, ArticleNumberNorm NVARCHAR(255)
, GenNo INT
, GenDescription NVARCHAR(255)
, State BIT
)
-- BULK INSERT DATA FROM FILESHARE
EXEC('BULK INSERT [TestHelper].[_tmpDvseImportData] FROM ''' + #Path + ''' WITH ( FIELDTERMINATOR = '';'',ROWTERMINATOR = ''\n'')')
-- STEP 1: FORMAT DATA
-- REMOVE DOUBLE QUOTES = CHAR(34)
UPDATE [TestHelper].[_tmpDvseImportData]
SET DataSupplier = REPLACE(DataSupplier, CHAR(34), '')
, ArticleNumber = REPLACE(ArticleNumber, CHAR(34), '')
, ArticleNumberNorm = REPLACE(ArticleNumberNorm, CHAR(34), '')
, GenDescription = REPLACE(GenDescription, CHAR(34), '')
-- STEP 2: REMOVE DUPLICATES
...
-- Drop table
DROP TABLE [TestHelper].[_tmpDvseImportData]
END
END;
GO
IF NOT EXISTS(SELECT * FROM [TestHelper].[__EFMigrationsHistory] WHERE [MigrationId] = N'20191008093824_db_view-DvseSuppliersWithMetaData')
BEGIN
CREATE VIEW [TestHelper].[DvseSuppliersWithMetaData] AS
SELECT supplier.Id
, supplier.[Key]
, supplier.Code
, supplier.[Name]
, (SELECT COUNT(*) FROM [TestHelper].[DvseArticle] WHERE SupplierId = supplier.Id) as TotalArticles
, (SELECT COUNT(*) FROM [TestHelper].[DvseArticle] WHERE SupplierId = supplier.Id AND IsActive = 1) as TotalActive
FROM [TestHelper].[DvseSupplier] AS supplier
GROUP BY supplier.Id
, supplier.[Key]
, supplier.Code
, supplier.[Name]
END;
GO
In Windows PowerShell within the .Replace method, you can use a script block. The script block can then manipulate the matched results. It isn't as clean as script-block substitution offered by PowerShell Core.
$sbCreate = {param ($m) "BEGIN`nEXEC('$($m.Groups[1].Value -replace "'","''")');`nEND;"}
$sbAlter = {param ($m) "BEGIN`nEXEC('$($m.Groups[1].Value -replace "'","''")');`nEND;" }
$sql = [regex]::replace($sql, "BEGIN\s+(CREATE (?:PROCEDURE|VIEW|FUNCTION).+?)END;", $sbCreate, "ignorecase,singleline")
$sql = [regex]::replace($sql, "BEGIN\s+(ALTER (?:PROCEDURE|VIEW|FUNCTION).+?)END;", $sbAlter, "ignorecase,singleline")
Explanation:
In each script block, parameter $m is the matched object. Since you want to access the first unnamed capture group (1), you can retrieve that value using $m.Groups[1].Value. The sub-expression operator $() is used to that we can access the properties of $m and use the -replace operation within the replacement string.
If you are you using PS version 6 or above, you should be able to use a scriptblock substitution to achieve what you want.
For example, given that you have saved your example string as a variable called sqlString, you could do the following:
$regex = [regex]::new("BEGIN\s+(CREATE (?:PROCEDURE|VIEW|FUNCTION).+?)END;", 'SingleLine')
$sqlString -replace $regex, {"BEGIN`nEXEC('$($_.Value.Replace("'", "''"))');`nEND;"}
I'm creating a grid that has two columns: Name and HotelId. The problem is that data for this grid should be sent with a single parameter of VARCHAR type and should look like this:
#Parameter = 'Name1:5;Name2:10;Name3:6'
As you can see, the parameter contains Name and a number that represents ID value and you can have multiple such entries, separated by ";" symbol.
My first idea was to write a query that creates a temp table that will have two columns and populate it with data from the parameter.
How could I achieve this? It seems like I need to split the parameter two times: by the ";" symbol for each row and then by ":" symbol for each column.
How should I approach this?
Also, if there is any other more appropriate solution, I'm open to suggestions.
First Drop the #temp table if Exists...
IF OBJECT_ID('tempdb..#temp', 'U') IS NOT NULL
/*Then it exists*/
DROP TABLE #temp
Then create #temp table
CREATE TABLE #temp (v1 VARCHAR(100))
Declare all the #Paramter....
DECLARE #Parameter VARCHAR(50)
SET #Parameter= 'Name1:5;Name2:10;Name3:6'
DECLARE #delimiter nvarchar(1)
SET #delimiter= N';';
Here, Inserting all #parameter value into #temp table using ';' separated..
INSERT INTO #temp(v1)
SELECT * FROM(
SELECT v1 = LTRIM(RTRIM(vals.node.value('(./text())[1]', 'nvarchar(4000)')))
FROM (
SELECT x = CAST('<root><data>' + REPLACE(#Parameter, #delimiter, '</data><data>') + '</data></root>' AS XML).query('.')
) v
CROSS APPLY x.nodes('/root/data') vals(node)
)abc
After inserting the value into #temp table..get all the value into ':' seprated...
select Left(v1, CHARINDEX(':', v1)-1) as Name , STUFF(v1, 1, CHARINDEX(':', v1), '') as HotelId FROM #temp
Then you will get this type of Output
This is my initial PL/SQL code :
TYPE VarcharArray IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
and i use it in the following code :
PROCEDURE Create(inFatherId IN VARCHAR2, inBarcode IN VarcharArray, inItemId IN VarcharArray)
IS
myCount NUMBER(38);
sampleId_FromDb NUMBER(38);
itemId_FromDb NUMBER(38);
BEGIN
myCount := inBarcode.COUNT;
FOR i IN 1..myCount
LOOP
SELECT ITEM.Id INTO itemId_FromDb FROM ITEM WHERE FatherId = inFatherId AND CampaignItemId = inItemId(i);
SELECT SAMPLE_SEQUENCE.NEXTVAL INTO sampleId_FromDb FROM DUAL;
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId) VALUES(sampleId_FromDb, inBarcode(i), itemId_FromDb);
END LOOP;
END;
I've seen that the array type can be translated into MS SQL with Table-Valued Parameters, however how can i iterate in a similar fashion so that i include in the iteration the thee operations ?
In the current PL/SQL implementation i send up to 50.000 elements in the array and the performance is decent. I would desire something similar also in MS SQL.
There's no need to be looping and inserting one row at a time. That's just a way to make your code slower. Since tables don't have any order in them, you need to add one column to define the order. Your type would be like this:
CREATE TYPE VarcharArray AS TABLE(ID int, Item VARCHAR(100));
Then, you can rewrite your procedure as a single INSERT statement.
CREATE PROCEDURE SomeProcedure(
#FatherId AS VARCHAR, --This might need a length or will be defaulted to length 1
#Barcode AS VarcharArray READONLY,
#ItemId AS VarcharArray READONLY
)
AS
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId)
SELECT NEXT VALUE FOR SAMPLE_SEQUENCE,
bc.Item,
i.Id
FROM ITEM i
JOIN #ItemId ii ON i.CampaignItemId = ii.Item
JOIN #Barcode bc ON ii.ID = bc.ID
WHERE i.FatherId = #FatherId;
You could also create a table with both values and prevent any ordering problems that could occur.
CREATE TYPE BarcodeItems AS TABLE(Item VARCHAR(100), Barcode VARCHAR(100));
GO
CREATE PROCEDURE SomeProcedure(
#FatherId AS VARCHAR, --This might need a length or will be defaulted to length 1
#BarcodeItems AS BarcodeItems READONLY
)
AS
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId)
SELECT NEXT VALUE FOR SAMPLE_SEQUENCE,
bi.Item,
i.Id
FROM ITEM i
JOIN #BarcodeItems bi ON i.CampaignItemId = bi.Item
WHERE i.FatherId = #FatherId;
I have a list of integers or of strings and need to pass it as a parameter for a Delphi DataSet. How to do it?
Here is an example. MyQuery is something like:
select * from myTable where intKey in :listParam
I'd set a parameter as a list or array or something else:
MyQuery.ParamByName('listParam').AsSomething := [1,2,3];
and it would result in this query sent to the sql server:
select * from myTable where intKey in (1, 2, 3)
It would be even better if the solution would also work with strings, making this query:
select * from myTable where stringKey in :listParam
become:
select * from myTable where stringKey in ('a', 'b', 'c')
I believe this is a simple question, but "IN" isn't a good keyword for searching the web.
Please answer how I should configure the parameter in the IDE, the query and how to pass the parameters.
I'm using Delphi 7.
Edited: I'm considering the answer is "it isn't possible to do directly". If someone give me a non-hackish answer, the accepted answer will be changed.
AFAIK, it is not possible directly.
You'll have to convert the list into a SQL list in plain text.
For instance:
function ListToText(const Args: array of string): string; overload;
var i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+QuotedStr(Args[i])+',';
result[length(result)] := ')';
end;
function ListToText(const Args: array of integer): string; overload;
var i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+IntToStr(Args[i])+',';
result[length(result)] := ')';
end;
To be used as such:
SQL.Text := 'select * from myTable where intKey in '+ListToText([1,2,3]);
SQL.Text := 'select * from myTable where stringKey in '+ListToText(['a','b','c']);
SQL accepts only single values as parameters so you cannot create a statement with one parameter that can map to a variable number of values, such as the example you gave.
However, you can still use parameterized SQL in this situation. The solution is to iterate over the list of values you have, adding a parameter marker to the SQL and a parameter to the parameter list for each value.
This is easiest to do with positional rather than named parameters but can be adapted for named parameters as well (you may need to adjust this code since I don't have Delphi available and don't remember the Parameter creation syntax):
//AValues is an array of variant values
//SQLCommand is some TDataSet component with Parameters.
for I := Low(AValues) to High(AValues) do
begin
if ParamString = '' then
ParamString = '?'
else
ParamString = ParamString + ', ?';
SQLCommand.Parameters.Add(AValues[I]);
end
SQLCommand.CommandText =
'SELECT * FROM MyTable WHERE KeyValue IN (' + ParamString + ')';
This will produce an injection-safe parameterized query.
There are several options for you but basically you need to get your values into a table. I would suggest a table variable for that.
Here is a version that unpacks an int list.
declare #IDs varchar(max)
set #IDs = :listParam
set #IDs = #IDs+','
declare #T table(ID int primary key)
while len(#IDs) > 1
begin
insert into #T(ID) values (left(#IDs, charindex(',', #IDs)-1))
set #IDs = stuff(#IDs, 1, charindex(',', #IDs), '')
end
select *
from myTable
where intKey in (select ID from #T)
It is possible to have multi-statement queries. The parameter :listParam should be a string:
MyQuery.ParamByName('listParam').AsString := '1,2,3';
You can use the same technique for strings. You just need to change the data type of ID to for instance varchar(10).
Instead of unpacking with a while loop you could make use of a split function
declare #T table(ID varchar(10))
insert into #T
select s
from dbo.Split(',', :listParam)
select *
from myTable
where charKey in (select ID from #T)
A string param could look like this:
MyQuery.ParamByName('listParam').AsString := 'Adam,Bertil,Caesar';
Create a temporary table and insert your values in it. Then use that table as part of a subquery.
For example, create MyListTable in your database. Insert your values into MyListTable. Then do
select * from myTable where keyvalue in (select keyvalue from MyListTable)
This avoids SQL injection attacks. But it's not elegant, is not performance friendly because you have to insert records before running your query, and can lead to concurrency issues.
Not my first choice to deal with your situation but it addresses your concern about sql injection.
If someone still having the same problem, if you are using firedac you can use macros like this:
Query -> "select * from myTable where intKey in (&listParam)"
Setting the macro -> MyQuery.MacroByName('listParam').AsRaw := '1, 2, 3';
I use some "IN" replacement. Here is the query I use:
SELECT * FROM MyTable WHERE CHARINDEX(','+cast(intKey as varchar(10))+',', :listParam) > 0
the code to send parameter:
MyQuery.ParamByName('listParam').AsString := ',1,2,3,';
The array item value can partially match some other values. For instance, "1" can be part of "100". To protect against it, I use comma as delimiter
Why not make a dynamic sql:
Quick and dirty, but still using parameters.
check 10 elements. I don't know how well this scales.
MyQuerySQL.Text:='SELECT * FROM myTable WHERE intKey in (:listParam0'
for i := 1 to 9 do begin
MyQuerySQL.Text := MyQuerySQL.Text + ',:listParam'+IntToStr(i)
end;
MyQuerySQL.Text := MyQuerySQL.Text+')';
for i:=0 to 9 do begin
MyQuery.ParamByName('listParam'+IntToStr(i)).AsInteger := ArrayofInt[0];
end;