Metoda tworzenia podkwerendy przy użyciu JDatabase

31

Na stronie http://docs.joomla.org/Selecting_data_using_JDatabase nie ma udokumentowanej metody pisania podzapytania za pomocą JDatabase.

https://gist.github.com/gunjanpatel/8663333 stanowi przykład jednego ze sposobów osiągnięcia tego celu (pominięto kilka bitów):

$subQuery = $db->getQuery(true);
$query    = $db->getQuery(true);

// Create the base subQuery select statement.
$subQuery->select('*')
    ->from($db->quoteName('#__sub_table'))
    ->where($db->quoteName('subTest') . ' = ' . $db->quote('1'));

// Create the base select statement.
$query->select('*')
    ->from($db->quoteName('#__table'))
    ->where($db->quoteName('state') . ' = ' . $db->quote('1'))
    ->where($db->quoteName('subCheckIn') . ' IN (' . $subQuery->__toString() . ')')
    ->order($db->quoteName('ordering') . ' ASC');

// Set the query and load the result.
$db->setQuery($query);

To wydaje się być dobrym, możliwym do przyjęcia podejściem, ale czy jest lepsze?

między mózgiem
źródło
4
Możesz pominąć wywoływanie metody toString () w $ subQuery. Joomla! automatycznie obsłuży to za ciebie. Poza tym używam tej samej metody i działa ona dla mnie dobrze.
Zachary Draper,
Jest to również ta sama metoda, której używamy w com_content w głównym github.com/joomla/joomla-cms/blob/staging/components/…
George Wilson
@ZacharyDraper ciekawe. Czy możesz pokazać kod, który jest za to odpowiedzialny?
Dmitry Rekun
3
@ZacharyDraper: PHP (zamiast Joomla! Per se) obsługuje to dla ciebie ( __toString()) jest metodą „magiczną”.
MrWhite
Tak, dziękuję w3d.
Zachary Draper

Odpowiedzi:

16

Tak, moim zdaniem sposób, w jaki zbudowałeś podkwerendę, został przyjęty przez większość programistów rozszerzeń joomla.

Używam tej samej metody na niektórych moich rozszerzeniach i niestandardowych rozszerzeniach wykonanych dla klientów.

Nie ma „oficjalnego” sposobu na zrobienie tego, ale robienie tego tak, jak pokazano, pozwala korzystać z narzędzia do tworzenia zapytań i nadal zapewnia dobrą czytelność

Skullbock
źródło
10

AFAIK nie ma wbudowanego sposobu wykonywania łatwych podkwerend, co prawdopodobnie jest wadą systemu i powinno zostać poprawione przez PR.

Jednak nie widzę problemu z twoim przykładem - wydaje się dość rozsądny.

~~~

Oto przykład w odpowiedzi na komentarz @ DavidFritsch poniżej. Im więcej o tym myślę, tym bardziej podoba mi się prostsze podejście przedstawione w PO. Bardziej jasne jest, co się dzieje.

$query = $this->db->getQuery(true)
  ->select('a.*')
  ->subQuery()
    ->select('b.*')
    ->from('#__table_b AS b')
    ->as('subQueryResult')
  ->endSubQuery()
  ->from('#__table_a AS a');
Don Gilbert
źródło
1
Czy masz pojęcie, jak to zrobić? Próbuję wyobrazić sobie format, którego użyłbyś, aby działał na jednym obiekcie zapytania i nic nie wydaje się łatwiejsze niż ta metoda.
David Fritsch
1
Warto może być subQuerySelectmetoda, która pozwoli ci to zrobić nieco „czysto”. Zmienię swoją odpowiedź, aby podać i podać przykład.
Don Gilbert,
Bardzo chciałbym to zobaczyć w Joomla
fruppel
3

Istnieje również sposób wykonywania zapytań zawierających podzapytania za pomocą interfejsu API platformy Joomla. Podstawowa idea korzystania z podkwerend opiera się na gunjanpatel .

