Writing multiple SQL queries to one CSV in PHP - php

I have a php script successfully running right now that writes results of a sql query into a CSV file, eventually mailing as an attachment. So far so good, but I need to add another similar query that just does some summing basically, however, I want it to run right after the first one and have them both Written into the CSV.
Hopefully/ideally, write the results of query 1, skip a few blank rows, and write the results of query 2. It would be nice If I could put a heading for the 2nd query that said "CSR Totals" But I'm not banking on that.
Anyway, I'm not sure how to go about that exactly. I would imagine using mysqli_multiple_queries? I don't want to refactor too much of what I have due to it working well, but I realize I'll have to change a few things. This just isn't my area of expertise so I'm hoping for some guided suggestions.
Here's the code: (notice I've concatenated the 2nd query but just to show my concept, I'm not sure if the syntax would be right.)
$result = mysqli_query($conn2,
"SELECT
firstn
, lastn
, extension
, Recieved
, RecievedKnown
, Outbound
, outboundKnown
FROM (
SELECT
firstn
, lastn
, c.extension
,CASE WHEN LEGTYPE1 = 2 AND ANSWERED = 1 THEN 'x' ELSE '' END AS Recieved
, case when LEGTYPE1 = 2 and answered = 1 and CALLINGPARTYNO = k.phone_number then 'x' ELSE '' end as RecievedKnown
, CASE WHEN ANSWERED = 1 AND LEGTYPE1 = 1 then 'x' ELSE '' end AS Outbound
, case when LEGTYPE1 = 1 and FINALLYCALLEDPARTYNO = k.phone_number then 'x' ELSE '' end as outboundKnown
FROM test.session a
INNER JOIN test.callsummary b ON a.NOTABLECALLID = b.NOTABLECALLID
right join jackson_id.users c on a.callingpartyno = c.extension or a.finallycalledpartyno = c.extension
LEFT JOIN test.known_numbers k ON a.callingpartyno = k.phone_number
WHERE a.ts >= curdate()
and(a.CALLINGPARTYNO in (select extension from test.test_users) OR a.finallycalledpartyno IN (select extension from test.test_users))
) x
ORDER BY extension;") or die(mysqli_error( $conn2));
$result .= mysqli_query($conn2,
"//2nd query in here") or die(mysqli_error( $conn2));
if (!$result) die('Couldn\'t fetch records');
$num_fields = mysqli_num_fields($result);
$headers = array();
while ($fieldinfo = mysqli_fetch_field($result)) {
$headers[] = $fieldinfo->name;
}
$fp = fopen('test.csv', 'w');
if ($fp && $result) {
fputcsv($fp, $headers);
while ($row = $result->fetch_array(MYSQLI_NUM)) {
fputcsv($fp, array_values($row));
}
fclose($fp);
}
$file = "test.csv";

Related

PHP, sql query to CSV, format rows for totals

I have a working script that, when executed, runs the following SQL query which is then written to a CSV. All of this works perfectly.
However, I'd like to adjust the formatting somewhat. It currently writes results by employee and orders by last night. I'd like to have a row at the end of all agent's records that said something like "Agent's Name: Totals" and then sums up the totals of their records.
For example, each row has their name, phone extension, and then several metrics which are either blank or have an 'x', and lastly a number representing time on the phone. So I'd like to total the x's in the appropriate fields, add the duration of time on the phone, and lastly a count of total calls (which would be a count of that employee's records).
I don't know if this should be done in the SQL, in the CSV code block or if there's a better way to store the metrics with PHP and do this programmatically.
I'm a rookie here, normally just relying on queries in MySQL workbench, so any help is much appreciated.
Here's the script:
$result = mysqli_query($conn2,
"SELECT
firstn
, lastn
, extension
, Recieved
, RecievedKnown
, Outbound
, outboundKnown
, Missed
, MissedKnown
, CallingNumber
, CalledNumber
, starttime
, endtime
, duration
, HOLDTIMESECS
, TERMINATIONREASONCODE
FROM (
SELECT
u.firstn
, u.lastn
, c.extension
, CASE WHEN LEGTYPE1 = 2 AND ANSWERED = 1 THEN 'x' ELSE '' END AS Recieved
, CASE WHEN LEGTYPE1 = 2 AND answered = 1 AND CALLINGPARTYNO = k.phone_number THEN 'x' ELSE '' END AS RecievedKnown
, CASE WHEN ANSWERED = 1 AND LEGTYPE1 = 1 THEN 'x' ELSE '' END AS Outbound
, CASE WHEN LEGTYPE1 = 1 AND FINALLYCALLEDPARTYNO = k.phone_number THEN 'x' ELSE '' END AS outboundKnown
, CASE WHEN Answered = 0 THEN 'x' ELSE '' END AS Missed
, CASE WHEN ANSWERED = 0 AND CALLINGPARTYNO = k.phone_number THEN 'x' ELSE '' END AS MissedKnown
, a.CALLINGPARTYNO AS CallingNumber
, a.FINALLYCALLEDPARTYNO AS CalledNumber
, b.starttime AS starttime
, b.endtime AS endtime
, b.duration
, a.holdtimesecs
, a.terminationreasoncode
FROM ambition.session a
INNER JOIN ambition.callsummary b ON a.NOTABLECALLID = b.NOTABLECALLID
INNER JOIN ambition.mxuser c ON a.RESPONSIBLEUSEREXTENSIONID = c.EXTENSIONID
INNER JOIN jackson_id.users u ON c.extension = u.extension
LEFT JOIN ambition.known_numbers k ON a.callingpartyno = k.phone_number
WHERE date(b.ts) >= curdate()
AND LEGTYPE1 <> 12 -- This keeps the report from having blank spaces due to the 12 legtype.
AND c.extension IN (7276,7314,7295,7306,7357,7200,7218,7247,7331,7255,7330,7000,7215,7240,7358,7312)
) x
ORDER BY lastn") or die(mysqli_error( $conn2));
$userDetails = array();
while($row = $result->fetch_array(MYSQLI_NUM)){
/* Let me take the extension as the key to store its respective counts */
$extension = $row['extension'];
/* Now in your while loop do all the calculations for the user */
if(!isset($userDetails[$extension])){
/* Since this is the first time for the extension your doin the calculations */
$userDetails[$extension]['missedCallCounts'] = 1; /* First time count */
}else{
$userDetails[$extension]['missedCallCounts'] += 1; /* Sum up the count */
}
}
foreach($userDetails as $userDetail){
/* In the following line dump the respective userdetails to csv which will show summary */
fputcsv($fp, array_values($userDetails));
}
And a screenshot of the current CSV results in excel (omitted names/numbers, but you still see the idea):
So for extension 7312, under the first two rows would be a new row with something like:
7312's Totals | 0 | 0 | 0 | 0 | 2 | 2 | - | - | - | - | 76 | 0
Your looping into the sql query to output the same to CSV file, at that time keep the records in generic array and dump once the loop gets over.
For Eg:
$userDetails = array();
while($row = $result->fetch_array(MYSQLI_NUM)){
/* Let me take the extension as the key to store its respective counts */
$extension = $row['extension'];
/* Now in your while loop do all the calculations for the user */
if(!isset($userDetails[$extension])){
/* Since this is the first time for the extension your doin the calculations */
$userDetails[$extension]['missedCallCounts'] = 1; /* First time count */
}else{
$userDetails[$extension]['missedCallCounts'] += 1; /* Sum up the count */
}
}
Now loop into the $userDetails and dump the same to CSV
foreach($userDetails as $userDetail){
/* In the following line dump the respective userdetails to csv which will show summary */
fputcsv($fp, array_values($userDetails));
}

PDO Memory Exhausted

This is common issue but I have no choice to code it like this just to get appropriate header and body in Excel file
here how it starts
When a request been made to print, I first began make a query to fetch the headers in the database
SELECT instruments.in_id, instrument_parameters.ip_id,
CASE WHEN gv_x_ipid = -1 THEN 'datetime' ELSE '' END xlabel,
CASE WHEN ip_label LIKE '%Reservoir%' THEN 0 ELSE in_order END legendIndex,
CASE WHEN in_name = 'General' THEN ip_label ELSE in_name END ylabel
FROM graph_plot
LEFT JOIN attributes gptype ON gp_type = gptype.at_id
LEFT JOIN graph_value ON gp_id = gv_gpid
LEFT JOIN instrument_parameters ON gv_y_ipid = ip_id
LEFT JOIN attributes pmunit ON ip_unit = pmunit.at_id
LEFT JOIN instrument_reading yvalue ON gv_y_ipid = iv_ipid
LEFT JOIN instruments ON iv_inid = in_id
WHERE gp_diid = :di_id AND
gp_type = :rpt_type AND
iv_status = 'Y' AND
iv_inid in (".implode(",", $coll->inid).") AND
gv_y_ipid in (".implode(",", $coll->ipid).")
GROUP BY ylabel
ORDER BY legendIndex
and this will produce numbers of headers that I will make it to be like this
DATE | Instrument1 | Instrument2 | Instrument3
The Instrument? will be dynamic based on the query above. I store this in new variable. But the original variable that holds the database results remain intact.
Later, using the same parameters, :di_id and :rpt_type, also another additional parameters, startDt and endDt to make another query just to return a long list of available dates in database. This is based on the startDt and endDt.
$sql2 = "SELECT iv_reading FROM instrument_reading WHERE iv_inid = :inid AND iv_ipid = :ipid AND iv_date = :dt AND iv_status = 'Y'";
When it finish getting the dates, I make two loop like this
foreach ($dates as $key => $dt) {
foreach ($resp as $InstNo => $InstRow) {
try {
$stmt2->execute(array(':dt' => $dt, ':inid' => $InstRow->in_id, ':ipid' => $InstRow->ip_id));
$rowDb = $stmt2->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT);
} catch(PDOException $e) {
echo '{"error":{"text":"'. $e->getMessage() .'"}}';
}
}
}
First, it starts looping the date and second it begins looping the headers (based on the query made right before getting the dates). My problem I always stuck here
$stmt2->execute(array(':dt' => $dt, ':inid' => $InstRow->in_id, ':ipid' => $InstRow->ip_id));
What do you think? Is there any better way to handle this?
For your information, I use Slim and PHPExcel. PHPExcel might have memory issue and I'm thinking to switch to Spout but the documents still about the basic stuff.
In your SQL, you may consider a limit clause to ease the memory load as follows:
$handle = fopen("file.csv", "wb");
$statement = "
SELECT instruments.in_id, instrument_parameters.ip_id,
CASE WHEN gv_x_ipid = -1 THEN 'datetime' ELSE '' END xlabel,
CASE WHEN ip_label LIKE '%Reservoir%' THEN 0 ELSE in_order END legendIndex,
CASE WHEN in_name = 'General' THEN ip_label ELSE in_name END ylabel
FROM graph_plot
LEFT JOIN attributes gptype ON gp_type = gptype.at_id
LEFT JOIN graph_value ON gp_id = gv_gpid
LEFT JOIN instrument_parameters ON gv_y_ipid = ip_id
LEFT JOIN attributes pmunit ON ip_unit = pmunit.at_id
LEFT JOIN instrument_reading yvalue ON gv_y_ipid = iv_ipid
LEFT JOIN instruments ON iv_inid = in_id
WHERE gp_diid = :di_id
AND gp_type = :rpt_type
AND iv_status = 'Y'
AND iv_inid in (".implode(",", $coll->inid).")
AND gv_y_ipid in (".implode(",", $coll->ipid).")
GROUP BY ylabel
ORDER BY legendIndex
LIMIT 250
";
$prep = $dbh->prepare($statement);
for ($i = 0; $prep -> rowCount < 250; $i+= 250) {
fputcsv(prep->fetchAll());
$prep = $dbh->prepare($statement.' OFFSET'.$i);
}
fclose($handle);
Alternatively, you could use system and call SELECT INTO, set the permissions (if necessary) and Bob's your uncle.
You have not terminated the fetch loop.
$rowDb = $stmt2->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT);
gets the "next" row or closes the 'cursor' and terminates.
Are you expecting to get exactly one row? If so, consider doing fetchAll. (Caution: the resultset may be an extra level deep in arrays.)
The PDO MySQL driver will do some buffering, which causes memory exhaustion when looping over large datasets. You can turn this off using $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); which should solve the problem.
$pdo = new PDO('mysql:localhost', $username, $password);
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$stmt = $pdo->prepare('SELECT * FROM instrument...');
$stmt->execute($parameters);
while($row = $stmt->fetch()) {
// Insert logic to write the row to the destination
}
If you'd rather set the attribute for that query only, you can do that as well:
$stmt = $pdo->prepare('SELECT * FROM instrument...', [
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY
]);
Keep in mind that you won't be able to run other queries until you are done with your unbuffered one. You can close the old cursor prematurely with $stmt->closeCursor() if you don't need the remaining results. I also cannot speak to the performance of this, but it solved my issue while writing a one-off script.
The setting is mentioned briefly in MySQL's documentation:
https://dev.mysql.com/doc/connectors/en/apis-php-pdo-mysql.html

