How to write nested query MySQL PHP - php

I'm developing a page to edit board meetings and I want to display all board members who did not attend specific meeting as a checkox located below who attend as an edit in case of user want to add more so I did this:
My code:
$q = "SELECT * FROM `boardteam`";
$r = mysql_query($q);
while ($dbfield = mysql_fetch_assoc($r))
{
$member_id =$dbfield['nationalID'];
$query = "SELECT `attendance` FROM `meetingattendance` WHERE `meetingID` = '$mid' AND `attendance`!= '$member_id'";
$res = mysql_query($query);
if ($res)
{
$tname ="";
switch ($dbfield['titleName'])
{
case "Dr":
$tname .= "د.";
break;
case "Ms":
$tname .= "السيدة.";
break;
case "Mr":
$tname .= "السيد.";
break;
}
$At .= "<input type='checkbox' name='moreAttendence[]' dir='rtl' value=".$dbfield['nationalID']."><div class='styled-checkbox'>".$tname." ".$dbfield['fName']." ".$dbfield['sName']." ".$dbfield['lName']."</div><br>";
}
}
DB:
CREATE TABLE `boardteam` (
`nationalID` int(10) NOT NULL,
`titleName` char(2) NOT NULL,
`fName` char(20) NOT NULL,
`sName` char(20) NOT NULL,
`lName` char(20) NOT NULL,
`gender` char(1) NOT NULL,
`birthDate` date DEFAULT NULL,
`materialStatus` char(15) DEFAULT NULL,
`jobTitle` varchar(100) NOT NULL,
`jobLocation` varchar(20) DEFAULT NULL,
`employer` varchar(100) DEFAULT NULL,
`email` varchar(100) NOT NULL,
`photo` varchar(255) DEFAULT NULL,
`academicGrade` char(15) DEFAULT NULL,
`employmentStartDate` date NOT NULL,
`employmentEndDate` date NOT NULL,
`employmentType` char(20) DEFAULT NULL,
`employmentStatus` char(15) DEFAULT NULL,
`jobStartDate` date DEFAULT NULL,
`jobNumber` int(10) DEFAULT NULL,
`cv` varchar(255) DEFAULT NULL,
PRIMARY KEY (`nationalID`)
)
CREATE TABLE `meetingattendance` (
`meetingID` int(11) NOT NULL,
`attendance` int(10) DEFAULT NULL,
`absence` int(10) DEFAULT NULL,
`reason` varchar(255) DEFAULT NULL,
`additionalAttendance` varchar(255) DEFAULT NULL,
KEY `absence` (`absence`),
KEY `meeingID` (`meetingID`),
KEY `attendance` (`attendance`),
CONSTRAINT `meetingattendane_ibfk_1` FOREIGN KEY (`meetingID`) REFERENCES `boardmeetings` (`meetingID`),
CONSTRAINT `meetingattendane_ibfk_2` FOREIGN KEY (`attendance`) REFERENCES `boardteam` (`nationalID`),
CONSTRAINT `meetingattendane_ibfk_3` FOREIGN KEY (`absence`) REFERENCES `boardteam` (`nationalID`)
)
With my code I got all board members including who attend, How to fix that ??

You need to use a LEFT JOIN in order to find people in the boardTeam who were not in a specific meeting. eg:
SELECT b.*, m.attendance
FROM boardTeam b
LEFT JOIN meetingattendance m
ON b.nationalID = m.attendance AND m.meetingID = $mid
WHERE m.meetingID IS NULL
If you want to get ALL board members, and then determine within PHP if they attended the meeting or not, simply remove the m.attendance IS NULL clause, as such:
SELECT b.*, m.attendance as attendance
FROM boardTeam b
LEFT JOIN meetingattendance m
ON b.nationalID = m.attendance AND m.meetingID = $mid
and now when you loop through the response rows in php, you can test as such (assuming you fetch your rows one by one into a $row variable):
if($row['attendance'] != null)
{
// attended meeting
}
else
{
// did not attend meeting
}
Also, as mentioned in the comments, use mysqli, or pdo instead of pure mysql_ functions
Example fiddle here: http://sqlfiddle.com/#!9/ba7d4/6

