Posso criar visualização com parâmetro no MySQL?

91

Eu tenho uma visão assim:

CREATE VIEW MyView AS
   SELECT Column FROM Table WHERE Value = 2;

Eu gostaria de torná-lo mais genérico, significa transformar 2 em uma variável. Eu tentei isso:

CREATE VIEW MyView AS
   SELECT Column FROM Table WHERE Value = @MyVariable;

Mas o MySQL não permite isso.

Eu encontrei uma solução alternativa feia:

CREATE FUNCTION GetMyVariable() RETURNS INTEGER DETERMINISTIC NO SQL
BEGIN RETURN @MyVariable; END|

E então a visão é:

CREATE VIEW MyView AS
   SELECT Column FROM Table WHERE Value = GetMyVariable();

Mas parece muito ruim, e o uso também é ruim - eu tenho que definir @MyVariable antes de cada uso da visualização.

Existe uma solução, que eu poderia usar assim:

SELECT Column FROM MyView(2) WHERE (...)

A situação concreta é a seguinte: Tenho uma tabela armazenando informações sobre a solicitação negada:

CREATE TABLE Denial
(
    Id INTEGER UNSIGNED AUTO_INCREMENT,
        PRIMARY KEY(Id),
    DateTime DATETIME NOT NULL,
    FeatureId MEDIUMINT UNSIGNED NOT NULL,
        FOREIGN KEY (FeatureId)
            REFERENCES Feature (Id)
            ON UPDATE CASCADE ON DELETE RESTRICT,
    UserHostId MEDIUMINT UNSIGNED NOT NULL,
        FOREIGN KEY (UserHostId)
            REFERENCES UserHost (Id)
            ON UPDATE CASCADE ON DELETE RESTRICT,
    Multiplicity MEDIUMINT UNSIGNED NOT NULL DEFAULT 1,
    UNIQUE INDEX DenialIndex (FeatureId, DateTime, UserHostId)
) ENGINE = InnoDB;

Uma multiplicidade é um número de solicitações idênticas registradas no mesmo segundo. Quero exibir uma lista de negações, mas às vezes, quando o aplicativo é negado, ele tenta novamente algumas vezes apenas para ter certeza. Normalmente, quando o mesmo usuário obtém negação 3 vezes no mesmo recurso em alguns segundos, é na verdade uma negação. Se tivéssemos mais um recurso, para atender a essa solicitação, as próximas duas negações não aconteceriam. Portanto, queremos agrupar as negações no relatório, permitindo ao usuário especificar o intervalo de tempo em que as negações devem ser agrupadas. Por exemplo, se tivermos negações (para o usuário 1 no recurso 1) em carimbos de data / hora: 1,2,24,26,27,45 e o usuário quiser agrupar negações que estão mais próximas umas das outras do que 4 segundos, ele deve obter algo assim: 1 (x2), 24 (x3), 45 (x1). Podemos supor que os espaços entre negações reais são muito maiores do que entre duplicações.

CREATE FUNCTION GetDenialMergingTime()
    RETURNS INTEGER UNSIGNED
    DETERMINISTIC NO SQL
BEGIN
    IF ISNULL(@DenialMergingTime) THEN
        RETURN 0;
    ELSE
        RETURN @DenialMergingTime;
    END IF;
END|

CREATE VIEW MergedDenialsViewHelper AS
    SELECT MIN(Second.DateTime) AS GroupTime,
        First.FeatureId,
        First.UserHostId,
        SUM(Second.Multiplicity) AS MultiplicitySum
    FROM Denial AS First 
        JOIN Denial AS Second 
            ON First.FeatureId = Second.FeatureId
                AND First.UserHostId = Second.UserHostId
                AND First.DateTime >= Second.DateTime
                AND First.DateTime - Second.DateTime < GetDenialMergingTime()
    GROUP BY First.DateTime, First.FeatureId, First.UserHostId, First.Licenses;

CREATE VIEW MergedDenials AS
    SELECT GroupTime, 
        FeatureId,
        UserHostId, 
        MAX(MultiplicitySum) AS MultiplicitySum
    FROM MergedDenialsViewHelper
    GROUP BY GroupTime, FeatureId, UserHostId;

Então, para mostrar as negações do usuário 1 e 2 nos recursos 3 e 4 mesclados a cada 5 segundos, tudo o que você precisa fazer é:

SET @DenialMergingTime := 5;
SELECT GroupTime, FeatureId, UserHostId, MultiplicitySum FROM MergedDenials WHERE UserHostId IN (1, 2) AND FeatureId IN (3, 4);

Eu uso a view porque nela é fácil filtrar dados e usá-los explicitamente na grade jQuery, ordenar automaticamente, limitar o número de registros e assim por diante.

