From 108781db5baeafa9144dc2b00c75f050294ab163 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Thu, 6 Aug 2026 13:44:02 -0400 Subject: [PATCH] Move more select * to column select for further optimization --- CONTRIBUTING.md | 24 +++++++++++++++++++++-- admin/ai_providers.php | 2 +- admin/modals/user/user_archive.php | 4 ++-- admin/payment_providers.php | 3 ++- admin/users.php | 3 ++- agent/accounts.php | 2 +- agent/asset.php | 6 ++++-- agent/assets.php | 2 +- agent/contact.php | 4 +++- agent/dashboard.php | 2 +- agent/invoice.php | 5 ++--- agent/modals/asset/asset.php | 4 +++- agent/modals/contact/contact.php | 2 +- agent/modals/payment/payment_add.php | 2 +- agent/modals/payment/payment_bulk_add.php | 2 +- agent/modals/payment/payment_edit.php | 2 +- agent/modals/revenue/revenue_add.php | 2 +- agent/modals/revenue/revenue_edit.php | 2 +- agent/modals/transfer/transfer_add.php | 4 ++-- agent/modals/transfer/transfer_edit.php | 4 ++-- agent/post/asset.php | 4 ++-- agent/post/client.php | 4 ++-- agent/post/contact.php | 2 +- agent/post/credential.php | 2 +- agent/post/location.php | 2 +- agent/post/project.php | 3 ++- agent/post/rack.php | 2 +- agent/post/recurring_invoice.php | 3 ++- agent/project.php | 11 +++++++---- agent/projects.php | 4 +++- agent/recurring_invoices.php | 8 +++++++- agent/reports/profit_loss.php | 4 ++-- agent/software.php | 4 +++- client/recurring_invoices.php | 5 ++++- client/ticket.php | 4 ++-- client/unpaid_invoices.php | 3 ++- cron/nightly_tasks.php | 13 ++++++++++-- 37 files changed, 107 insertions(+), 52 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac74a5b51..5cf36d651 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -178,7 +178,8 @@ A user can be restricted to a subset of clients through `user_client_permissions **A list — `clientScopeSql()`.** Any query returning more than one row appends the fragment for that resource's own client column: ```php -$sql = mysqli_query($mysqli, "SELECT * FROM expenses +$sql = mysqli_query($mysqli, "SELECT expense_id, expense_date, expense_amount, expense_description + FROM expenses WHERE expense_archived_at IS NULL " . clientScopeSql('expense_client_id') . " ORDER BY expense_date DESC"); @@ -224,7 +225,26 @@ Per [SECURITY.md](SECURITY.md) — never in a public issue. ## Conventions -**Database naming.** Every column is prefixed with the singular name of the entity it belongs to: `tickets.ticket_id`, `tickets.ticket_subject`, `clients.client_name`. This makes JOIN results unambiguous and is why queries can `SELECT *` across joins safely. New tables must follow it. +**Database naming.** Every column is prefixed with the singular name of the entity it belongs to: `tickets.ticket_id`, `tickets.ticket_subject`, `clients.client_name`. This makes JOIN results unambiguous, so a `SELECT *` across joins is never *wrong*. New tables must follow it. + +**Select the columns you use, not `*`.** Unambiguous is not the same as cheap. `SELECT *` across three joined tables fetches every column of all three, including the `*_notes` and `*_details` TEXT columns, and throws away whatever the page never renders. A search result list that shows five fields was pulling sixty. List the columns instead: + +```php +$sql = mysqli_query($mysqli, "SELECT ticket_id, ticket_prefix, ticket_number, ticket_subject, client_name + FROM tickets + LEFT JOIN clients ON ticket_client_id = client_id + WHERE ticket_archived_at IS NULL + " . clientScopeSql('ticket_client_id') . ""); +``` + +Two things follow from that: + +- A query whose result only feeds `mysqli_num_rows()` needs no columns at all — write `SELECT 1`. Do not select a primary key "just in case": if the query joins two tables that both carry that column name, an unqualified `SELECT ticket_template_id` is an ambiguous-column error. +- Keep the join even when no column of the joined table survives into the `SELECT`, if the join is doing work — supplying a `WHERE` term, an `ORDER BY`, or the client column you scope on. Dropping a join is a separate decision from trimming the column list. + +The trade is real and worth stating: `SELECT *` picks up new columns for free, an explicit list does not. Add a column to a table and every query that needs it must be updated by hand, and the failure mode is a blank field or a PHP 8 undefined-key warning rather than an error. That is the price of not fetching data nobody reads, and the project has decided to pay it on anything that loops or touches a TEXT column. + +The exception is `api/v1/*/read.php`. Those endpoints hand the whole row to `read_output.php`, which serialises it straight into the JSON response — there the row *is* the output contract, so `SELECT *` is correct and trimming it would silently drop fields from every consumer. The prefix is the entity name, which is usually but not always the singular of the table name. Where a table is named for its container rather than its row, the prefix follows the row: `calendar_events` → `event_*`, `asset_interfaces` → `interface_*`, `invoice_items` / `quote_items` → `item_*`, `rack_units` → `unit_*`, `user_roles` → `role_*`, `product_stock` → `stock_*`. Pick the prefix your columns will read best as and use it for every column in the table. diff --git a/admin/ai_providers.php b/admin/ai_providers.php index 3bb6b5729..b9b728314 100644 --- a/admin/ai_providers.php +++ b/admin/ai_providers.php @@ -6,7 +6,7 @@ $order = "ASC"; require_once "includes/inc_all_admin.php"; -$sql = mysqli_query($mysqli, "SELECT * FROM ai_providers ORDER BY $sort $order"); +$sql = mysqli_query($mysqli, "SELECT ai_provider_api_key, ai_provider_api_url, ai_provider_id, ai_provider_name FROM ai_providers ORDER BY $sort $order"); $num_rows = mysqli_num_rows($sql); diff --git a/admin/modals/user/user_archive.php b/admin/modals/user/user_archive.php index 02637e8d9..f93205894 100644 --- a/admin/modals/user/user_archive.php +++ b/admin/modals/user/user_archive.php @@ -12,13 +12,13 @@ $user_email = escapeHtml($row['user_email']); $user_avatar = escapeHtml($row['user_avatar']); $user_initials = escapeHtml(initials($user_name)); -$sql_related_tickets = mysqli_query($mysqli, "SELECT * FROM tickets +$sql_related_tickets = mysqli_query($mysqli, "SELECT 1 FROM tickets WHERE ticket_assigned_to = $user_id AND ticket_resolved_at IS NULL AND ticket_closed_at IS NULL"); $ticket_count = mysqli_num_rows($sql_related_tickets); // Related Recurring Tickets Query -$sql_related_recurring_tickets = mysqli_query($mysqli, "SELECT * FROM recurring_tickets WHERE recurring_ticket_assigned_to = $user_id"); +$sql_related_recurring_tickets = mysqli_query($mysqli, "SELECT 1 FROM recurring_tickets WHERE recurring_ticket_assigned_to = $user_id"); $recurring_ticket_count = mysqli_num_rows($sql_related_recurring_tickets); diff --git a/admin/payment_providers.php b/admin/payment_providers.php index e80a62ea3..0f7545f03 100644 --- a/admin/payment_providers.php +++ b/admin/payment_providers.php @@ -6,7 +6,8 @@ $order = "ASC"; require_once "includes/inc_all_admin.php"; -$sql = mysqli_query($mysqli, "SELECT * FROM payment_providers +$sql = mysqli_query($mysqli, "SELECT account_name, category_name, payment_provider_description, payment_provider_id, + payment_provider_name, payment_provider_threshold, vendor_name FROM payment_providers LEFT JOIN accounts ON payment_provider_account = account_id LEFT JOIN vendors ON payment_provider_expense_vendor = vendor_id LEFT JOIN categories ON payment_provider_expense_category = category_id diff --git a/admin/users.php b/admin/users.php index 14e49f3d5..954ae331e 100644 --- a/admin/users.php +++ b/admin/users.php @@ -8,7 +8,8 @@ require_once "includes/inc_all_admin.php"; $sql = mysqli_query( $mysqli, - "SELECT SQL_CALC_FOUND_ROWS * FROM users + "SELECT SQL_CALC_FOUND_ROWS role_name, user_archived_at, user_avatar, user_config_force_mfa, user_email, + user_settings.user_id, user_name, user_role_id, user_status, user_token FROM users LEFT JOIN user_roles ON user_role_id = role_id LEFT JOIN user_settings ON users.user_id = user_settings.user_id WHERE (user_name LIKE '%$q%' OR user_email LIKE '%$q%') diff --git a/agent/accounts.php b/agent/accounts.php index ef8301d71..bae78d76b 100644 --- a/agent/accounts.php +++ b/agent/accounts.php @@ -11,7 +11,7 @@ enforceUserPermission('module_financial'); $sql = mysqli_query( $mysqli, - "SELECT SQL_CALC_FOUND_ROWS * FROM accounts + "SELECT SQL_CALC_FOUND_ROWS account_currency_code, account_id, account_name, account_notes, opening_balance FROM accounts WHERE (account_name LIKE '%$q%') AND account_archived_at IS NULL ORDER BY $sort $order LIMIT $record_from, $record_to" diff --git a/agent/asset.php b/agent/asset.php index 2e8e20976..d7e0e3bdc 100644 --- a/agent/asset.php +++ b/agent/asset.php @@ -122,7 +122,7 @@ if (isset($_GET['asset_id'])) { $recurring_ticket_count = mysqli_num_rows($sql_related_recurring_tickets); // Related Documents - $sql_related_documents = mysqli_query($mysqli, "SELECT * FROM asset_documents + $sql_related_documents = mysqli_query($mysqli, "SELECT 1 FROM asset_documents LEFT JOIN documents ON asset_documents.document_id = documents.document_id WHERE asset_documents.asset_id = $asset_id AND document_archived_at IS NULL @@ -252,7 +252,9 @@ if (isset($_GET['asset_id'])) { // Related Software Query $sql_related_software = mysqli_query( $mysqli, - "SELECT * FROM software_assets + "SELECT software_expire, software_assets.software_id, software_key, software_license_type, + software_name, software_notes, software_purchase, software_seats, software_type, + software_version FROM software_assets LEFT JOIN software ON software_assets.software_id = software.software_id WHERE software_assets.asset_id = $asset_id AND software_archived_at IS NULL diff --git a/agent/assets.php b/agent/assets.php index c80b2a093..1705d0ade 100644 --- a/agent/assets.php +++ b/agent/assets.php @@ -641,7 +641,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); $location_name_display = $location_name; } - $sql_credentials = mysqli_query($mysqli, "SELECT * FROM credentials WHERE credential_asset_id = $asset_id"); + $sql_credentials = mysqli_query($mysqli, "SELECT 1 FROM credentials WHERE credential_asset_id = $asset_id"); $credential_count = mysqli_num_rows($sql_credentials); // Tags diff --git a/agent/contact.php b/agent/contact.php index 2db7bee17..fc7aca669 100644 --- a/agent/contact.php +++ b/agent/contact.php @@ -83,7 +83,9 @@ if (isset($_GET['contact_id'])) { $asset_count = mysqli_num_rows($sql_related_assets); // Linked Software Licenses - $sql_linked_software = mysqli_query($mysqli, "SELECT * FROM software_contacts, software + $sql_linked_software = mysqli_query($mysqli, "SELECT software_expire, software_contacts.software_id, software_key, software_license_type, + software_name, software_notes, software_purchase, software_seats, software_type, + software_version FROM software_contacts, software WHERE software_contacts.contact_id = $contact_id AND software_contacts.software_id = software.software_id AND software_archived_at IS NULL diff --git a/agent/dashboard.php b/agent/dashboard.php index 237a497b1..61f336696 100644 --- a/agent/dashboard.php +++ b/agent/dashboard.php @@ -117,7 +117,7 @@ if ($user_config_dashboard_financial_enable == 1) { $profit = $total_income - $total_expenses; - $sql_accounts = mysqli_query($mysqli, "SELECT * FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC"); + $sql_accounts = mysqli_query($mysqli, "SELECT account_id, account_name, opening_balance FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC"); $sql_latest_invoice_payments = mysqli_query($mysqli, " SELECT client_name, invoice_number, invoice_prefix, payment_amount, payment_date FROM payments diff --git a/agent/invoice.php b/agent/invoice.php index c4129a555..660151858 100644 --- a/agent/invoice.php +++ b/agent/invoice.php @@ -130,8 +130,7 @@ if (isset($_GET['invoice_id'])) { //Get billable, and unbilled tickets to add to invoice $sql_tickets_billable = mysqli_query( $mysqli, " - SELECT - * + SELECT 1 FROM tickets WHERE @@ -199,7 +198,7 @@ if (isset($_GET['invoice_id'])) { // Saved Payment Methods $sql_saved_payment_methods = mysqli_query($mysqli, " - SELECT * FROM client_saved_payment_methods + SELECT 1 FROM client_saved_payment_methods LEFT JOIN payment_providers ON client_saved_payment_methods.saved_payment_provider_id = payment_providers.payment_provider_id WHERE saved_payment_client_id = $client_id diff --git a/agent/modals/asset/asset.php b/agent/modals/asset/asset.php index 7ad997ff0..0922e67fe 100644 --- a/agent/modals/asset/asset.php +++ b/agent/modals/asset/asset.php @@ -219,7 +219,9 @@ $file_count = mysqli_num_rows($sql_related_files); // Related Software Query $sql_related_software = mysqli_query( $mysqli, - "SELECT * FROM software_assets + "SELECT software_expire, software_assets.software_id, software_key, software_license_type, + software_name, software_notes, software_purchase, software_seats, software_type, + software_version FROM software_assets LEFT JOIN software ON software_assets.software_id = software.software_id WHERE software_assets.asset_id = $asset_id AND software_archived_at IS NULL diff --git a/agent/modals/contact/contact.php b/agent/modals/contact/contact.php index e495ac928..c0027ac40 100644 --- a/agent/modals/contact/contact.php +++ b/agent/modals/contact/contact.php @@ -158,7 +158,7 @@ while ($row = mysqli_fetch_assoc($sql_note_type_icons)) { } // Linked Services -$sql_linked_services = mysqli_query($mysqli, "SELECT * FROM service_contacts, services +$sql_linked_services = mysqli_query($mysqli, "SELECT 1 FROM service_contacts, services WHERE service_contacts.contact_id = $contact_id AND service_contacts.service_id = services.service_id ORDER BY service_name ASC" diff --git a/agent/modals/payment/payment_add.php b/agent/modals/payment/payment_add.php index 9845d9456..42862cf43 100644 --- a/agent/modals/payment/payment_add.php +++ b/agent/modals/payment/payment_add.php @@ -93,7 +93,7 @@ ob_start(); - Select an Account - - Select an Account - - Account - - Select Account - '$revenue_created_at' OR account_archived_at IS NULL) ORDER BY account_archived_at ASC, account_name ASC"); + $sql_accounts = mysqli_query($mysqli, "SELECT account_archived_at, account_currency_code, account_id, account_name, opening_balance FROM accounts WHERE (account_archived_at > '$revenue_created_at' OR account_archived_at IS NULL) ORDER BY account_archived_at ASC, account_name ASC"); while ($row = mysqli_fetch_assoc($sql_accounts)) { $account_id_select = intval($row['account_id']); $account_name_select = escapeHtml($row['account_name']); diff --git a/agent/modals/transfer/transfer_add.php b/agent/modals/transfer/transfer_add.php index 1b0a84ff8..10217e7dc 100644 --- a/agent/modals/transfer/transfer_add.php +++ b/agent/modals/transfer/transfer_add.php @@ -50,7 +50,7 @@ ob_start(); - Account To - '$transfer_created_at' OR account_archived_at IS NULL) ORDER BY account_archived_at ASC, account_name ASC"); + $sql_accounts = mysqli_query($mysqli, "SELECT account_archived_at, account_id, account_name, opening_balance FROM accounts WHERE (account_archived_at > '$transfer_created_at' OR account_archived_at IS NULL) ORDER BY account_archived_at ASC, account_name ASC"); while ($row = mysqli_fetch_assoc($sql_accounts)) { $account_id_select = intval($row['account_id']); $account_name_select = escapeHtml($row['account_name']); @@ -119,7 +119,7 @@ ob_start();