Comment créer une fonction d'agrégation définie par l'utilisateur?

8

J'ai besoin d'une fonction d'agrégation que MySQL ne fournit pas.

Je voudrais que ce soit dans la version MySQL de SQL (c'est-à-dire pas en C).

Comment puis-je faire cela? Ce que je suis bloqué, c'est la création d'une fonction d'agrégation - les documents ne semblent pas mentionner comment cela est fait.

Exemples d'utilisation souhaitée d'une productfonction:

mysql> select product(col) as a from `table`;
+------+
| a    |
+------+
|  144 |
+------+
1 row in set (0.00 sec)

mysql> select col, product(col) as a from `table` group by col;
+-----+------+
| col | a    |
+-----+------+
|   6 |   36 |
|   4 |    4 |
+-----+------+
2 rows in set (0.01 sec)
Matt Fenwick
la source

Réponses:

7

Selon la documentation http://dev.mysql.com/doc/refman/5.5/en/adding-udf.html, il est uniquement possible d'écrire des fonctions d'agrégation en C. Désolé!

Colin 't Hart
la source
Soit C ou C ++. Pas SQL en tout cas.
Mike Sherrill 'Cat Recall'
1
Je suppose que tout langage pouvant générer des bibliothèques binaires dans le format binaire pris en charge par la plate-forme avec les conventions d'appel C.
Colin 't Hart du
Je ne sais pas. Il a été documenté comme "C ou C ++ (ou un autre langage qui peut utiliser les conventions d'appel C)" dans la version 5.0. La documentation a supprimé "ou un autre langage pouvant utiliser les conventions d'appel C" dans la version 5.1. C'est une phrase étrange à laisser tomber.
Mike Sherrill 'Cat Recall'
est-il disponible dans les versions récentes de mysql maintenant (après quelques années)?
Dinesh
9

Je ne sais pas s'il existe un moyen de définir une nouvelle fonction d'agrégation, non sans jouer avec le code source de MySQL.

Mais si vos chiffres sont tous positifs, vous pouvez très bien dériver de l'identité arithmétique:

log( product( Ai ) ) = sum( log( Ai ) )

que vous pouvez utiliser EXP(SUM(LOG(x)))pour calculer PRODUCT(x). Test dans SQL-Fiddle :

SELECT EXP(SUM(LOG(a))) AS product
FROM t ;

SELECT col, EXP(SUM(LOG(a))) AS product
FROM t 
GROUP BY col ;

Lorsque les données peuvent avoir des 0, cela devient un peu plus compliqué:

SELECT (NOT EXISTS (SELECT 1 FROM t WHERE a = 0)) 
       * EXP(SUM(LOG(a))) AS p
FROM t 
WHERE a > 0 ;

SELECT d.col, 
       (NOT EXISTS (SELECT 1 FROM t AS ti WHERE ti.col = d.col AND ti.a = 0)) 
       * COALESCE(EXP(SUM(LOG(t.a))),1)  AS p
FROM 
    ( SELECT DISTINCT col
      FROM t
    ) AS d
  LEFT JOIN
    t  ON  t.col = d.col
       AND t.a > 0
GROUP BY d.col ;

Testé chez SQL-Fiddle


Pour les autres SGBD, qui n'ont pas la conversion automatique de MySQL des valeurs booléennes en entiers, le

(NOT EXISTS (SELECT ...))

devrait être remplacé par:

(CASE WHEN EXISTS (SELECT 1...) THEN 0 ELSE 1 END) 

Spécifiquement pour Oracle, quelques modifications supplémentaires seront nécessaires, sans changer la logique de la réponse, uniquement parce qu'Oracle ne suit pas la norme ANSI stricte dans certains domaines. Testé à SQL-Fiddle-2

ypercubeᵀᴹ
la source
2
C'est mignon. Les mathématiques au secondaire sont revenues me hanter. +1 !!!
RolandoMySQLDBA
1
Cool math, mais je voulais en fait savoir comment créer une fonction d'agrégation en général. productétait juste censé être un exemple de plusieurs.
Matt Fenwick
C'est assez cool, mais ne fonctionne pas si l'une des valeurs est nulle, car log (0) n'est pas défini.
jameshfisher du
@jameshfisher Correct. On peut facilement écrire la condition supplémentaire, en vérifiant les zéros (où le produit serait nul bien sûr). Je ne pensais pas à l'époque qu'il fallait ajouter cette complication.
ypercubeᵀᴹ
Pour moi, la meilleure façon d'ajouter cette condition n'est pas claire. Nous ne pouvons pas ajouter la condition dans la fonction interne des valeurs: puisque nous voulons que PRODUCT(..., 0, ...) = 0, nous voulons que EXP(SUM(..., f(0), ...)) = 0, pour certains fque nous choisissons, mais pour satisfaire, nous avons besoin que SUM(..., f(0), ...) = LOG(0)- encore une fois contrariée par la même question que log (0 ) n'est pas défini. Nous devons vérifier la présence de zéro d'une autre manière, par exemple MIN(ABS(a)) = 0. Nous aurions donc SELECT CASE WHEN MIN(ABS(a)) = 0 THEN 0 ELSE EXP(SUM(LOG(a))) END AS product. Est-ce le genre de chose à laquelle vous pensiez?
jameshfisher
3

Dans le but d'apprendre à pêcher, j'ai réussi à compiler et à installer un "Hello, World!" UDF (fonction définie par l'utilisateur) pour MySQL trouvée ici . Le fichier hello_world.so (après avoir été respecté gcc -shared -o hello_world.so -I /usr/include/mysql hello_world.c) doit être stocké dans / usr / lib / mysql / plugins / avec 755 autorisations sur les systèmes Linux Ubuntu. [Le "-I / usr / include / mysql" est le chemin d'accès aux fichiers d'en-tête mysql; J'ai trouvé que mon code ne compilerait pas sans ce paramètre, mais YMMV.]

Le programme ne fait qu'imprimer la chaîne "Hello, World!" pour chaque enregistrement dans l'ensemble de données résultant d'une requête, mais c'est tout ce qu'il est censé faire. J'essaierai d'écrire une petite fonction d'agrégation dans les prochains jours. Il existe un exemple de fonction agrégée qui calcule le coût moyen d'un groupe d'enregistrements de prix et de quantité; la fonction SMALL ne devrait pas être si différente de cette fonction à la fin.

J'espère que cela t'aides.

Jeffrey Rolland
la source