Efficient way to query SQL and format to CSV - php

I have a MySQL database that I am keeping temperature readings from several different sensors. I initially thought of using three different tables to store my data:
mysql> select * from sensor_info;
+----+------------------+------+--------+
| id | address | name | active |
+----+------------------+------+--------+
| 1 | 28D684CD02000057 | NULL | 1 |
| 2 | 28099B49030000D8 | NULL | 1 |
| 3 | 28339ACD0200004B | NULL | 1 |
+----+------------------+------+--------+
mysql> select * from data_period limit 4;
+----+---------------------+
| id | ts |
+----+---------------------+
| 1 | 2012-06-30 09:35:02 |
| 2 | 2012-06-30 09:36:22 |
| 3 | 2012-06-30 09:37:46 |
| 4 | 2012-06-30 09:40:36 |
+----+---------------------+
mysql> select * from data_points limit 4;
+----+-------------+-----------+-------+
| id | data_period | sensor_id | data |
+----+-------------+-----------+-------+
| 1 | 1 | 1 | 77.90 |
| 2 | 1 | 2 | 77.34 |
| 3 | 1 | 3 | 77.56 |
| 4 | 2 | 1 | 78.01 |
+----+-------------+-----------+-------+
What I'm trying to do is to take my stored data and put it into a CSV file so I can display it using dygraphs Javascript library. I need to get my data into a format like this:
date,temp1,temp2,temp3
2012-06-30 09:35:02,77.90,77.34,77.56
2012-06-30 09:36:22,78.01,77.36,77.59
....
Every way I start to do this (using PHP), I seem to make this overly complicated and have to put queries inside loops inside loops. Am I making this harder on myself than I need to?
Will most of the work be done using the queries or using PHP? Down the road, I will also want to add code that will place NULL in the CSV if a temperature reading is missing from a particular timestamp.
I'm not looking for a very specific answer, I just want to know what direction I should go. I don't even know how to start to format my data from the database or if I should try looking at a different format to store my info in the database.

I'd run a single select query to join the lot together. You can use an outer join where there might not be data.
SELECT data_period.ts AS date, dp1.data AS temp1, dp2.data AS temp2, dp3.data AS temp3
FROM data_period
LEFT OUTER JOIN data_points AS dp1 ON dp1.data_period=data_period.id AND dp1.sensor_id=1
LEFT OUTER JOIN data_points AS dp2 ON dp2.data_period=data_period.id AND dp2.sensor_id=2
LEFT OUTER JOIN data_points AS dp3 ON dp3.data_period=data_period.id AND dp3.sensor_id=3
(See: SQL Fiddle Example )
That should give you a single set of results that you can just loop through.
If you really want MySQL to do most of the work, you can change the first line to (I think)
SELECT data_period.ts +','+ IFNULL(dp1.data,'NULL') +','+ IFNULL(dp2.data,'NULL') +','+ IFNULL(dp3.data,'NULL')
To Synthesize the comment and the answer and to get this out to a file:
SELECT data_period.ts AS date, dp1.data AS temp1, dp2.data AS temp2, dp3.data AS temp3
INTO OUTFILE '/home/user/output.csv'
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'
ESCAPED BY '\\'
LINES TERMINATED BY '\n'
FROM data_period LEFT OUTER JOIN data_points AS dp1 ON dp1.data_period=data_period.id
AND dp1.sensor_id=1 LEFT OUTER JOIN data_points AS dp2 ON
dp2.data_period=data_period.id AND dp2.sensor_id=2 LEFT OUTER JOIN data_points AS dp3
ON dp3.data_period=data_period.id AND dp3.sensor_id=3;

