Aktive Datensatz-Unterabfragen von CodeIgniter

32452
Rocket Hazmat

Ich verwende CodeIgniter bei der Arbeit, und in einer unserer Modelldateien befanden sich viele Unterabfragen. Ursprünglich musste ich jede Unterabfrage manuell schreiben und fragte mich, ob ich stattdessen aktive Datensätze verwenden könnte.

Um mir das Leben zu erleichtern, habe ich eine Unterabfragebibliothek für CodeIgniter erstellt.

Ich habe es in das CodeIgniter-Wiki gestellt, aber ich hatte nie wirklich einen Blick darauf. Kannst du mir sagen, ob es etwas gibt, was ich verbessern sollte oder was ich eigentlich nicht tun sollte?

PS Fühlen Sie sich frei, dies zu verwenden, wenn Sie möchten.

PPS join_rangeist eine Hilfsmethode für die Beantwortung dieser Frage .

PPPS Die aktuellste Version finden Sie hier .

class Subquery{
    var $CI;
    var $db;
    var $statement;
    var $join_type;
    var $join_on;

    function __construct(){
        $this->CI =& get_instance();
        $this->db = array();
        $this->statement = array();
        $this->join_type = array();
        $this->join_on = array();
    }

    /**
     * start_subquery - Creates a new database object to be used for the subquery
     *
     * @param $statement - SQL statement to put subquery into (select, from, join, etc.)
     * @param $join_type - JOIN type (only for join statements)
     * @param $join_on - JOIN ON clause (only for join statements)
     *
     * @return A new database object to use for subqueries
     */
    function start_subquery($statement, $join_type='', $join_on=1){
        $db = $this->CI->load->database('', true);
        $this->db[] = $db;
        $this->statement[] = $statement;
        if(strtolower($statement) == 'join'){
            $this->join_type[] = $join_type;
            $this->join_on[] = $join_on;
        }
        return $db;
    }

    /**
     * end_subquery - Closes the database object and writes the subquery
     *
     * @param $alias - Alias to use in query
     *
     * @return none
     */
    function end_subquery($alias=''){
        $db = array_pop($this->db);
        $sql = "({$db->_compile_select()})";
        $alias = $alias!='' ? "AS $alias" : $alias;
        $statement = array_pop($this->statement);
        $database = (count($this->db) == 0)
            ? $this->CI->db: $this->db[count($this->db)-1];
        if(strtolower($statement) == 'join'){
            $join_type = array_pop($this->join_type);
            $join_on = array_pop($this->join_on);
            $database->$statement("$sql $alias", $join_on, $join_type);
        }
        else{
            $database->$statement("$sql $alias");
        }
    }

    /**
     * join_range - Helper function to CROSS JOIN a list of numbers
     *
     * @param $start - Range start
     * @param $end - Range end
     * @param $alias - Alias for number list
     * @param $table_name - JOINed tables need an alias(Optional)
     */
    function join_range($start, $end, $alias, $table_name='q'){
        $range = array();
        foreach(range($start, $end) AS $r){
            $range[] = "SELECT $r AS $alias";
        }
        $range[0] = substr($range[0], 7);
        $range = implode(' UNION ALL ', $range);

        $sub = $this->start_subquery('join', 'inner');
        $sub->select($range, false);
        $this->end_subquery($table_name);
    }
}

Verwendungsbeispiel

Diese Abfrage:

SELECT `word`, (SELECT `number` FROM (`numbers`) WHERE `numberID` = 2) AS number
FROM (`words`) WHERE `wordID` = 3 

würde werden:

