A Oracle Parâmetros com a declaração IN?
-
06-07-2019 - |
Pergunta
Got um c # .net aplicativo que eu preciso modificar. A consulta no momento efetivamente faz isso:
select * from contract where contractnum = :ContractNum
(muito simplificado, só para mostrar que estamos usando um = e um parâmetro)
Esse parâmetro é lido a partir do arquivo Settings.Settings no aplicativo C # e tem uma corda nele. Eu preciso modificá-lo para incluir vários contratos, então eu acho que eu posso mudar o SQL para:
select * from contract where contractnum in (:ContractNum)
mas que não retorna nenhum resultado, não importa como eu formatar a cadeia no parâmetro.
Existe uma maneira eu posso obter oráculo para fazer um com um parâmetro?
qualquer ajuda apreciada, obrigado todos.
Solução
ainda têm de encontrar um db que suportes avaliar uma variável de cadeia única contendo vírgulas para separar como a cláusula IN
sola.
As suas opções são a subsequência a variável de modo a vírgula delimitado o conteúdo de variáveis ??são transformados em fileiras, para que você possa, em seguida, juntar-se para isso. Ou, para usar SQL dinâmico, que é uma instrução SQL construído como uma corda em um sproc antes a instrução é executada.
Outras dicas
você poderia usar uma função pipeline para transformar uma string em uma mesa que poderia ser usada com o operador IN
. Por exemplo (testado com 10gR2):
SQL> select * from table(demo_pkg.string_to_tab('i,j,k'));
COLUMN_VALUE
-----------------
i
j
k
com o seguinte pacote:
SQL> CREATE OR REPLACE PACKAGE demo_pkg IS
2 TYPE varchar_tab IS TABLE OF VARCHAR2(4000);
3 FUNCTION string_to_tab(p_string VARCHAR2,
4 p_delimiter VARCHAR2 DEFAULT ',')
5 RETURN varchar_tab PIPELINED;
6 END demo_pkg;
7 /
Package created
SQL> CREATE OR REPLACE PACKAGE BODY demo_pkg IS
2 FUNCTION string_to_tab(p_string VARCHAR2,
3 p_delimiter VARCHAR2 DEFAULT ',')
4 RETURN varchar_tab PIPELINED IS
5 l_string VARCHAR2(4000) := p_string;
6 l_first_delimiter NUMBER := instr(p_string, p_delimiter);
7 BEGIN
8 LOOP
9 IF nvl(l_first_delimiter,0) = 0 THEN
10 PIPE ROW(l_string);
11 RETURN;
12 END IF;
13 PIPE ROW(substr(l_string, 1, l_first_delimiter - 1));
14 l_string := substr(l_string, l_first_delimiter + 1);
15 l_first_delimiter := instr(l_string, p_delimiter);
16 END LOOP;
17 END;
18 END demo_pkg;
19 /
Package body created
A sua consulta seria algo como isto:
select *
from contract
where contractnum in (select column_value
from table(demo_pkg.string_to_tab(:ContractNum)))
Você pode usar uma coleção Oráculo de números como um parâmetro (variável de ligação) quando você usa ODP.NET como dataprovider. Isso funciona com servidor Oracle 9, 10 ou 11 e ODP.net release> = 11.1.0.6.20.
Uma solução semelhante é possível quando você usa Devart do .NET dataprovider para Oracle.
Vamos selecionar os contratos com o contractnum 3 e 4.
Temos que usar um tipo de Oracle para transferir uma matriz de números de contrato para a nossa consulta.
MDSYS.SDO_ELEM_INFO_ARRAY
é usado porque se usarmos este Oracle já predefinidos tipo que não tem que definir o nosso próprio tipo Oracle. Você pode preencher MDSYS.SDO_ELEM_INFO_ARRAY
com Max 1048576 números.
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
[OracleCustomTypeMappingAttribute("MDSYS.SDO_ELEM_INFO_ARRAY")]
public class NumberArrayFactory : IOracleArrayTypeFactory
{
public Array CreateArray(int numElems)
{
return new Decimal[numElems];
}
public Array CreateStatusArray(int numElems)
{
return null;
}
}
private void Test()
{
OracleConnectionStringBuilder b = new OracleConnectionStringBuilder();
b.UserID = "sna";
b.Password = "sna";
b.DataSource = "ora11";
using (OracleConnection conn = new OracleConnection(b.ToString()))
{
conn.Open();
using (OracleCommand comm = conn.CreateCommand())
{
comm.CommandText =
@" select /*+ cardinality(tab 10) */ c.* " +
@" from contract c, table(:1) tab " +
@" where c.contractnum = tab.column_value";
OracleParameter p = new OracleParameter();
p.OracleDbType = OracleDbType.Array;
p.Direction = ParameterDirection.Input;
p.UdtTypeName = "MDSYS.SDO_ELEM_INFO_ARRAY";
//select contract 3 and 4
p.Value = new Decimal[] { 3, 4 };
comm.Parameters.Add(p);
int numContracts = 0;
using (OracleDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
numContracts++;
}
}
conn.Close();
}
}
}
O índice em contract.contractnum não é usado quando um omite dica / * + cardinalidade (aba 10) * /. Presumi contractnum é a chave principal para esta coluna serão indexados.
Veja também aqui: http://forums.oracle.com/ fóruns / thread.jspa? messageID = 3869879 # 3869879
Para usar o parâmetro com a declaração IN você pode usar esta construção:
select * from contract where contractnum
in (select column_value from table (:ContractNum))
onde ContractNum é o tipo de matriz personalizado.
Eu sei que isto é uma questão antiga, mas é um dos vários em que a resposta selecionada não resolver o meu problema e eu não quero começar ainda outra discussão sobre este tema, então eu vou colocar para baixo o que eu encontrei em minhas viagens na esperança de que ele pode ajudar alguém.
Eu não trabalho com a Oracle muito, mas, como em SQL Server, parece que para passar um parâmetro que você precisa ter um UDT (usuário tabela definida) correspondente para o qual você tem permissão para executar valor de tabela (eu poderia ser errado). Isto significa que outras respostas que sugerem o uso de um built-in SYS UDT vêm com algum frete e eu não conseguia descobrir se realmente é possível passar uma tabela para algo que não é um PL / SQL procedimento armazenado na versão atual de ODP.net.
Em segundo lugar, a solução cadeia de análise é um truque para todas as razões óbvias (não pode armazenar em cache o plano de execução ou o que a Oracle chama, não escala bem, etc).
Então eu passei um pouco muito tempo tentando fazer o IN-cláusula usando um parâmetro com valor de tabela em um datamart para que eu só ter permissão de leitura antes de eu foi atingido por um clarão ofuscante do óbvio ( Em nenhum menos um fórum ASP.net ). Acontece que a Oracle oferece suporte a consultas XML 'nativamente' Então, ao invés de passar uma matriz de valores você pode passar uma lista XML (se isso é tudo que você precisa). Mais uma vez, eu posso estar errado, mas ele é tratado como um parâmetro de ligação legítimo e este é um exemplo de como é simples de usar (vb.net, ADO.net, ODP.net usando pacote NuGet):
Dim xe As New XElement("l", New XElement("i", "ITEM-A"), New XElement("i", "ITEM-B"))
Using conn As New OracleConnection(myConnectionString)
conn.Open()
Using cmd As OracleCommand = conn.CreateCommand()
cmd.CommandType = CommandType.Text
Dim query As String
query = " SELECT s.FOO, q.BAR " & vbCrLf
query &= " FROM TABLE1 s LEFT OUTER JOIN " & vbCrLf
query &= " TABLE2 q ON q.ID = s.ID " & vbCrLf
query &= " WHERE (COALESCE(q.ID, 'NULL') NOT LIKE '%OPTIONAL%') AND "
query &= " (s.ID IN ("
query &= " SELECT stid "
query &= " FROM XMLTable('/l/i' PASSING XMLTYPE(:stid) COLUMNS stid VARCHAR(32) PATH '.')"
query &= " )"
query &= " )"
cmd.CommandText = query
Dim parameter As OracleParameter = cmd.Parameters.Add("stid", OracleDbType.NVarchar2, 4000)
parameter.Value = xe.ToString
Using r As OracleDataReader = cmd.ExecuteReader
While r.Read()
//Do something
End While
End Using
End Using
conn.Close()
Esta é mais uma observação do que uma solução cuidadosamente estudadas para comentário, por favor se há alguma coisa inadequada sobre fazê-lo desta forma.
EDIT. Há aparentemente um limite de 4000 caracteres usando este método (2000 se NVARCHAR) então eu tive que assistir a minha paginação. A mensagem de erro informativo você ganha se você passar por cima é 'ORA-01460: por implementar ou conversão razoável solicitado'