To do this you can use two SQL statements giving an outer and an inner loop. The first statement
select ts from data_points as point
inner join data_period on data_period = data_period.id
group by ts
order by ts
will get a list of the dates that you have data for.
The second one on the inner loop will get a list of data values ordered by the sensor id - if you have missing values in the data_points then this list will be wrong and have gaps; so if you do have gaps you may need to do an extra step to get the list of sensors and then populate this list from the data coming back from the query.
The following example illustrates how you could proceed with this.
// get the outer list
$sql = "select ts from data_points as point
inner join data_period on data_period=data_period.id
group by ts
order by ts";
$result = mysql_query($sql);
$result || die(mysql_error());
while($row = mysql_fetch_row($result))
{
// get the date for the second query
$date = $row[0];
$sql = "select data from data_points as point
inner join data_period on data_period=data_period.id
inner join sensor_info on sensor_id = sensor_info .id
where ts = '$date'
order by sensor_id";
$result_1 = mysql_query($sql);
// now create an array from the values - we could use mysql_fetch_array to do this
// but this way allows us the possibility of extending the processing to
// take into account missing values.
$vals = array();
while($row_1 = mysql_fetch_row($result_1)) {
$vals[]=$row_1[0];
}
print "$date,".join(",",$vals)."\n";
}

I'd recommend just doing the csv formatting in php after the fact.
Here is a handy function I found online and really love using. And if you wrap your SQL calls in a class then you can just have this method available after you query. :)
Note I modified this to me MSSQL but its easy to replace it with just mysql.
public final function SQL2CSV() {
// set initial .csv file contents
$CSV = array();
$rowid = 0;
//This would be your query.
$res = $this->fetchQuery();
// get column captions and enclose them in doublequotes (") if $print_captions is not set to false
for ($i = 0; $i < mssql_num_fields($res); $i++) {
$fld = mssql_fetch_field($res, $i);
$colnames[] = $fld->name;
}
// insert column captions at the beginning of .csv file
$CSV[$rowid] = implode(",", $colnames);
// iterate through each row
// replace single double-quotes with double double-quotes
// and add values to .csv file contents
if (mssql_num_rows($res) > 0) {
while ($row = mssql_fetch_array($res, MSSQL_NUM)) {
$rowid++;
for ($i = 0; $i < sizeof($row); $i++)
//$row[$i] = '"'.str_replace('"', '""', $row[$i]).'"';
$row = str_replace (',', '', $row);
$CSV[$rowid] = "\n".implode(",", $row);
}
}
RETURN $CSV;
}

Related

Output The Contents Of A Pivot / Linking Table In A Nested While Loop - MySQL / PHP PDO