Mas é apenas uma solução alternativa feia. Existe uma maneira adequada de fazer isso?

ssobczak
fonte

Respostas:

158

Na verdade, se você criar func:

create function p1() returns INTEGER DETERMINISTIC NO SQL return @p1;

e ver:

create view h_parm as
select * from sw_hardware_big where unit_id = p1() ;

Em seguida, você pode chamar uma visualização com um parâmetro:

select s.* from (select @p1:=12 p) parm , h_parm s;

Espero que ajude.

Leonard Strashnoy
fonte
30
Uau, essa é uma das coisas mais hackeadas que já vi no SQL;) Mas é exatamente o que eu queria fazer.
ssobczak
2
Essa técnica funciona ao criar uma visualização dentro de um procedimento armazenado, quando a visualização criada depende de um varchar passado para o procedimento armazenado. Neste caso, tive que 'definir @ p1 = 12;' na linha antes da chamada para criar a visualização.
Clayton Stanley
2
Há algum potencial para problemas (confusão de dados do locatário) se vários locatários do banco de dados chamarem esse código simultaneamente?
Gruber
2
@Mr_and_Mrs_D a tabela derivada precisa de um alias. você pode chamá-lo do que quiser, mas não pode omitir
Robin Kanters
4
A variável p1 retém seu valor depois disso, então se você usar a visualização novamente sem passar o parâmetro, ela usará a anterior passada - o que pode ser confuso! Você pode "apagá-lo" depois de usá-lo desta forma: selecione s. * Em (selecione p1: = 12 p) passe, h_parm s, (selecione @ p1: = - 1) limpe; (Supondo que -1 seja um valor inválido para este propósito)
BuvinJ
21
CREATE VIEW MyView AS
   SELECT Column, Value FROM Table;


SELECT Column FROM MyView WHERE Value = 1;

É a solução adequada no MySQL, alguns outros SQLs permitem definir Views mais exatamente.

Nota: A menos que a Visualização seja muito complicada, o MySQL a otimizará perfeitamente.

MindStalker
fonte
1
No meu caso, a parte WHERE, na qual quero usar o parâmetro, está no neasted select, então é impossível filtrar de fora da visualização.
ssobczak
Na verdade, seleções neasted não são permitidas nas visualizações, mas eu as dividi em duas visualizações. V1 filtra e agrega dados e, no topo de V1, há V2. Não consigo filtrar dados de V1 fora dela (em V2), porque fora eles são visíveis como agregados.
ssobczak
2
Então, não use uma visão, se você precisar de controle exato, crie a consulta inteira todas as vezes ou crie a consulta dentro de um procedimento armazenado. Salvar como uma vista parece inútil. Porém, se você postar as perguntas que está tentando alcançar, alguém poderá sugerir um caminho diferente / melhor.
MindStalker
Eu não queria fazer isso, porque vai tornar minha pergunta simples bastante complexa, mas se você acha que pode ser útil, vou tentar.
ssobczak
1

Eu vim anteriormente com uma solução alternativa diferente que não usa procedimentos armazenados, mas usa uma tabela de parâmetros e alguma mágica connection_id ().

EDIT (copiado dos comentários)

crie uma tabela que contém uma coluna chamada connection_id(torne-a um bigint). Coloque colunas nessa tabela para parâmetros da visualização. Coloque uma chave primária no connection_id. substitua na tabela de parâmetros e use CONNECTION_ID()para preencher o valor connection_id. Na visualização, use uma junção cruzada com a tabela de parâmetros e coloque WHERE param_table.connection_id = CONNECTION_ID(). Isso fará a junção cruzada com apenas uma linha da tabela de parâmetros que é o que você deseja. Você pode então usar as outras colunas na cláusula where, por exemplo, where orders.order_id = param_table.order_id.

Justin Swanhart
fonte
5
Qual? Por favor, diga-nos algo mais.
marzapower
1
crie uma tabela que contém uma coluna chamada connection_id (torne-a um bigint). Coloque colunas nessa tabela para parâmetros da visualização. Coloque uma chave primária no connection_id. substitua na tabela de parâmetros e use CONNECTION_ID () para preencher o valor de connection_id. Na visualização, use uma junção cruzada para a tabela de parâmetros e coloque WHERE param_table.connection_id = CONNECTION_ID (). Isso fará a junção cruzada com apenas uma linha da tabela de parâmetros que é o que você deseja. Você pode então usar as outras colunas na cláusula where, por exemplo, onde orders.order_id = param_table.order_id.
Justin Swanhart
KLUDGE! Mas fofo.
Rick James