Oto przykład wykonywania zapytań w modelach zestawów zagnieżdżonych :

Zapytanie SQL:

-- Find the Immediate Subordinates of a Node
SELECT node.title, (COUNT(parent.id) - (sub_tree.depth + 1)) AS depth
FROM lubd3_usergroups AS node,
        lubd3_usergroups AS parent,
        lubd3_usergroups AS sub_parent,
        (
                SELECT node.id, (COUNT(parent.id) - 1) AS depth
                FROM lubd3_usergroups AS node,
                        lubd3_usergroups AS parent
                WHERE node.lft BETWEEN parent.lft AND parent.rgt
                        AND node.id = 1
                GROUP BY node.id
                ORDER BY node.lft
        )AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
        AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
        AND sub_parent.id = sub_tree.id
GROUP BY node.id
-- not showing the parent node
HAVING depth = 1
-- showing the parent node
-- HAVING depth <= 1
ORDER BY node.lft;

i przekształcone zapytanie do wykonania przez Joomla:

// Create the subQuery select statement.
// Nested Set Queries: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
// CROSS JOIN: http://www.informit.com/articles/article.aspx?p=30875&seqNum=5
$subQuery->select(array('node.id', '(COUNT(parent.id) - 1) AS depth'))
    ->from($db->quoteName('#__usergroups') . 'node')
    ->join('CROSS', $db->quoteName('#__usergroups', 'parent'))
    ->where($db->quoteName('node.lft') . ' BETWEEN  ' . $db->quoteName('parent.lft') . ' AND ' . $db->quoteName('parent.rgt') . ' AND ' . $db->quoteName('node.id') . ' = ' . $db->quote('1'))
    ->group($db->quoteName('node.id'))
    ->order($db->quoteName('node.lft'));

// Create the base select statement.
$query->select(array('node.title', '(COUNT(parent.id) - (sub_tree.depth + 1)) AS depth'))
    ->from($db->quoteName('#__usergroups') . 'node')
    ->join('CROSS', $db->quoteName('#__usergroups', 'parent'))
    ->join('CROSS', $db->quoteName('#__usergroups', 'sub_parent'))
    ->join('CROSS', '(' . $subQuery .') AS sub_tree')
    ->where($db->quoteName('node.lft') . ' BETWEEN  ' . $db->quoteName('parent.lft') . ' AND ' . $db->quoteName('parent.rgt')
    . ' AND ' . $db->quoteName('node.lft') . ' BETWEEN  ' . $db->quoteName('sub_parent.lft') . ' AND ' . $db->quoteName('sub_parent.rgt')
    . ' AND ' . $db->quoteName('sub_parent.id') . ' = ' . $db->quoteName('sub_tree.id'))
    ->group($db->quoteName('node.id'))
    ->having($db->quoteName('depth') . ' = ' . $db->quote('1'))
    ->order($db->quoteName('node.lft'));

// Set the query and load the result.
$db->setQuery($query);
$rowList = $db->loadAssocList();

echo "<pre>";
print_r($rowList);
echo "</pre>";
Mario Neubauer
źródło
1
Wygląda dobrze, ale jest absolutnie taki sam, jak w przykładzie PO: Najpierw utwórz podzapytanie, a następnie użyj go w głównym zapytaniu. Pytanie brzmiało, czy istnieje lepszy sposób.
fruppel
1

Przedstawię moją wersję tego fragmentu, a następnie wyjaśnię moje uzasadnienie i dołączę cytaty z podręcznika Standardy kodowania Joomla (które zostaną sformatowane w formacie queblock).

$subquery = $db->getQuery(true)
    ->select("checkin")
    ->from("#__sub_table")
    ->where("subTest = 1");

$query = $db->getQuery(true)
    ->select("*")
    ->from("#__table")
    ->where([
        "state = 1",
        "subCheckIn IN ({$subQuery})"
    ])
    ->order("ordering");

$db->setQuery($query);