I have a situation where I'm trying to output the contents of a MySQL pivot/linking table from a many-to-many relationship with PHP.
In the code below I have a series of boards that will contain images. The actual board previews are outputted with the first block of PHP, but inside this I need a nested while loop that outputs the images themselves.
The pivot/linking table is called boards_images has two columns board_id and image_id and these are both foreign keys to the boards table and images table. A representation of the tables is given below the main code below.
Because some boards will have an image that is already on other boards I obviously need some type of conditional logic that outputs the image when the related board is present.
Each board preview will only show four images so I will need need to add a LIMIT 4 clause to the MySQL
My Question
What is the best way to approach this, do I:
a) Need to do two database calls one in the parent while loop and one in the nested while loop, or do I need to get all of the info from the MySQL database in the parent while loop with a multiple JOIN?
b) How do I actually output the content of the pivot/linking table? I can't seem to get my head around how to do this.
<?php
// $db_id is a variable created from a user login $_SESSION value
$sql = "SELECT boards.board_id, boards.board_name, users.user_id
FROM boards
JOIN users ON boards.user_id = users.user_id
WHERE users.user_id = :user_id
ORDER BY boards.board_id DESC";
$stmt = $connection->prepare($sql);
$stmt -> execute([
':user_id' => $db_id
]);
// parent while loop that outputs the board name
while ($row = $stmt->fetch()) {
$dbBoardname = htmlspecialchars($row['board_name']);
?>
<div class="board-component">
<h2><?= $dbBoardname; ?></a></h2>
<?php
// --- NESTED INNER WHILE LOOP
$SQL2 = "SELECT boards_images.board_id, boards_images.image_id, images.filename
FROM boards_images
JOIN images ON boards_images.image_id = images.image_id
WHERE boards_images.board_id = :board_id
LIMIT 4";
$stmt2 = $connection->prepare($SQL2);
$stmt2 -> execute([
':board_id' => $dbBoardId
]);
while ($row2 = $stmt2->fetch()) {
$dbImageId = htmlspecialchars($row['image_id']);
$dbImageFilename = htmlspecialchars($row['filename']);
?>
<img src='<?= $wwwRoot . "/images-lib/{$dbImageFilename}" ?>' >
<?php } ?> <!-- end of nested while loop -->
</div>
<?php } ?> <!-- end of parent while loop -->
Representation of the Tables
// 'fk' stands for foreign key
// BOARDS_IMAGES LINKING / PIVOT TABLE
+----------------+----------------+
| board_id (fk) | image_id (fk) |
+----------------+----------------+
| 1 | 23 |
| 1 | 106 |
| 1 | 55 |
| 1 | 22 |
+----------------+----------------+
// BOARDS TABLE
+-------------+--------------+---------------+
| board_id | board_name | user_id (fk) |
----------------------------------------------
| 1 | London | 21 |
| 2 | France | 21 |
+-------------+--------------+---------------+
// IMAGES TABLE
+-------------+--------------------+---------------+
| image_id | filename | user_id (fk) |
---------------------------------------------------+
| 23 | BigBen.jpeg | 21 |
| 106 | TowerBridge.jpeg | 21 |
| 55 | TheMall.jpg | 21 |
| 22 | BuckPalace.jpg | 21 |
+-------------+--------------------+---------------+
// USERS TABLE
+-----------------+----------------+
| user_id | username |
+-----------------+----------------+
| 21 | johndoe |
+-----------------+----------------+
a) Need to do two database calls one in the parent while loop and one
in the nested while loop, or do I need to get all of the info from the
MySQL database in the parent while loop with a multiple JOIN?
You can do one query to get all the data necessary, we can limit the number of images displayed in php, and the join to the users table isn't needed because you have the FK in the boards table:
SELECT boards.board_name, images.filename
FROM boards
INNER JOIN boards_images on boards_images.board_id = boards.board_id
INNER JOIN images on boards_images.image_id = images.image_id
WHERE boards.user_id = :user_id
b) How do I actually output the content of the pivot/linking table? I
can't seem to get my head around how to do this.
With the output of the above query resulting in something like:
board_name | filename
-------------------------------
London | BigBen.jpeg
London | TowerBridge.jpeg
London | TheMall.jpg
London | BuckPalace.jpg
France | BigBen.jpeg
France | TowerBridge.jpeg
France | TheMall.jpg
France | BuckPalace.jpg
Your php loop could look something like this:
<?php
// $db_id is a variable created from a user login $_SESSION value
$sql = "SELECT boards.board_name, images.filename
FROM boards
INNER JOIN boards_images on boards_images.board_id = boards.board_id
INNER JOIN images on boards_images.image_id = images.image_id
WHERE boards.user_id = :user_id";
$stmt = $connection->prepare($sql);
$stmt -> execute([
':user_id' => $db_id
]);
$dbBoardname_last = '';
$imgcount = 0;
while ($row = $stmt->fetch()) {
$dbBoardname = htmlspecialchars($row['board_name']);
$dbImageFile = "$wwwRoot/images-lib/" . htmlspecialchars($row['filename']);
//if the boardname is the same as the previous row only add images.
if($dbBoardname != $dbBoardname_last)
{
//reset the image count for new boards
$imgcount = 0;
echo "<div class=\"board-component\"><h2>$dbBoardname</a></h2>";
}
//By counting the images within the loop we can avoid using multiple or nested queries to the DB
if($imgcount < 4) {
echo "<img src=\"$dbImageFile\">";
}
$imgcount++;
if($dbBoardname != $dbBoardname_last)
{
echo '</div>';
}
//record the last board_name to check if a new board element should be created
$dbBoardname_last = $dbBoardname;
}
?>
Note: hopefully this code works as you intended but long term I think best practice would be to parse the SQL output into a JSON object and iterate over that instead, the code might come out cleaner
The approach of running a query, looping the results and for each result, running another query is a suboptimal approach, but it works. You may want to try that out, because it's simple and it is very much possible that the performance issues you are worried about would never materialize, kindly read this: https://stackify.com/premature-optimization-evil/
The disadvantage, of course, is that, instead of running a single command, you run n + 1 command (assuming that the number of boards is n). Queries are costly, so, if you already encountered some performance issues related to this, or you want to write very performant code, then you can:
write a single query
the FORM clause's first table would be boards
LEFT JOIN boards_images and images, with the appropriate criteria
the resulting record would contain the boards and images data, defaulting to null if there are not enough matches
SELECT ...
FROM boards
JOIN users
ON boards.user_id = users.user_id
LEFT JOIN boards_images bi1
ON boards.board_id = bi1.board_id
LEFT JOIN images i1
ON bi1.image_id = i1.image_id
LEFT JOIN boards_images bi2
ON boards.board_id = bi2.board_id AND bi2.board_id NOT IN (bi1.board_id)
LEFT JOIN images i2
ON bi2.image_id = i2.image_id
LEFT JOIN boards_images bi3
ON boards.board_id = bi3.board_id AND bi3.board_id NOT IN (bi1.board_id, bi2.board_id)
LEFT JOIN images i3
ON bi3.image_id = i3.image_id
LEFT JOIN boards_images bi4
ON boards.board_id = bi4.board_id AND bi4.board_id NOT IN (bi1.board_id, bi2.board_id, bi3.board_id)
WHERE users.user_id = :user_id
You loop the records returned and use the returned values. I have left out the SELECT clause's content for you to fill it, you will need boards and images data there, with the images data columns ideally suffixed by the image index, so you can iterate the index and build the column names inside the loop.