Related

Reduce MySQL request time with Codeigniter

I use Codeigniter 3.1.11 and have some code which makes one big query to MySQL with an array. The time of a query with a limit of 30000 is about 9 seconds.
How can I reduce the request time? Maybe by using some indexes on my table, or do you know another method? If I need to use indexes, what indexes would I need to use and how can I use these indexes in my query on Codeigniter?
Code from model:
function rows_update() {
$query = $this->db->order_by('rating', 'DESC')->get_where('members', 'game_rating_and_balance_update_last_time <= now() - INTERVAL 1 DAY', '30000', '0');
$arrUpdateBatchData = [];
while ($row = $query->unbuffered_row('array'))
{
// some code here
$arrUpdateData = [
'UserID' => $row['UserID'],
'game_vault_balance' => $new_game_vault_balance,
'game_available_balance' => $new_game_available_balance,
'rating' => $rating_member,
'game_rating_and_balance_update_last_time' => date('Y-m-d H:i:s')
];
$arrUpdateBatchData[] = $arrUpdateData;
if (count($arrUpdateBatchData) > 500)
{
$this->db->update_batch('members', $arrUpdateBatchData, 'UserID');
$arrUpdateBatchData = [];
}
}
//update last items
if (count($arrUpdateBatchData) > 0)
{
$this->db->update_batch('members', $arrUpdateBatchData, 'UserID');
$arrUpdateBatchData = [];
}
return;
}
Raw query to MySQL with update_batch (I simply write only one row from an array):
UPDATE members SET game_vault_balance = CASE WHEN UserID = '9915075' THEN 803.60516004772 ELSE game_vault_balance END, game_available_balance = CASE WHEN UserID = '9915075' THEN 4.1253850908788 ELSE game_available_balance END, rating = CASE WHEN UserID = '9915075' THEN 0.24 ELSE rating END, game_rating_and_balance_update_last_time = CASE WHEN UserID = '9915075' THEN '2020-07-24 22:00:36' ELSE game_rating_and_balance_update_last_time END WHERE UserID IN('9915075')
Table structure:
CREATE TABLE `members` (
`id` int(10) UNSIGNED NOT NULL,
`UserID` varchar(64) NOT NULL,
`telegram_id` varchar(64) DEFAULT NULL,
`first_name` varchar(64) DEFAULT NULL,
`last_name` varchar(64) DEFAULT NULL,
`language` varchar(64) DEFAULT NULL,
`currency` varchar(64) DEFAULT NULL,
`status` varchar(64) DEFAULT NULL,
`rating` varchar(64) DEFAULT NULL,
`game_vault_balance` decimal(32,8) DEFAULT 0.00000000,
`game_available_balance` decimal(32,8) DEFAULT 0.00000000,
`game_rating_and_balance_update_last_time` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
`created` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Indexes of this table:
ALTER TABLE `members`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `UserID` (`UserID`) USING BTREE;
AUTO_INCREMENT for the members table:
ALTER TABLE `members`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;

Creating an invoice from SQL query without putting query in a loop

My business has a simple invoicing system written by me a few years ago in php with a MySQL database. When I wrote it, I knew just enough to be dangerous. While it does work, a lot of the code is very inefficient, slow and poorly conceived, so I'm re-writing some of it in the hopes of fixing it.
There are two tables: customers and transactions. When invoices need to be created, there are two queries, the second unfortunately in a while loop. Here is some simplified pseudo-code to show what I'm doing:
// Get list of customers who owe something
SELECT name, address
FROM customers
WHERE
(SELECT COUNT(*) FROM transactions
WHERE transactions.customer_id = customers.id
AND owed > 0)
> 0
ORDER BY address
//loop through that result and query the transactions table to get a
//list of charges for each customer. Like:
while ($row = customer_array()) {
echo name_and_address;
SELECT * FROM transactions WHERE id = $row['customer_id']
AND owed = TRUE ORDER BY date
while ($row = transactions_array()) {
echo each_transaction_row;
}
So obviously the nested loops, subqueries and queries in loops are bad, worse and slow. I've tried joins but can't seem to figure out how to make them work correctly in this context. What is the correct way to do something like this? Can it be done with a single query?
Table structure:
CREATE TABLE `customer_data` (
`account_id` int(6) NOT NULL AUTO_INCREMENT,
`service_address` varchar(255) DEFAULT NULL,
`service_zipcode` int(5) DEFAULT NULL,
`first_name` varchar(255) DEFAULT NULL,
`last_name` varchar(255) DEFAULT NULL,
`billing_address1` varchar(255) DEFAULT NULL,
`billing_address2` varchar(255) DEFAULT NULL,
`billing_city` varchar(255) DEFAULT NULL,
`billing_state` varchar(2) DEFAULT NULL,
`billing_zipcode` varchar(20) DEFAULT NULL,
`phone1` varchar(100) DEFAULT NULL,
`phone2` varchar(100) DEFAULT NULL,
`quoted_price` int(6) DEFAULT NULL,
`current_price` int(6) DEFAULT NULL,
`original_interval` varchar(10) DEFAULT NULL,
`current_interval` varchar(7) DEFAULT NULL,
`temp_interval` int(5) NOT NULL,
`email` varchar(100) DEFAULT NULL,
`origin_date` varchar(20) DEFAULT NULL,
`remarks` text,
`crew_remarks` text NOT NULL,
`customer_type` varchar(10) DEFAULT NULL,
`perm_crew_assign` int(5) NOT NULL DEFAULT '0',
`temp_crew_assign` int(5) NOT NULL,
`date_last_service` date DEFAULT NULL,
`next_scheduled_date` date DEFAULT NULL,
`excluded_days` varchar(255) NOT NULL,
`special_instructions` text NOT NULL,
`inactive` tinyint(1) NOT NULL DEFAULT '0',
`location` varchar(255) NOT NULL,
`sent_letter` date NOT NULL,
`date_added` datetime NOT NULL,
`email_notify` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1687 DEFAULT CHARSET=utf8
CREATE TABLE `transactions` (
`transaction_id` int(10) NOT NULL AUTO_INCREMENT,
`account_id` int(6) NOT NULL,
`crew_id` int(3) NOT NULL,
`date_performed` date NOT NULL,
`date_recorded` datetime NOT NULL,
`price` int(6) NOT NULL,
`amount_paid` int(6) NOT NULL,
`description` varchar(255) NOT NULL,
`user` varchar(255) NOT NULL,
`status` int(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`transaction_id`)
) ENGINE=InnoDB AUTO_INCREMENT=69233 DEFAULT CHARSET=latin1
You can do this with one query and one loop. You can join the two tables and sort the result. Then when the name changes, you know you are starting a new customer.
SELECT c.name, c.address, t.*
FROM customers AS c
JOIN transactions AS t ON (t.customer_id = c.customers.id)
WHERE c.owed > 0
ORDER BY `c`.`name`
// Data looks like this
// | customer.name | customer.address | transaction.* |
$prevName = null;
$prevCustTransList = [];
$prevCustTransTotalOwed = 0;
while ($row = result_array()) { // pseudo code
if ( $row['c.name'] != $prevName ) {
if ( $prevName != null && $prevCustTransTotalOwed > 0 ) {
echo $prevName; // pseudo code
foreach($prevCustTransList as $t) {
echo $t; // pseudo code
}
}
$prevName = $row['c.name'];
$prevCustTransTotalOwed = 0;
$prevCustTransList = [];
}
// Keep the list of transactions to print out later.
$prevCustTransList[] = transaction data; // pseudo code
// Keep a running total of the amount owed
// Replace with actual calculation
$prevCustTransTotalOwed += transaction.price;
}
Yes you can do this in one query, ordering by account_id. In your 'transactions' table account_id should be indexed, since you'll be using it to join to 'customer_data'.
$st_date = '2016-01-01';
$end_date = '2016-02-01';
//select sql
SELECT c.name,c.account_id, ts.*
FROM customer_data c
INNER JOIN transactions ts ON (c.account_id = ts.account_id)
WHERE ts.price > 0 and ts.date_performed between $st_date and $end_data
$current_account_id = null;
while ($row = $results) {
if ($current_account_id != $row['account_id']) {
//print customer name, details, etc..
}
//print the transactions detail
echo $results['transaction_id']; //etc...
//keep track of the current customer
$current_account_id = $row['account_id']
}

How to streamline this (rather large) MySQL query in PHP

I thought I was pretty good at this sort of thing but...
I have the following query from a customer table that is about 8,500 rows and an address table that is about the same number of rows.
This query takes about 10 seconds to complete and I can't figure how to get it to milliseconds.
What am I doing wrong?
<select>
<option value=""></option>
<?php // get the customers
$sql = "
SELECT `cust`.`custid`, `cust`.`custname`, `cust`.`custactive`, `address`.`addtype`, `address`.`address1`, `address`.`addcity`, `address`.`addstate`
FROM `cust`
LEFT Join `address`
ON `cust`.`custid` = `address`.`addcustid`
and `address`.`addtype` = 'b'
WHERE `cust`.`custactive` = 'y'"
;
$result = mysqli_query($con,$sql) or die('Query failed: Could not get list of CLIENTS: ' . mysqli_error($con)); // query
while ($row = mysqli_fetch_array($result)) {
$custid = $row['custid'];
$space="";
$endingspaces = 4-(2*strlen($prodid));
for($count=1; $count<$endingspaces; $count++){
$space .=" ";
}
$custid = $row['custid'];
$custname = substr($row['custname'],0,15);
$address1 = substr($row['address1'],0,15);
$addcity = substr($row['addcity'],0,15);
$addstate = $row['addstate'];
print "<option value=\"$custid\">$custid: $space$custname, $address1, $addcity, $addstate</option>";
}
?>
</select>
DDL:
CREATE TABLE `cust` (
`custid` int(11) DEFAULT NULL,
`custname` varchar(45) DEFAULT NULL,
`custactive` varchar(255) DEFAULT NULL,
`custcreated` varchar(255) DEFAULT NULL,
`custmodified` varchar(255) DEFAULT NULL,
`cust_dataease_custid` int(11) DEFAULT NULL,
`custtype` varchar(5) DEFAULT NULL,
`custrep` int(11) DEFAULT NULL,
`custsource` varchar(5) DEFAULT NULL,
`custdiscount` decimal(65,30) DEFAULT NULL,
`custrepcomm` varchar(255) DEFAULT NULL,
`custsurcharge` varchar(255) DEFAULT NULL,
`custterms` varchar(12) DEFAULT NULL,
`custups` varchar(255) DEFAULT NULL,
`custnotes` varchar(255) DEFAULT NULL,
`custbilldif` varchar(5) DEFAULT NULL,
UNIQUE KEY `custid` (`custid`),
KEY `cust_dataease_custid_idx` (`cust_dataease_custid`),
KEY `custrep_idx` (`custrep`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `address` (
`addid` int(11) unsigned NOT NULL AUTO_INCREMENT,
`addcustid` int(11) unsigned NOT NULL,
`addname` varchar(200) DEFAULT NULL,
`addtype` enum('b','s') NOT NULL DEFAULT 'b',
`address1` varchar(150) NOT NULL,
`address2` varchar(150) DEFAULT NULL,
`addcity` varchar(150) DEFAULT NULL,
`addstate` varchar(150) DEFAULT NULL,
`addstateother` varchar(150) DEFAULT NULL,
`addzip` varchar(150) DEFAULT NULL,
`addcountry` varchar(150) DEFAULT NULL,
`addnote` tinyblob,
PRIMARY KEY (`addid`),
KEY `add_cust_id_idx` (`addcustid`)
) ENGINE=InnoDB AUTO_INCREMENT=13037 DEFAULT CHARSET=utf8
John Green got me to thinking about the way information was being sent to and from the server, which led me to find the problem. Thanks John.
This is how I fixed it: Apparently, when my loop was iterating, it was "printing" each of the 8500 OPTION lines, which action (i didn't know), is a server-client interaction. So for each row, the server and browser had to have a conversation, which slowed everything down.
I fixed it by having the server compile the entire SELECT box on the server side, including the opening SELECT tags and interacting with the browser once through echoing my "add-to" variable: $select_box.
Now it takes like 1.2 seconds. I can live with that.
Thanks everyone for helping me chase this rabbit.
<?php // get the products
$select_box = "<select name=\"customer\" id=\"customer\" onChange=\"getcustinfo();\" data-placeholder=\"Choose a Customer\" class=\"chosen-select\" style=\"width:227px;\" tabindex=\"1\">
<option value=\"\"></option>";
$sql = "
SELECT *
FROM `cust`
LEFT Join `address`
ON `cust`.`custid` = `address`.`addcustid`
and `address`.`addtype` = 'b'
WHERE `cust`.`custactive` = 'y'"
;
$result = mysqli_query($con,$sql) or die('Query failed: Could not get list of CLIENTS: ' . mysqli_error($con)); // query
while ($row = mysqli_fetch_array($result)) {
foreach ($row as $key => $value){ ${$key} = $value; }
$space="";
$custname = substr($row['custname'],0,15);
$address1 = substr($row['address1'],0,15);
$addcity = substr($row['addcity'],0,15);
$select_box .= "<option value=\"$custid\">$custid: $space$custname, $address1, $addcity, $addstate</option>";
}
$select_box .="</select>";
echo $select_box;
?>

MySQL delete troubleshooting

I restarted the MySQL service and I attempted to use my PHP programs delete function to delete an existing row but I'm finding although the delete queries were counted the row was not deleted. I tried applying on delete cascade to the foreign key of the child table but that did not seem to have an effect. I'm wondering why the delete would be doing nothing.
CREATE TABLE `customers` (
`idcustomers` int(11) NOT NULL AUTO_INCREMENT,
`firstname` varchar(45) DEFAULT NULL,
`lastname` varchar(45) DEFAULT NULL,
`address1` varchar(45) DEFAULT NULL,
`address2` varchar(45) DEFAULT NULL,
`city` varchar(45) DEFAULT NULL,
`state` varchar(45) DEFAULT NULL,
`zip` varchar(45) DEFAULT NULL,
`phone` varchar(45) DEFAULT NULL,
`email` varchar(45) DEFAULT NULL,
`cell` varchar(45) DEFAULT NULL,
PRIMARY KEY (`idcustomers`),
UNIQUE KEY `idcustomers_UNIQUE` (`idcustomers`)
) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=latin1
CREATE TABLE `events` (
`idevents` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(250) DEFAULT NULL,
`start` datetime DEFAULT NULL,
`end` datetime DEFAULT NULL,
`allday` varchar(50) DEFAULT NULL,
`url` varchar(1000) DEFAULT NULL,
`customerid` int(11) NOT NULL,
`memo` longtext,
`dispatchstatus` varchar(45) DEFAULT NULL,
PRIMARY KEY (`idevents`),
KEY `FK_events` (`customerid`),
CONSTRAINT `FK_events` FOREIGN KEY (`customerid`) REFERENCES `customers` (`idcustomers`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin1
Com_delete 2
The PHP looks like this:
<?php
session_start();
date_default_timezone_set("America/Los_Angeles");
if($_SESSION['loggedin'] != TRUE)
{
header("Location: index.php");
}
require_once('../php.securelogin/include.securelogin.php');
$mysqli = new mysqli($ad_host, $ad_user, $ad_password, "samedaycrm");
if ($mysqli->connect_errno) {
printf("Connect failed: %s\n", $mysqli->connect_error);
exit();
}
$customerid = $_SESSION['customer_id'];
$tSQL = "delete from events where customerid = \"$customerid\"";
$result = $mysqli->query($tSQL);
$tSQL = "delete from customers where idcustomers = \"$customerid\"";
$result = $mysqli->query($tSQL);
echo $mysqli->error;
?>
Assuming that the customerid and idcustomers columns are both numeric it should be fine. You should not need to quote the variables in those queries btw, then you wouldnt need to escape them. You may try:
$tSQL = "delete from events where customerid = $customerid";
but it should not be any different than what you used already. Of course if you are not sure of the type of the column you can use:
$tSQL = "delete from events where customerid = '".$customerid."'";
or you can get away with:
$tSQL = "delete from events where customerid = '$customerid'";
but I have always hated that for some reason.
if all of that fails troubleshoot by spitting out the $customerid (or even the whole $tSQL) variable and then trying the query manually in phpmyadmin or toad or whatever db client you use, and see what it tells you. If it just says 0 rows affected, then run it like a select instead. Tailor to fit.

Multiple prepared statements with MYSQLI

I just moved to mysqli and I was wondering: can I do a multiple query with the prepared statements?
Here is the example: I need to check if this username is also in the table "future_user" and not just in "user" as it is doing right now. For code appeal I'd rather not write again the same function just changing "user" with "future_user".
function isFreeUsername($string)
{
$DB = databaseConnect();
$stmt = $DB->prepare("SELECT * FROM user WHERE username=? LIMIT 1");
$stmt->bind_param("s", $username);
if(isset($_SESSION) && isset($_GET['username'])) $username = $_GET['username'];
else $username = $string;
$stmt->execute();
$stmt->store_result();
if($stmt->num_rows > 0) $return = 0;
else $return = 1;
$stmt->close();
$DB->close();
return $return;
}
TABLES:
CREATE TABLE user
(
uid mediumint(6) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
username varchar(15) NOT NULL,
password varchar(15) BINARY NOT NULL,
mail varchar(50) NOT NULL,
name varchar(50) NOT NULL,
surname varchar(50) NOT NULL,
birth char(10) NOT NULL,
sex tinyint(1) unsigned NOT NULL default 1,
address varchar(50) NOT NULL,
city varchar(50) NOT NULL,
zip char(5) NOT NULL,
province varchar(50) NOT NULL,
country tinyint(3) NOT NULL,
number1 varchar(50) NOT NULL,
number2 varchar(50) NOT NULL,
last_login TIMESTAMP,
registered TIMESTAMP,
online tinyint(1) unsigned default 0,
admin tinyint(1) unsigned default 0,
comment_allowed tinyint(1) unsigned default 0,
post_allowed tinyint(1) unsigned default 0
) ENGINE=InnoDB;
CREATE TABLE future_user
(
username varchar(15) NOT NULL,
password varchar(15) BINARY NOT NULL,
mail varchar(50) NOT NULL,
name varchar(50) NOT NULL,
surname varchar(50) NOT NULL,
birth char(8) NOT NULL,
sex tinyint(1) unsigned NOT NULL,
address varchar(50) NOT NULL,
city varchar(50) NOT NULL,
zip char(10) NOT NULL,
province varchar(50) NOT NULL,
country varchar(50) NOT NULL,
number1 varchar(50) NOT NULL,
number2 varchar(50) NOT NULL,
code char(10) NOT NULL
) ENGINE=InnoDB;
"SELECT *
FROM user u
LEFT JOIN future_user fu on fu.id = u.id
WHERE u.username=?
LIMIT 1"
With out seeing more of your table structure this what i can come up with.
This will select the user in future user too
You could do a CROSS JOIN to connect the two tables and query them that way
SELECT * FROM user JOIN future_user
WHERE user.username = ? OR future_user.username = ?
You'll probably need to tweak that * so that identically named columns in the two tables don't overlay each other.

Categories