Użyj Łańcuchu zapytań, aby połączyć kilka metod zapytań, jedna po drugiej, przy czym każda metoda zwraca obiekt, który może obsługiwać następną metodę. Poprawia to czytelność i upraszcza wynikowy kod.

  • Najpierw piszę zapytania wewnętrzne i przechodzę do zapytania najbardziej zewnętrznego. To pozwala mi połączyć wszystkie metody budowania zapytań bezpośrednio do getQuery()metody. W rzeczywistości nazwa zmiennej jest zapisywana tylko raz podczas budowania pojedynczego zapytania.
    Oto wspaniały przykład niektórych zagnieżdżonych zapytań (kiedy pomyślałem, że ładnie jest ustawić rzędy w łańcuchach).

  • Staram się unikać wykonywania wielu select()i / lub where()wywołań w ramach tego samego zapytania, ponieważ widziałem, że prowadzi to do zamieszania mniej doświadczonych programistów . Ponieważ metody te akceptują tablice, uważam, że ich stosowanie jest bardziej czytelne i lepsza praktyka kodowania.

  • i wreszcie najbardziej kontrowersyjny temat ...

    Nazwy tabel i nazwy kolumn tabeli powinny być zawsze ujęte w metodzie quoteName (), aby uniknąć nazw tabel i kolumn tabeli. Wartości pól sprawdzane w zapytaniu powinny zawsze być zawarte w metodzie quote (), aby uniknąć wartości przed przekazaniem jej do bazy danych. Wartości pól liczb całkowitych sprawdzane w zapytaniu powinny być również rzutowane na (int).

    Jestem bardzo skonfliktowany w tej postawie. Kiedy po raz pierwszy przyszedłem do Joomla w zeszłym roku, pomyślałem, że nie będę wykonywać bezużytecznych połączeń (bez korzyści dla stabilności, bezpieczeństwa, czytelności zapytania) na wartościach statycznych! Jednak mój pracodawca lubi pomysł toeing linię Joomla, i muszę przyznać, że zazwyczaj mają wysokie uznanie dla reguł, więc zostały niwecząc w dół moje zapytania z quote(), (int)i quoteName()co oznacza również mnóstwo łańcuchów znaków (wszystkie odpowiednio rozmieszczone). Końcowymi rezultatami mojej pracy są strasznie rozdęte bloki zapytań, które nawet ja trudno mi się przyglądać. Najgorsze / najdłuższe linie, które nie nadają się do układania w stosy w pionie, to join()wywołania ze względu na tablename, alias ON, a następnie jeden lub więcej warunków, które mogą wymagać cytowania lub nie.Rozumiem, że ta polityka jest wdrażana z myślą o bezpieczeństwie dla początkujących programistów, ale z pewnością chciałbym, aby ta polityka została w jakiś sposób zahartowana wrażliwością, że nie wszyscy koderzy Joomla są ignorantami kopiującymi. Mam na myśli, spójrz jak czysty i krótki kod wygląda bez zbędnych wywołań.

  • Jeśli chodzi o wycieranie:

    • Prawie nigdy nie używam *w moich klauzulach SELECT
    • Nigdy nie dzwonię __toString()
    • Nie cytuję liczb całkowitych, rzucam je jako liczby całkowite
    • Nie piszę, ASCponieważ jest to domyślny kierunek sortowania
    • Dokładam wszelkich starań, aby nie używać słów kluczowych mysql podczas tworzenia nowych nazw tabel i nazw kolumn
    • W ramach osobistych preferencji mam tendencję do stosowania podwójnego cytowania w argumentach łańcuchowych mojej metody w celu zachowania jednolitości, odróżnienia od pojedynczego cytowania mysql, aby móc cieszyć się zmienną interpolacją, którą zwykle piszę ze „ złożoną składnią ”.
    • Używam pouczających nazw zmiennych i komentarzy, aby poprawić czytelność moich zagnieżdżonych zapytań i ogólnie mojego kodu
    • Testuję kod, zanim opuści on moją opiekę
mickmackusa
źródło