MySQL PHP select where "X,Y" is in X,Y,Z

How do I complete this code below? I am trying to select news from the database and the locations which can be 23,22,88,90 location codes. I know you can just do IN('23', '22', '88', '90') but the problem is, my locations are a string so it comes out like IN('23,22,88,90') I believe.
How do I expand on the string of locations and select all or any including the locations in the string? So in the database, newsLocations could be 22 23 22,90 23,80,90 90. If that makes sense? so if $locationstoGet has 22,88,90 only, it will get the newsLocation even if the result is just 88,90 without the 22.
$locationsToGet = '22,88';
$db->query("SELECT * FROM news WHERE newsLocation IN($locationstoGet)");
I hope I explained this alright.
I saw a response on another site here
So I will adapt the solution there to your scenario. Change locationsToGet into an array, and use the implode function to generate the right syntax for the IN Clause.
$locationsToGetArr = array('22','88');
$locations = "'".implode("','",$locationsToGetArr)."'"; //this should output '22','88'
$db->query("SELECT * FROM news WHERE newsLocation IN($locations)");
This solution is assuming your database structure is as such
+--------+--------------+
| news | newsLocation |
+--------+--------------+
| 1 | 88 |
| 1 | 22 |
| 2 | 22 |
| 2 | 88 |
+--------+--------------+
But if you are storing your data as the following instead
+--------+--------------+
| news | newsLocation |
+--------+--------------+
| 1 | 88,22 |
| 2 | 22,88 |
+--------+--------------+
You will not have much choice besides to select all from news table and have PHP filter the location. Not a very efficient method.
If your data is comma separated stored in databse column then you can use MYSQL FIND IN SET as per below example.
SELECT FIND_IN_SET('b','a,b,c,d');
OR you can try with regular expression in MYSQL but it will be too slow.
You can make an array of your locations and then populate your query string with the items from the array
$locations = '22,88';
$locationsToGetArray = explode(",", $locationToGet)
$query = "SELECT * FROM news WHERE newsLocation IN(";
for ($i = 0; $i < count($locationsToGetArray); $i++) {
$query .= $locationsToGetArray[$i];
if($i == (count($locationToGetArray) - 1)) $query.= ")";
else $query .= ",";
}
$db->query($query);

How to show summary table with other table data in a row?

