Problem with mysql_fetch_assoc - php

I'm having a little trouble with a mysql_fetch_assoc script i hope you can help with.
Background:
I'm retrieving my data for part of my site with a typical MySQL query and echoing out the results from various fields etc etc from my main table whose structure is not important but which has a unique id which is 'job_id'
In order to have multiple catagories associated with that 'job_id' i have employed a toxi solution which associates catgories to each 'job_id'.
TABLE `tags` (
`tag_id` INT NOT NULL AUTO_INCREMENT,
`tag_name` VARCHAR(20) NOT NULL,
PRIMARY KEY (`tag_id`)
)
CREATE TABLE `tag_relational` (
`job_id` INT NOT NULL,
`tag_id` INT NOT NULL
)
When i echo out the info from the main table (using 'job_id') i also want to echo all the catagories which that job_id is matched against, which id using:
$query = "SELECT * FROM tags t
JOIN tag_relational r
ON t.tag_id=r.tag_id
WHERE r.job_id = $job_id";
$result=mysql_query($query) or die(mysql_error());
$cats=mysql_fetch_assoc($result);
In my code i'm using this to echo out the matched catagories:
<p>Job Catagories | <?php while ($cats=mysql_fetch_assoc($result)) { echo $cats['tag_name'];}?></p>
I have two problems:
The above echo seems to be ignoring the first tag name all together, so if a job is tagged with six catagories, it only echos five. If a job is only tagged with one, I get nothing. The query works in the SQL shell, so I'm assuming the problem lie in the PHP.
When I do get multiple echos, I want to be able to seperate them with a comma or a |, but I'm unfamiliar with using group_concat in queries, so I could also use a little help there.

Because mysql_fetch_assoc is already called once before the while loop, the first row is always discarded.
This should solve your problems:
$query = "SELECT * FROM tags t
JOIN tag_relational r
ON t.tag_id=r.tag_id
WHERE r.job_id = $job_id";
$result=mysql_query($query) or die(mysql_error());
<p>Job Catagories |
<?php
$first = true;
while ($cats=mysql_fetch_assoc($result)) {
if($first){
$first = false;
} else{
echo ", ";
}
echo $cats['tag_name'];
}
?>
</p>

use SEPARATOR in group_concat. see example below
mysql> SELECT student_name,
-> GROUP_CONCAT(DISTINCT test_score
-> ORDER BY test_score DESC SEPARATOR ' ')
-> FROM student
-> GROUP BY student_name;

Related

calculation within table row by row

