Puis-je créer une vue avec un paramètre dans MySQL?

91

J'ai une vue comme celle-ci:

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

J'aimerais le rendre plus générique, cela signifie changer 2 en variable. J'ai essayé ceci:

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

Mais MySQL ne le permet pas.

J'ai trouvé une solution de contournement laide:

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

Et puis la vue est:

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

Mais cela a l'air vraiment merdique, et l'utilisation est également merdique - je dois définir @MyVariable avant chaque utilisation de la vue.

Y a-t-il une solution que je pourrais utiliser comme celle-ci:

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

La situation concrète est la suivante: j'ai une table contenant des informations sur la demande refusée:

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;

Une multiplicité est un nombre de requêtes identiques enregistrées dans la même seconde. Je veux afficher une liste de refus, mais parfois, lorsque l'application est refusée, elle réessaye plusieurs fois pour m'en assurer. Donc, généralement, lorsque le même utilisateur obtient un refus 3 fois sur la même fonctionnalité en quelques secondes, il s'agit en fait d'un refus. Si nous avions une ressource de plus, pour répondre à cette demande, les deux prochains refus ne se produiraient pas. Nous souhaitons donc regrouper les refus dans le rapport permettant à l'utilisateur de spécifier la période dans laquelle les refus doivent être regroupés. Par exemple, si nous avons des refus (pour l'utilisateur 1 sur la fonctionnalité 1) dans les horodatages: 1, 2, 24, 26, 27, 45 et que l'utilisateur souhaite regrouper les refus qui sont plus proches les uns des autres que 4 secondes, il devrait obtenir quelque chose comme ceci: 1 (x2), 24 (x3), 45 (x1). Nous pouvons supposer que les espaces entre les dénégations réelles sont beaucoup plus grands qu'entre les duplications.

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;

Ensuite, pour afficher les refus des utilisateurs 1 et 2 sur les fonctionnalités 3 et 4 fusionnées toutes les 5 secondes, tout ce que vous avez à faire est:

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

J'utilise la vue car il est facile de filtrer les données et de l'utiliser explicitement dans la grille jQuery, de les commander automatiquement, de limiter le nombre d'enregistrements, etc.

Mais c'est juste une vilaine solution de contournement. Existe-t-il une bonne façon de procéder?

ssobczak
la source

Réponses:

158

En fait, si vous créez func:

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

et voir:

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

Ensuite, vous pouvez appeler une vue avec un paramètre:

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

J'espère que cela aide.

Leonard Strashnoy
la source
30
Wow, c'est l'une des choses les plus piratées que j'ai jamais vues en SQL;) Mais c'est exactement ce que je voulais faire.
ssobczak
2
Cette technique fonctionne lors de la création d'une vue dans une procédure stockée, lorsque la vue créée dépend d'un varchar passé à la procédure stockée. Dans ce cas, je devais 'set @ p1 = 12;' sur la ligne avant l'appel pour créer la vue.
Clayton Stanley
2
Existe-t-il un potentiel de problèmes (confusion des données du client) si plusieurs clients de base de données appellent ce code simultanément?
Gruber
2
@Mr_and_Mrs_D la table dérivée a besoin d'un alias. vous pouvez l'appeler comme vous voulez, mais vous ne pouvez pas l'omettre
Robin Kanters
4
La variable p1 conserve sa valeur après cela, donc si vous utilisez à nouveau la vue sans passer le paramètre, elle utilisera la précédente passée - ce qui peut être déroutant! Vous pouvez le «effacer» après son utilisation comme ceci: sélectionnez s. * De (sélectionnez p1: = 12 p) pass, h_parm s, (sélectionnez @ p1: = - 1) effacer; (En supposant que -1 est une valeur non valide à cet effet)
BuvinJ
21
CREATE VIEW MyView AS
   SELECT Column, Value FROM Table;


SELECT Column FROM MyView WHERE Value = 1;

Est la bonne solution dans MySQL, certains autres SQL vous permettent de définir des vues plus précisément.

Remarque: à moins que la vue ne soit très compliquée, MySQL optimisera cela très bien.

MindStalker
la source
1
Dans mon cas, la partie WHERE, dans laquelle je veux utiliser le paramètre est en neasted select, il est donc impossible de la filtrer de l'extérieur de la vue.
ssobczak
En fait, les sélections imbriquées ne sont pas autorisées dans les vues, mais je les ai divisées en deux vues. V1 filtre et agrège les données, et en plus de V1, il y a V2. Je ne peux pas filtrer les données de V1 à l'extérieur (dans V2), car à l'extérieur, elles sont visibles comme agrégées.
ssobczak
2
Ensuite, n'utilisez pas du tout une vue, si vous avez besoin d'un contrôle exact, créez la requête entière à chaque fois ou créez la requête dans une procédure stockée. Enregistrer en tant que vue semble inutile. Cependant, si vous postez les requêtes que vous essayez d'atteindre, quelqu'un pourrait être en mesure de suggérer un itinéraire différent / meilleur.
MindStalker
Je ne voulais pas faire cela, car cela rendra ma question simple assez complexe, mais si vous pensez que cela peut être utile, je vais essayer.
ssobczak
1

J'ai précédemment proposé une solution de contournement différente qui n'utilise pas de procédures stockées, mais utilise à la place une table de paramètres et un peu de magie connection_id ().

EDIT (copié à partir des commentaires)

créer une table qui contient une colonne appelée connection_id(en faire un bigint). Placez des colonnes dans cette table pour les paramètres de la vue. Mettez une clé primaire sur le connection_id. replacez dans la table des paramètres et utilisez CONNECTION_ID()pour renseigner la valeur connection_id. Dans la vue, utilisez une jointure croisée avec la table de paramètres et mettez WHERE param_table.connection_id = CONNECTION_ID(). Cela va créer une jointure croisée avec une seule ligne de la table de paramètres, ce que vous voulez. Vous pouvez ensuite utiliser les autres colonnes de la clause where, par exemple where orders.order_id = param_table.order_id.

Justin Swanhart
la source
5
Laquelle? Veuillez nous en dire plus.
marzapower
1
créer une table qui contient une colonne appelée connection_id (en faire un bigint). Placez des colonnes dans cette table pour les paramètres de la vue. Mettez une clé primaire sur connection_id. remplacez-le dans la table de paramètres et utilisez CONNECTION_ID () pour remplir la valeur connection_id. Dans la vue, utilisez une jointure croisée avec la table de paramètres et mettez WHERE param_table.connection_id = CONNECTION_ID (). Cela va créer une jointure croisée avec une seule ligne de la table de paramètres, ce que vous voulez. Vous pouvez ensuite utiliser les autres colonnes de la clause where par exemple où orders.order_id = param_table.order_id.
Justin Swanhart
KLUDGE! Mais mignon.
Rick James