I have following MySQL scheme :
Method table: PK id, name, comment
Okp table: PK id, FK method_id, comment
Tnved table: PK id, FK method_id, comment
Okp --many to one--> Method <-- many to one-- Tnved
Image representation:
I'm need to show HTML summary table from methods. But each method (each row) could have many data from other tables in fields and I'm need to show them all.
It looks like this:
Methods summary
+-----+-----------+--------------+---------------+-----+---------+
| id | name | All OKP data | All TNVED data| ... | Comment |
+-----+-----------+--------------+---------------+-----+---------+
| 1 | Cloth 1 | 841000 | 5007000000 | ... | Special |
| | | 842000 | 5111000000 | | |
| | | 843000 | 5112000000 | | |
| | | 844000 | ... much more | | |
| | | ...much more | | | |
+-----+-----------+--------------+---------------+-----+---------+
| 2 | Game 76 | 259000 | 6100000000 | ... | Nice |
| | | 816700 | 6200000000 | | |
| | | 880000 | 6400000000 | | |
| | | ...much more | ...much more | | |
+-----+-----------+--------------+---------------+-----+---------+
| ... | ... | ... | ... | | ... |
+-----+-----------+--------------+---------------+-----+---------+
| 999 | T-shirt 3 | 831701 | 6302600000 | ... | Bad |
+-----+-----------+--------------+---------------+-----+---------+
I'm tryed to use SQL JOIN but it looks monstrously with multiple redundancy. So i don't know how to use queries better.
I'm solved it with PHP by recieving related data for each row with separate queries, but this solution is too slow. (In fact i have 1000+ rows).
SO how to query and show such data?
I'm using following method to get information from DB:
//Model-file pseudo-code
$result = array();
$methods = $this->db
->select('*')
->from('method')
->get()
->result();
$i = 0;
foreach ($methods as $method){
$result[$i]['method_t'] = $method;
$result[$i]['okps'] = $this->db
->select('*')
->from('okp')
->where('method_id', $method['id]')
->get()
->result();
$result[$i]['tnveds'] = $this->db
->select('*')
->from('tnved')
->where('method_id', $method['id]')
->get()
->result();
//so on
$i++;
}
I'm using following method to show summary table:
//View-file pseudo-code
//Table header
foreach ($result as $method) {
echo '<tr>';
echo '<td>' . $method['method_t']['id'] . '</td>';
echo '<td>' . $method['method_t']['name'] . '</td>';
echo '<td><ul>';
foreach ($method['okps'] as $okp) {
echo '<li>' . $okp['id'] . '</li>';
//in fact much more data from $okp with separate template
}
echo '</ul></td>';
echo '<td><ul>';
foreach ($method['tnveds'] as $tnved) {
echo '<li>' . $tnved['id'] . '</li>';
}
//in fact much more data from $tnveds with separate template
echo '</ul></td>';
//so on
echo '</tr>';
}
echo '</table>';
Perhaps I'm missing something due to my lack of php skills, but I see no reason why you can't get all the records in one shot and then just use the php to show it how you want. Here's an example, but the php can only best be described as pseudo-code as I have little experience with it:
select
m.id as m_id,
m.name as m_name,
m.comment as m_comment,
o.id as o_id,
o.comment as o_comment,
t.id as t_id,
t.comment as t_comment
from
method m
inner join okp o
on m.id = o.method_id
inner join tnved t
on m.id = t.method_id
order by m.id, o.id, t.id;
For the php, something like the following. I omitted the tvned stuff as you can add that in by just copying the model of the okp part.
$is_first=true;
$m_id_last = 0;
$m_id = 0;
$o_id_last = 0;
$o_id = 0;
$o_str = "";
foreach ($result as $method) {
$m_id_last = $m_id;
$m_id = $method['m_id'];
if ((!is_first) && ($m_id_last != $m_id)) {
echo '<tr>';
echo '<td>' . $m_id . '</td>';
echo '<td>' . $method['name'] . '</td>';
echo '<td><ul>';
echo o_str;
echo '</ul></td>';
echo '</tr>';
$o_str = "";
}
$o_str .= '<li>' . $method['o_id'] . '</li>';
$is_first=false;
}
Why not to use a single SQL query, but be careful when using INNER JOIN.
Because you can have for method #1 related rows in okp table, but haven't any rows for method #1 in tnved table and vice versa.
Beware: in query and php code below I'm using name column instead of comment column for okp and tnved tables.
So, SQL should be smth like this:
SELECT m.id, o.id AS oid, o.name AS oname, t.id AS tid, t.name as tname, m.comment
FROM
(method AS m LEFT JOIN okp AS o ON m.id = o.method_id)
LEFT JOIN tnved AS t ON m.id = t.method_id
ORDER BY m.id
After execution of query above you'll have smth like this:
https://drive.google.com/a/thepaydock.com/file/d/0B3C0Ywfdde5Fa2lPc0FBdlZENG8/view?usp=drivesdk
Next iterate query results rows with PHP:
$output = array();
foreach($results AS $id => $method) {
$output[$id]['okps'] = array();
$output[$id]['tnveds'] = array();
if(!is_null($method['oid'])) {
$output[$id]['okps'][$method['oid']] = array(
'name' => $method['oname'];
);
};
if(!is_null($method['tid'])) {
$output[$id]['tnveds'][$method['tid']] = array(
'name' => $method['tname'];
);
}
}
Next render $output array where $output array key is method_id.
Also php code above can be refactored. I've provided basic example.
The basic issue is the semi-cartesian product. If there are five rows from "okp" (for given m_id) and six rows from "tnved", a join operation is going to match each row from "okp" with each row from "tnved", a total of thirty rows. Six copies of each "okp" row, and five copies of each "tnved" row.
If there are three, four, five tables involved in the join, and each has a dozen or more rows... the resulting conglomeration of duplication becomes unwieldy.
There are several approaches to taming the problem.
If I was going to run a query against "method", and process each row from in a loop, one "m_id" at a time...
For each row from "method", I would execute another single query to get back all of the rows from "okp", "tnved", et al. by combining the individual queries with a UNION ALL. This would eliminate the duplication of the rows. With an appropriate index defined e.g. "... on okp (m_id, id)", the queries should be pretty efficient.
For example:
SELECT okp.m_id AS m_id
, 'okp' AS src
, okp.id AS id
, okp.comment AS comment
FROM okp
WHERE okp.m_id = ?
ORDER BY okp.m_id, okp.id
UNION ALL
SELECT tnved.m_id AS m_id
, 'tnved' AS src
, tnved.id AS id
, tnved.comment AS comment
WHERE tnved.m_id = ?
ORDER BY tnved.m_id, tnved.id
We use a UNION ALL and not a UNION, so the results are just concatenated together, and avoid the overhead of a sort and removal of duplicate rows.
Note that we include a "src" discriminator column. Our code can use the value in this column to determine which query returned it. In this case, each query is from a single table, so we can just identify the table the values are from.
If one of the tables has more columns to return than the others, we add "dummy" expressions to the SELECT list of the other queries. That lets us satisfy the requirements of the UNION ALL where all the sets being combined must have the same number of columns, and the same datatypes.
This approach eliminates a lot of the duplication we get with a semi-cartesian product. This approach requires an "extra" query for each m_id that we process, but if we're only putting 20 m_id on a page, it's only 20 queries to run.
as far as the php for this... execute the query, supplying m_id value for each of the bind placeholders. fetch the results, and each row into the appropriate (emptied) array. rows from "okc" into an $okc[] array, the rows from "tnved" into a $tnved[] array.
Looks like that setup would work for the current "outputting" code.
As long as you're processing a limited number of m_id, and there are appropriate indexes available, this approach can be reasonably efficient.

Query based on three database tables always returns zero results

I have a database with three tables in it:
places:
id | name | latitude | longitude |
------|--------|------------|------------|
1 | place1 | 11.123456 | 76.123456 |
------|--------|------------|------------|
2 | place2 | 23.123456 | 65.123456 |
etc ...
categorized_places:
id | place_id | cat_id |
------|----------|--------|
1 | 1 | 2 |
------|----------|--------|
2 | 2 | 1 |
etc ...
places_visited:
id | user_name | user_email | place_id |
------|-----------|------------|----------|
1 | user_1 | x#mail.com | 2 |
------|-----------|------------|----------|
2 | user_2 | y#mail.com | 2 |
There's also a fourth named categories, but it's not important in this.
I'm trying to filter the places from the places-table to show the user the nearest place, that he/she has not yet visited.
$cur_cat is set on the previous page, where the user selects which kind of place he/she would like to visit.
$cur_user and $cur_user_email are based on $_SESSION variables
$max_lat, $max_lon, $min_lat and $min_lon are based on the users current position
I'm using this code in php (with PDO), but it always returns zero results:
$get_places = $db->prepare("
SELECT
places.id,
places.name,
places.latitude,
places.longitude
FROM
places,
categorized_places,
places_visited
WHERE
places.id = categorized_places.place_id
AND categorized_places.cat_id = '$cur_cat'
AND places.latitude <= '$max_lat'
AND places.latitude >= '$min_lat'
AND places.longitude <= '$max_lon'
AND places.longitude >= '$min_lon'
AND places_visited.user_name = '$cur_user'
AND places_visited.user_email = '$cur_user_email'
AND places.id != places_visited.place_id
");
$get_places->execute();
The code always shows 0 results and throws no error. I've also made sure, that the places are not already in the places_visited table.
I've stared at this for so very long now, and I just can't figure out the error.
Any help would be very appreciated!
Your query is doing inner joins. So, it can only return places that the user has visited. No way that it can return places that a user hasn't visited. Before proceeding further, here is a simple rule: Never use commas in the from clause. ANSI standard explicit JOIN syntax has been around for over two decades, and you should use it. In fact, in this case, you need it, because you need an outer join:
SELECT p.id, p.name, p.latitude, p.longitude
FROM places p INNER JOIN
categorized_places cp
ON p.id = cp.place_id LEFT JOIN
places_visited pv
ON pv.place_id = p.id AND
pv.user_name = '$cur_user' AND
pv.user_email = '$cur_user_email'
WHERE cp.cat_id = '$cur_cat' AND
p.latitude <= '$max_lat' AND
p.latitude >= '$min_lat' AND
p.longitude <= '$max_lon' AND
p.longitude >= '$min_lon' AND
pv.place_id IS NULL;
What this does is it matches the conditions to all the places visited, using an outer join. Then the condition pv.place_id IS NULL chooses the ones that have not been visited. Note that the conditions on the places_visited table go in the ON clause. The conditions on the other two tables remain in the WHERE clause. In general, when using LEFT OUTER JOIN, the filters on the first table stay in the WHERE clause. The filters on the second table go in the ON clause.
I also introduced table aliases. These help make queries easier to write and to read.

MYSQL left join if then statement favorite table procedure

i have 2 tables
here is table 1 table name is
forexample
itemlist
+-----+----------+-----+
| uid | username | age |
+-----+----------+-----+
| 1 | doe | 17 |
| 2 | smith | 18 |
| 3 | john | 30 |
+-----+----------+-----+
and other one is
fav
+-----+------+---------+
| uid | user | itemuid |
+-----+------+---------+
| 1 | alex | 2 |
+-----+------+---------+
Here is my mysql query *NOT Working * any way to fix this problem when i run php file i got error in mysql syntax
SELECT c.uid, c.username, c.age,i.uid,i.user,i.itemuid
from itemlist c
left join fav i on c.uid = i.itemuid
if (i.user = 'alex') THEN
SET #fav = 1;
ELSE
SET #fav = 0;
END IF
this is sample php
while ($row = mysqli_fetch_array($res)){
if ($row['fav'] = '1'){
echo $row['username']." is exit in fav";
}else{
echo $row['username']." is not exit in fav";
}
}
i hope you understand my question right ?
To get a column named fav returned in the resultset, you would need to include an expression in the SELECT list, and give it an alias fav.
It's not at all clear why you would need a MySQL user-defined variable; if you don't know why you'd need one, then you probably don't need one.
Given that your PHP code is looking for a column named fav in the resultset, likely you want something like this:
SELECT c.uid
, c.username
, c.age
, i.uid AS i_uid
, i.user
, i.itemuid
, IF(i.user='alex',1,0) AS fav
FROM itemlist c
LEFT
JOIN fav i ON i.itemuid = c.uid
Note that the original query had two columns named uid; if you want to return both, and be able to reference both of those by column name, you need to have distinct names for each. In the query above, I've assigned an alias to the i.uid column so that both uid columns will be available by distinct column name.

Categories