I have a table in database as followings:
locationname postcode locationlatitude locationlongitude
--------------------------------------------------------
1. gsfs 2322. 352353. 35235235
2. gsfs 2322. 352443. 352353
3. gsfs 2322. 352353. 35235235
.
.
I want to calculate the distance between each location to all locations in the table(many to many). I have been able to compute one-to-many and one-to-one calculation but I couldn't figure out how to calculate distance from every location to every location. Do I have to use nested loop or not? Should I have to fetch all the data into an array first? Can you please help me out with this. I don't know whether my question is clear or not.
I have tried but I couldn't execute it due to errors and stupid mistakes.
$sql = "SELECT id, locationname, locationlatitude, locationlongitude, postcode FROM distancetable";
$result = $conn->query($sql);
$i=0;
$j=0;
while($row = mysql_fetch_assoc($result)) {
for ($row=0; $row <num_rows ; $row++) {
$lat1=$row[$i]["locationlatitude"];
$long1=$row[$i]["locationlongitude"];
$suburb1=$row[$i]["locationname"];
$lat2=$row[$j]["locationlatitude"];
$long2=$row[$j]["locationlongitude"];
$suburb2=$row[$j]["locationname"];
$distance=Round(ACOS(SIN(PI()*$long1/180)*SIN(PI()*$long2/180)+COS(PI()*$long1/180)*COS(PI()*$long2/180)*COS(PI()*$lat1/180-PI()*$lat2/180))*6371,3);
echo nl2br (" \n ");
echo $row[id]. ". Distance between " .$suburb1. " to " .$suburb2. "=" .$distance;
$i++;
$j++;
So I made a table like this:
create table latlongdata (
id int auto_increment primary key not null,
locationname varchar(35) not null,
postcode char(5) not null,
locationlatitude numeric(6, 3) not null,
locationlongitude numeric(6,3) not null
);
then I added some sample lat/long values like so:
insert into latlongdata (locationname, postcode, locationlatitude, locationlongitude)
VALUES
('Fenwick, MI', '48834',43.142,-85.049),
('Andover, MN', '55304', 45.255, -93.287),
('Minneapolis, MN', '55422', 45.015, -93.340),
('Lydia, SC', '29079', 34.296, -81.113);
then I made a query like so:
select
a.postcode as FromPostCode,
b.postcode as ToPostCode,
round(acos(sin(pi()*a.locationlongitude/180)*sin(pi()*b.locationlongitude/180)+cos(pi()*b.locationlongitude/180)*cos(pi()*a.locationlatitude/180-pi()*b.locationlatitude/180))*6731, 3) as distance
from latlongdata a
inner join latlongdata b
where b.id > a.id;
...which should be close to what you want, but I think there might be something wrong with your math since the results didn't make a lot of sense. But that should be how you do it.
Note the self-join with the inequality operator on the SELECT statement. This will prevent you from calculating B -> A after you've already calculated A -> B. Fair warning: With a large table, you should expect the result set for this to be... large. It might be better to directly insert the results into a new table (assuming you have sufficient storage space) than try to select out the results.

SQL - Delete row based on number of rows in another table

How would I go about deleting a row from the table 'subjects' that has a primary id 'subject_id' based on the number of rows in another table named 'replies' that uses a 'subject_id' column as a reference.
Example in pseudo code:
If ('subject' has less than 1 reply){
delete 'subject'}
I don't know much about SQL triggers so I have no clue if I would be able to incorporate this directly in the database or if I'd have to write some PHP code to handle this...
To delete any subjects that have had no replies, this query should do the trick:
DELETE s.* FROM subjects AS s
WHERE NOT EXISTS
(
SELECT r.subject_id
FROM replies AS r
WHERE r.subject_id = s.subject_id
);
Demo: DB Fiddle Example
One of the MySQL gurus will need to weigh in on whether or not you can do this directly, but in PHP you could...
$query = "SELECT subject_id FROM subjects WHERE subject='test'";
$return = mysqli_query($mysqli, $query);
$id = mysqli_fetch_assoc($return);
$query = "SELECT reply_id FROM replies WHERE subject_id='".$id[0]."'";
$return = mysqli_query($mysqli, $query);
if(mysqli_num_rows($return) < 1){
$query = "DELETE FROM subjects WHERE subject_id='1'";
$return = mysqli_query($mysqli, $query);
}
This example assumes the "subject" is unique. In other words, SELECTing WHERE subject='test' will only ever return one subject_id. If you were doing this as a periodic cleaning, you would grab all the subject_id values (no WHERE clause) and loop through them to remove them if no replies.
You can achieve this in one query by selecting all (unique) subject-ids from the replies table, and delete all subjects that doesn't have a reply in there. Using SELECT DISTINCT, you don't get the IDs more than once (if a subject has more than one reply), so you don't get unnecessary data.
DELETE FROM subjects
WHERE subject_id NOT IN (SELECT DISTINCT subject_id FROM replies)
Any subject that doesn't have a reply should be deleted!
So you want to delete all subjects with no replies:
DELETE FROM subjects WHERE subject_id NOT IN
(SELECT subject_id FROM replies);
I think this is what you want...

MySQL query searching (advanced)

I have a course page, this page is setup to display the details (easy), then who is teaching it first, second, third, and forth period of each semester. The problem with that is, my teachers data is handled in one giant chain.
Bobby: 1-1-1-1-1-1-1-1
Tina: 20-20-20-10-1-1-1-1
Joey: 20-1-1-1-49-432-10-19
What I want to do is find a course: 20, when all of the information is shown a search through teachers would be made to find out who is teaching course 20 and what period?
Ex.
Course id: 20
Period 1: Joey, Tina
Period 2: Tina
Period 3: Tina
I want to get every teacher teaching that course in one search but given the obstacle of the capact data which may be a problem.
Teacher Table:
id / name / link / course (1-1-1-1-1-1-1-1)
Course Table:
id / name / code / grade / level
Teachers Course contains the id's from course Table
While re-structuring your db might be the best answer, I thought I'd post a straight php solution that works with your current structure and presumptively the rest of your code.
//I set up variables to contain your expected search results so I could test
$search_course = '20';
$search_results = array(
'Bobby' => '1-1-1-1-1-1-1-1',
'Tina' => '20-20-20-10-1-1-1-1',
'Joey' => '20-1-1-1-49-432-10-19'
);
//explode the course strings into arrays and store with teacher names so
//you can loop through them later
foreach($search_results as $teacher=>$string_courses){
$array_courses = explode('-',$string_courses);
$search_results[$teacher] = $array_courses;
}
//Match course you are searching for to the elements in your array
//Create a result array with period and matching teachers
foreach($search_results as $teacher=>$courses){
foreach($courses as $period => $course){
if($course == $search_course){
$results[$period][] = $teacher;
}
}
}
//Loop through your result array and show the results
//I expect you'll have different html for this
foreach($results as $period => $teachers){
echo 'Period: ';
echo $period+1;
echo implode(',',$teachers);
echo '<br>';
}
The printed results match the list you wanted in your OP
Create a new table
Something like this:
CREATE TABLE `TeacherToPeriod` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`TeacherID` INT NOT NULL ,
`CourseID` INT NOT NULL ,
`Period` INT NOT NULL
) ENGINE = MYISAM ;
Insert the data
Here is some PHP-Code:
foreach($lines as $line){
$line_data = split($line, ': ');
$teacher = $line_data[0];
// SELECT your $teacher_id from the database
$courses = split($line_data[0], '-');
$i = 0;
foreach($courses as $course_id){
$i++;
$sql = "INSERT INTO `TeacherToPeriod` (`TeacherID` ,`CourseID` ,`Period`) ";
$sql.= "VALUES ($teacher_id, $course_id, $i);"
mysql_query($sql);
}
}
Select the data you want
SELECT * FROM `TeacherToPeriod` WHERE `CourseID` = 20 ORDER BY `Period` ASC;
You should change the structure of your db, instead of storing a string of all the periods, you should have an additional table with three columns: teacher,course,period and have a separate row in this table for each course that a teacher is teaching. Then determining who is teaching what course would simply be a matter of querying that table by course id and then sorting by period. e.g:
SELECT teacher_id, course_id, period FROM course_info WHERE course_id = 20
ORDER BY period;

How can you access two identically-named columns in a MySQL LEFT JOIN query?

I have two tables.
table_x:
id INT(11)
tag INT(11)
table_tags:
id INT(11)
name VARCHAR(255)
Then I use PHP to perform the following query:
SELECT * FROM table_x LEFT JOIN table_tags ON table_x.tag = table_tags.id
The only problem is: how do I access table_x.id and table_tags.id in the results?
Here is the PHP code:
$query = "SELECT * FROM table_x LEFT JOIN table_tags ON table_x.tag = table_tags.id";
$results = mysql_query($query);
while($row = mysql_fetch_array($results))
{
// how do I now access table_x.id and table_tags.id ???
}
You name the columns in your column list:
SELECT
table_x.id AS x_id,
table_tags.id AS tag_id,
other, columns, here
FROM table_x LEFT JOIN table_tags
ON table_x.tag = table_tags.id
In general, it's considered good form to name the columns you want returned explicitly rather than relying on * (which may cause the actual number, order, and names of columns to change if the underlying table structure changes). At the same time, you can alias the column with new names in the result set for ease of use (the AS keyword is optional in most SQL dialects).
Update: OP states in a comment that he must use "*". Although this is not recommended, you can do this:
SELECT
table_x.id AS x_id,
table_tags.id AS tag_id,
*
FROM table_x LEFT JOIN table_tags
ON table_x.tag = table_tags.id
which combines both the named columns you want and the (bad) "*" technique. Your columns will be included in the result set twice, in the first two column positions and in their regular positions in the column list. The values will be the same.
Use it like this
$query = "SELECT (table_x.id) AS xid, (table_tags.id) AS tid FROM table_x LEFT JOIN table_tags ON table_x.tag = table_tags.id";
$results = mysql_query($query);
while($row = mysql_fetch_array($results))
{
**//fetch id of table_x with xid**
**echo "table_x id: ".$row['xid'];**
//fetch id of table_tags with tid
**echo "table_tags id: ".$row['tid'];**
}

Summing a field from all tables in a database

I have a MySQL database called "bookfeather." It contains 56 tables. Each table has the following structure:
id site votes_up votes_down
The value for "site" is a book title. The value for "votes_up" is an integer. Sometimes a unique value for "site" appears in more than one table.
For each unique value "site" in the entire database, I would like to sum "votes_up" from all 56 tables. Then I would like to print the top 25 values for "site" ranked by total "votes_up".
How can I do this in PHP?
Thanks in advance,
John
You can do something like this (warning: Extremely poor SQL ahead)
select site, sum(votes_up) votes_up
from (
select site, votes_up from table_1
UNION
select site, votes_up from table_2
UNION
...
UNION
select site, votes_up from table_56
) group by site order by sum(votes_up) desc limit 25
But, as Dav asked, does your data have to be like this? There are much more efficient ways of storing this kind of data.
Edit: You just mentioned in a comment that you expect there to be more than 56 tables in the future -- I would look into MySQL limits on how many tables you can UNION before going forward with this kind of SQL.
Here's a PHP code snip that should get it done.
I have not tested it so it might have some typos and stuff, make sure you replace DB_NAME
$result = mysql_query("SHOW TABLES");
$tables = array();
while ($row = mysql_fetch_assoc($result)) {
$tables[] = '`'.$row["Tables_in_DB_NAME"].'`';
}
$subQuery = "SELECT site, votes_up FROM ".implode(" UNION ALL SELECT site, votes_up FROM ",$tables);
// Create one query that gets the data you need
$sqlStr = "SELECT site, sum(votes_up) sumVotesUp
FROM (
".$subQuery." ) subQuery
GROUP BY site ORDER BY sum(votes_up) DESC LIMIT 25";
$result = mysql_query($sqlStr);
$arr = array();
while ($row = mysql_fetch_assoc($result)) {
$arr[] = $row["site"]." - ".$row["sumVotesUp"];
}
print_r($arr)
The UNION part of Ian Clelland answer can be generated using a statement like the following. The table INFORMATION_SCHEMA.COLUMNS has a column TABLE_NAME to get all tables.
select * from information_schema.columns
where table_schema not like 'informat%'
and column_name like 'VOTES_UP'
Join all inner SELECT with UNION ALL instead of UNION. UNION is doing an implicit DISTINCT (on oracle).
The basic idea would be to iterate over all your tables (using a SQL SHOW TABLES statement or similar) in PHP, then for every table, iterate over the rows (SELECT site,votes_up FROM $table). Then, for every row, check the site against an array that you're building with sites as keys and votes up as values. If the site is already in the array, increment its votes appropriately; otherwise, add it.
Vaguely PHP-like pseudocode:
// Build an empty array for use later
$votes_array = empty_array();
// Get all the tables and iterate over them
$tables = query("SHOW TABLES");
for($table in $tables) {
$rows = query("SELECT site,votes_up FROM $table");
// Iterate over the rows in each table
for($row in $rows) {
$site = $row['site'];
$votes = $row['votes_up'];
// If the site is already in the array, increment votes; otherwise, add it
if(exists_in_array($site, $votes_array)) {
$votes_array[$site] += $votes;
} else {
insert_into_array($site => $votes);
}
}
}
// Get the sites and votes as lists, and print out the top 25
$sorted_sites = array_keys($votes_array);
$sorted_votes = array_values($votes_array);
for($i = 0; $i < 25; $i++) {
print "Site " . $sorted_sites[$i] . " has " . $sorted_votes[$i] . " votes";
}
"I allow users to add tables to the database." - I hope all your users are benevolent and trustworthy and capable. Do you worry about people dropping or truncating tables, creating incorrect new tables that break your code, or other things like that? What kind of security do you have when users can log right into your database and change the schema?
Here's a tutorial on relational database normalization. Maybe it'll help.
Just in case someone else that comes after you wants to find what this could have looked like, here's a single table that could do what you want:
create database bookfeather;
create user bookfeather identified by 'bookfeather';
grant all on bookfeather.* to 'bookfeather'#'%';
use bookfeather;
create table if not exists book
(
id int not null auto_increment,
title varchar(255) not null default '',
upvotes integer not null default 0,
downvotes integer not null default 0,
primary key(id),
unique(title)
);
You'd vote a title up or down with an UPDATE:
update book set upvotes = upvotes + 1 where id = ?
Adding a new book is as easy as adding another row:
insert into book(title) values('grails in action')
I'd strongly urge that you reconsider.

Categories