How does one go about deleting records from an array with Joins

I have a query with multiple joins in it. After I take the results and run it through a Id-checker I want to be able to delete records from that array where the IDDestination equals $ID.
Since this query has joins on it and I am filtering them based on one of the joined tables, How do I go about deleting those records from the array based off that joined table?
And I only wanted this to happen after the user confirms.
$query = "
select d.IDCourse,
d.name as course_name,
d.slug,
d.short_description,
d.address,
e.city_name,
e.state_code,
d.zip,
e.city_slug,
e.state_slug,
h.IDDestination,
LOWER(e.iso_code) as country_slug, a.*,
b.IDTeetimeType,
c.name as teetime_type,
b.start_time,b.end_time,
(case dayofweek(a.teetime_dt)
when 1 then `b`.`sun`
when 2 then `b`.`mon`
when 3 then `b`.`tue`
when 4 then `b`.`wed`
when 5 then `b`.`thu`
when 6 then `b`.`fri`
when 7 then `b`.`sat`
end) AS `price`, g.tax_rate, f.alias
from cart_course_teetimes a
join course_priceplan b
on a.IDCoursePricePlan = b.IDCoursePricePlan
join course_teetime_type c
on b.IDTeetimeType = c.IDTeetimeType
join course d
on b.IDCourse = d.IDCourse
join vw_cities e
on d.IDCity = e.IDCity
join destinations_cities h
on h.IDCity= d.IDCity
LEFT JOIN (SELECT * FROM media_mapping WHERE is_main_item=1 AND IDGalleryType=3) f
ON d.IDGallery = f.IDGallery
left join course_tax
g on a.IDCourseTax = g.IDCourseTax
where a.IDCart = :cart_id
order by d.name, a.teetime_dt, b.start_time;";
$prepared = array(
"cart_id" => $idCart,
);
$conn = new DBConnection();
$results = $conn->fetch($query, $prepared);
$conn = null;
$results = !empty($results) ? $results : array();
$id = null;
foreach($results as $row) {
// Set ID for the first record.
if($id === null)
$id = $row['IDDestination'];
// will stay true, otherwise it's false and we should kill the loop.
if($id != $row['IDDestination']) {
$newid=$row['IDDestination'];
echo "<script type='text/javascript'> emptycart();</script>";
$query = "DELETE FROM cart_course_teetimes a WHERE h.IDDestination='.$id.'";
$res =mysql_query($query) or die (mysql_error());
break;
}
}
This is incorrect PHP:
$query = "DELETE FROM cart_course_teetimes a WHERE h.IDDestination='.$id.'"
You're already inside a "-quoted string, so . PHP concatenation operators aren't operators, they're just a period.
You want either of these instead:
$query = "DELETE FROM cart_course_teetimes a WHERE h.IDDestination='" . $id . "'";
$query = "DELETE FROM cart_course_teetimes a WHERE h.IDDestination='$id'"
Right now you're producing
... WHERE h.IDDestination = .42.
which is not valid SQL.
Plus it appears you're mixing database libraries. You've got $conn->fetch() which implies you're using one of the OO db libraries (mysqli? pdo? custom wrapper?). But you then call mysql_query(). Unless you've EXPLICITLY called mysql_connect(), that delete query will never execute. Database connections made in one of the libraries are utterly useless in any of the other libraries.