$this->db->select('word')->from('words')->where('wordID', 3);
$sub = $this->subquery->start_subquery('select');
$sub->select('number')->from('numbers')->where('numberID', 2);
$this->subquery->end_subquery('number'); 
Antworten
26
@BenV: Warum haben Sie das Tag von `Codeigniter` in` Code-Igniter` geändert? Rocket Hazmat vor 10 Jahren 0
Warum ist $ db ein Array? rightfold vor 10 Jahren 1
@Rocket: Ich dachte, der richtige Name enthielt ein Leerzeichen, aus dem ein Bindestrich angebracht wäre, aber ich habe nur nachgesehen und es ist alles ein Wort, das Ihren Tag richtig macht. Fühlen Sie sich frei, um zurück zu rollen. BenV vor 10 Jahren 0
@Time Machine: `$ db` ist ein Array, da jedes Mal, wenn Sie` start_subquery` aufrufen, ein neues Datenbankobjekt erstellt wird. Dies ermöglicht Unterabfragen in Unterabfragen. Rocket Hazmat vor 10 Jahren 0
Können Sie uns eine Beispielanwendung geben? RobertPitt vor 10 Jahren 0
@RobertPitt: Ich habe der Frage ein Beispiel hinzugefügt. Rocket Hazmat vor 10 Jahren 0
Wenn Sie neugierig sind, finden Sie die neueste Version hier: https://github.com/NTICompass/CodeIgniter-Subqueries Rocket Hazmat vor 9 Jahren 0
schön ... trotzdem, können wir eine Unterabfrage unter der Unterabfrage machen? Kann ich andererseits unbegrenzt rekursiv Unterabfragen haben? zfm vor 8 Jahren 0
@zfm: Ja kannst du! "start_subquery" verfolgt, wie oft es aufgerufen wurde. Wenn also "end_subquery" aufgerufen wird, weiß es, wo die Unterabfrage abgelegt werden soll. http://pastebin.com/KxfrHb1J Rocket Hazmat vor 8 Jahren 0
@Rocket: Nach dem, was Sie über Pastebin geschrieben haben, gab es zwei Unterabfragen auf derselben Ebene. Ist es möglich, etwas wie $ $ = $ sub-> subquery ... zu haben, so wird das Ergebnis in etwa wie folgt aussehen: SELECT * FROM A WHERE xxx IN (SELECT xxx FROM B WHERE yyy IN (SELECT ...)) ` zfm vor 8 Jahren 0
@zfm: Das Pastebin-Beispiel erzeugt Folgendes: SELECT-Wort (SELECT-Nummer FROM (Zahlen) WHERE numberID = 2 AND ab IN (SELECT-Test FROM (Testen) WHERE a = 12)) AS-Nummer FROM (Wörter) WHERE wordID = 3 ':-D Rocket Hazmat vor 8 Jahren 0
@zfm: Wenn Sie "end_subquery" aufrufen, wird es unter dem zuletzt geöffneten "start_subquery" verschachtelt. Das Beispiel für Pastebin * wird also tun, was Sie wollen. :-D Rocket Hazmat vor 8 Jahren 0
Ich habe es bekommen ... vielen Dank! So eine wundervolle Bibliothek ist das :) zfm vor 8 Jahren 0
@zfm: Gern geschehen, und danke, dass du es benutzt hast :-) Rocket Hazmat vor 8 Jahren 0

3 Antworten auf die Frage

15
RobertPitt

Ich persönlich denke, Sie gehen in Sachen Dinge in die falsche Richtung, Sie können leicht eine selectAbfragezeichenfolge in die Methode eingeben und den 2. Parameter auf true setzen, um Backticks zu umgehen.

Die Ausgabe würde also die Unterabfragezeichenfolge innerhalb der Hauptabfrage auswählen.

Ich würde etwas im Sinne von tun:

class MyModel extends Model
{
    public function getRows()
    {
        //Create a subquery and render it to a stirng
        $sub = $this->db->select('number')->from('numbers')->where('numberID', 2)->_compile_select();

        //Clear the data from the CI Arrays
        $this->db->_reset_select();

        //Build the main query passing in the sub-query and disabling backticks
        $this->db->select("word,(" . $sub . ")", false)->where('wordID', 3);

        //Get the results
        $result = $this->get("words");
    }
}

Quellen:

Lassen Sie mich zunächst sagen, dass der obige Code möglicherweise nicht vollständig funktioniert, da ich keine ATM-Maschine getestet habe, aber ich weiß, dass dies möglich ist und Sie nicht die gesamte zusätzliche Logik benötigen.

Es scheint mir ziemlich einfach zu sein, ohne neue zu erstellen $db.

Ich würde auch empfehlen, die obige Logik in eine Klasse zu kapseln, damit Sie die Objekte herumgeben und das Leben einfacher machen können, da es sich bei dem oben genannten um einen POC handelt


Konzept:

class InnerQuery extends CI_DB_active_record
{
    public function __construct()
    {   
    }

    public function __call($method,$params = array())
    {
        //Remove methods that modify the database
        switch(strtolower($method))
        {
             case 'get':
             case 'count_all_results':
             case 'get_where':
                 trigger_error("Cannot use {$method} in InnerQuery");
             break;
        }
        return $this;
    }

    public function compile()
    {
        return "(" . $this->_compile_select() . ")";
    }

    public function __tostring()
    {
        return $this->compile();
    }
}

Die obige Klasse erweitert also dasselbe Objekt wie $this->dbin Ihrem Controller, sodass Sie alle Methoden verwenden können, um eine Abfrage zu erstellen, z

$this->InnerQuery->select("item as item_key")->from("inner_table")->where("foo","zed");

Sie sollten die übergeordneten Methoden deaktivieren, die die Datenbank ändern, oder Abfragen ausführen, da dies nur zum Erstellen einer Auswahlzeichenfolge verwendet wird.

so sollten Sie in der Lage sein zu tun:

$this->db->select("word")->where('wordID', 3);
$this->db->select($this->InnerQuery,false);

Diese würde die DB-Klasse verwenden, um Ihre Abfrage zu erstellen, und sie kann einfach an die äußere Auswahl übergeben werden und die __tostringgibt die (SELECT ...)geschweiften Klammern zurück und übergibt sie an die Hauptabfrage.

Ich möchte lieber nicht _compile_select () und _reset_select () für das DB-Hauptobjekt aufrufen. Das würde bedeuten, dass ich vor dem Rest der Abfrage alle Unterabfragen deklarieren muss, und das möchte ich nicht. Der Zweck dieser Bibliothek ist es auch, dies zu abstrahieren. Rocket Hazmat vor 9 Jahren 0
Ich habe aber auch gesagt, dass * ich empfehlen würde, die obige Logik in eine Klasse zu kapseln, damit Sie das Objekt herumgeben und das Leben einfacher machen können. * Dies würde das Problem lösen RobertPitt vor 9 Jahren 0
Ich habe ursprünglich versucht, die aktive Datensatzklasse zu erweitern, aber das ist fehlgeschlagen. Ich glaube, ich habe es falsch gemacht. Ich mag deine Methode wirklich sehr. Ich werde das wahrscheinlich tun, wenn ich die Zeit habe, meine Bibliothek zu aktualisieren. Rocket Hazmat vor 9 Jahren 0
Kein Problem, entschuldige die Verwirrung oben, hoffe, du bekommst eine stabilere Klasse zusammen :) RobertPitt vor 9 Jahren 1
Ausgezeichnet. sehr nützlich und einfach großartig ... weiter so und Gott segne dich. Allinonescript.com (http://allinonescript.com) Entwickler in aller Welt erwerben Wissen. Vadivel S vor 2 Jahren 0
4
Hailwood

Ich vermisse hier vielleicht etwas, aber für mich scheint es, als hätten Sie eine Klasse, in die Sie eine vorgefertigte Abfrage geben.

Ich denke, wäre es nicht vorteilhaft, eine Unterabfrage auf dieselbe Weise wie die Abfragen der obersten Ebene erstellen zu lassen?

Sie übergeben keine vordefinierte Abfrage per se. "start_subquery" gibt Sie (eine Referenz auf) das Datenbankobjekt von CodeIgniter zurück. Sie können dann aktive Abfragemethoden für dieses Objekt aufrufen. `end_subquery` ruft die Abfrage vom db-Objekt ab, verpackt sie in` () `und fügt sie der Hauptabfrage hinzu. Rocket Hazmat vor 10 Jahren 0
Ich sehe, ich mag diesen Ansatz viel besser als selbst schreiben. Wenn ich es richtig durchgelesen habe, habe ich Zeit. Ich bin mir nicht sicher, ob es sehr viel anderes gibt. A + -Code, Haben Sie in Erwägung gezogen, den Code als Erweiterung im CI-Issue-Tracker zu verwenden? Hailwood vor 10 Jahren 1
Ich habe es im CodeIgniter-Wiki veröffentlicht. Rocket Hazmat vor 10 Jahren 0
3
crashcoder

benutze das

$this->db->where('`store_id` NOT IN (SELECT `store_id` FROM `user_store`)', NULL, FALSE);

Überprüfen Sie dies

Ummmm danke, aber das weiß ich schon. Diese Frage stammt aus dem Januar 2011, ich habe im Juli 2011 tatsächlich Unterstützung für "WHERE" in meinen Code eingefügt. Die neueste Version meines Codes finden Sie hier: https://github.com/NTICompass/CodeIgniter-Subqueries Rocket Hazmat vor 8 Jahren 1