Moving from mysql to mysqli - problems

I have the following code:
$query3 = "SELECT
Office,
COUNT(Office) As Tot_Part,
(SELECT COUNT(Office) FROM trespondent WHERE completion_status= 'Started' OR completion_status = 'Complete') As Total_Resp
FROM trespondent
WHERE completion_status <> 'New'
GROUP BY Office
ORDER BY Office";
$result3 = $mysqli->query($query3);
I am trying to move from mysql to mysqli and am struggling terribly. The above statement brings back only 1 row and not the 26 that there should be. Any pointers welcome.
That's because you're fetching just one row ($los = $result3->fetch_row();).
Try it in a loop.
Like this:
$result3 = $mysqli->query($query3);
while($los = $result3->fetch_row()) {
print_r($los); /* sanity */
}

Does mysql_fetch_array reset the query var?

For example I have a mysql query that gets some data. Then runs another query based some of the data that it got.
If i just return the first query, in my case $qOne. Everything works great.
BUT, after using my while loop while ($row = mysql_fetch_array($qOne)) It then returns as an empty array. (but the second query I return DOES work)
I tried to see if I could "save" the first query in another var im not messing with like this $savedResult = $qOne, then i'd just return the $savedResult but that did not work.
Does anyone know how I can get my function below to return both of the results? Thanks!
function getFoods($sort, $start, $limit) {
$qOne = mysql_query("SELECT a.id, a.name, a.type, AVG(b.r) AS fra, COUNT(b.id) as tvotes FROM `foods` a LEFT JOIN `foods_ratings` b ON a.id = b.id GROUP BY a.id ORDER BY fra DESC, tvotes DESC LIMIT $start, $limit;");
$i = 0;
$qry = "";
while ($row = mysql_fetch_array($qOne)) {
$fid = $row['id'];
if ($i > 0)
$qry .= " UNION ";
$i++;
$qry .= "SELECT fid, ing, amount FROM foods_ing WHERE fid='$fid'";
}
$qTwo = mysql_query($qry);
return array($qOne, $qTwo);
}
When you have returned the two query result resources, remember that you will need to fetch from them when you actually want to use them. (we don't see the code where you implement that).
To make use of $qOne after you already have looped through it, you must rewind it back to the first position. That is done with mysql_data_seek()
mysql_data_seek($qOne, 0);

Categories