Combining these MySQL queries - mysql

I am learning MySQL and messing about with joins. I am wondering if I can cut these queries down and combine them. This is what I would NORMALLY do:
$stmt = $db_pdo->prepare('SELECT date, table2_id, comments
FROM table1
WHERE user = "victor"');
$stmt->execute();
foreach ($stmt as $row) {
$table2_id = $row['table2_id'];
}
$stmt = $db_pdo->prepare('SELECT table3_id
FROM table2
WHERE table2_id = '.$table2_id.'');
$stmt->execute();
foreach ($stmt as $row) {
$table3_id = $row['table3_id'];
}
$stmt = $db_pdo->prepare('SELECT previous_comments_count
FROM table3
WHERE table3_id = '.$table3_id.'');
$stmt->execute();
foreach ($stmt as $row) {
$table3_id = $row['table3_id'];
}
(I changed the variables, so it looks stupid on purpose--I did not design the table for this company)
Basically, I need to get most of my information from the first table, but there is one "id" that needs to cross two tables that I would like to put into the first query.
So what I tried was:
$stmt = $db_pdo->prepare('SELECT date, table2_id, comments
FROM table1
LEFT JOIN table2
ON table1.table2_id = table2.table2_id
LEFT JOIN table3
ON table2.table3_id = table3.table3_id
WHERE user = "victor"');
And obviously my first (and many tweaked versions of this line) have failed, leading to a blank fetch.
I can do the first left join, but even that doesn't seem to show the information from the second table. The second and third tables do not have the same columns as the first table. (I don't think this is a problem but I could be wrong.)
I've done some reading on various websites, and though I love figuring out my problems on my own, I think it's best to ask if what I'm trying to accomplish is even possible and to try and get it done ASAP. I've read some other questions like this, but I am just simply befuddled looking at the complexity of this let alone those ones. (It's one of those days...)
It could be that I'm also using pdo and prepared statements for the first time as well, I do NOT think that's the problem, but I apologize if it is. I'm not a MySQL genius and have not spent much time with databases.
Also (side question, if permitted): Do I have to use foreach? Or can I just trust that calling $stmt after the execute is good enough? (AKA passes true/false on the ONE result that should show up)

Try the joins like this:
table3
LEFT JOIN table2
ON table3.table3_id = table2.table3_id
LEFT JOIN table1
ON table2.table2_id = table1.table2_id

Related

ID lost after left join on table

Hey I tried this thread but it doesn't work and i can't figure out why...
here's my SQL:
SELECT * FROM gone_items
LEFT JOIN items
ON gone_items.item_ID=items.ID
WHERE
gone_items.aus_ID='$ID'
ORDER BY items.name ASC
Now, I fetch that via PHP and have a $row and try another mysql to get the individual ID's of the gone_items table. But if i use $row['ID'] I get the ID of the items.ID not the one from gone_items.ID.
I tried setting the variable manually in the first query but it doesn't work.
I also tried this: MYSQL Left join A.table and b.table while retaining a.table id
Also didn't help me...
All I want is to retain the ID (Primary key) from the gone_items table..
Can anyone please tell me what I'm doing wrong ?
Love
Gram
EDIT
//Query for Joined infos
$sqlx="SELECT foto_res_ausgeliehene_geg.ID, foto_res_ausgeliehene_geg.aus_ID, foto_res_ausgeliehene_geg.geg_ID, foto_res_ausgeliehene_geg.zusaetzliches, foto_res_gegenstaende.ID, foto_res_gegenstaende.bezeichnung, foto_res_gegenstaende.seriennummer, foto_res_gegenstaende.interne_seriennummer, foto_res_gegenstaende.zusaetzliches FROM foto_res_ausgeliehene_geg
LEFT JOIN foto_res_gegenstaende
ON foto_res_ausgeliehene_geg.geg_ID=foto_res_gegenstaende.ID
WHERE
foto_res_ausgeliehene_geg.aus_ID='$ID'
ORDER BY foto_res_gegenstaende.bezeichnung ASC
";
$ergebnisx = mysqli_query($db,$sqlx);
while ($zeilex = mysqli_fetch_assoc($ergebnisx))
{
//Query for individual infos
$sqly="SELECT * FROM foto_res_ausgeliehene_geg
WHERE `geg_ID`='".$zeilex['ID']."'
AND `aus_ID`='$ID'
GROUP BY `geg_ID`
";
$ergebnisy = mysqli_query($db,$sqly);
while ($zeiley = mysqli_fetch_assoc($ergebnisy))
{};
Now I did select all items individually. The foto_res_ausgeliehene_geg.ID still merges with the foto_res_gegenstanede.ID due to the LEFT JOIN.
So if i access $zeilex['ID'] im getting the ID of foto_res_gegenstaende.ID.
Would it help if I rename the ID field in one of the tables into lets say item_ID ?
Thanks alot.
Love
Gram.
Instead of using select *, you should explicitly state what items you want to select. Else you can get conflicts with multiple id fields. In your case something like:
select gone_items.id, gone_items.column1, gone_items.column2, items.column1, items.column2
It is also considered good practice, to limit the amount of data there is being selected. But is meanwhile also a highly debateable what is the right way. Performance issue in using SELECT *?
WORKS!
I simply renamed one of the Primary ID keys to something else, in this case, one of them got ID -> item_ID. The other one still is ID that way the left join won't merge them.
yolo
EDIT
WORKING CODE
$sqlx="SELECT foto_res_ausgeliehene_geg.item_ID, foto_res_ausgeliehene_geg.aus_ID, foto_res_ausgeliehene_geg.geg_ID, foto_res_ausgeliehene_geg.zusaetzliches, foto_res_gegenstaende.ID, foto_res_gegenstaende.bezeichnung, foto_res_gegenstaende.seriennummer, foto_res_gegenstaende.interne_seriennummer, foto_res_gegenstaende.zusaetzliches FROM foto_res_ausgeliehene_geg
LEFT JOIN foto_res_gegenstaende
ON foto_res_ausgeliehene_geg.geg_ID=foto_res_gegenstaende.ID
WHERE
foto_res_ausgeliehene_geg.aus_ID='$ID'
ORDER BY foto_res_gegenstaende.bezeichnung ASC
";
$ergebnisx = mysqli_query($db,$sqlx);
while ($zeilex = mysqli_fetch_assoc($ergebnisx))
{
//Query for individual infos
$sqly="SELECT * FROM foto_res_ausgeliehene_geg
WHERE `item_ID`='".$zeilex['item_ID']."'
";
$ergebnisy = mysqli_query($db,$sqly);
while ($zeiley = mysqli_fetch_assoc($ergebnisy))
{

multiple left join's issue

$sth = $db->prepare("SELECT tblCompanies.*, users.Username, cargo.Name, (tblCompanies.Money + SUM(tblCTerminals.sellValue)) as assets, (COUNT(tblFinishedContracts.ID) + COUNT(tblFinishedSubContracts.ID)) as completed FROM ((((tblCompanies LEFT JOIN users ON tblCompanies.CompanyCEO = users.ID) LEFT JOIN cargo ON (tblCompanies.PreferredCargo = cargo.Cargo_ID)) LEFT JOIN tblCTerminals ON (tblCompanies.Company_ID = tblCTerminals.companyID)) LEFT JOIN tblFinishedContracts ON (tblCompanies.Company_ID = tblFinishedContracts.companyID)) LEFT JOIN tblFinishedSubContracts ON (tblCompanies.Company_ID = tblFinishedSubContracts.companyID) WHERE (users.Username LIKE :info || tblCompanies.CompanyName LIKE :info2 || CONCAT('$',FORMAT((tblCompanies.Money + SUM(tblCTerminals.sellValue)),2)) LIKE :info3 || CONCAT('$',FORMAT(tblCompanies.Money,2)) LIKE :info4 || cargo.Name LIKE :info5 || users.pLevel LIKE :info6 || CONCAT('$',FORMAT((users.Cash_In_Bank + users.Cash_In_Hand),2)) LIKE :info7)");
$sth->bindValue(':info', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
$sth->bindValue(':info2', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
$sth->bindValue(':info3', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
$sth->bindValue(':info4', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
$sth->bindValue(':info5', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
$sth->bindValue(':info6', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
$sth->bindValue(':info7', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
Ok so now my reasoning for this mess:
I'm using the jQuery Datatables plugin and when it does a request to the server it sends the query. It expects back the results, how many results in the search query and how many total results. With this query I can return the results.
However on COUNT(tblFinishedContracts.ID) it is grabbing the table 8 times. So for example instead of returning the correct amount of 8 it is returning a count of 112.
When I do:
SELECT COUNT(tblFinishedContracts.ID)
FROM tblCompanies
LEFT JOIN tblFinishedContract
ON (tblCompanies.Company_ID = tblFinishedContracts.ID)
WHERE tblCompanies.Company_ID = 11
It works, it returns 8. If someone knows a better way of doing what I'm trying to accomplish or can tell me how to fix the issue, it would be greatly appreciated!
Thanks,
Jeff
EDIT: to add, no columns have duplicate names everything is different.
There's no single silver bullet for this issue; getting information about a parent table, while also getting information from multiple child tables with one-to-many relationships, is always tricky. But there are a few techniques that can help.
Firstly, you don't actually do anything with tblFinishedContracts except get a count of records from it; so instead of writing COUNT(tblFinishedContracts.ID) and using a join, you can use a subquery: write (SELECT COUNT(1) FROM tblFinishedContracts WHERE Company_ID = tblCompanies.Company_ID). The same thing applies to tblFinishedSubContracts, so you can eliminate that join as well. (By the way, eliminating these joins should also improve your performance significantly, since you'll only need to retrieve information about contracts that belong to companies that are actually being returned.)
That takes care of most of the problem. The joins to users and cargo to get the CEO and preferred cargo shouldn't cause any problems. (Right?)
The only remaining difficulty is tblCTerminals, since SUM(tblCTerminals.sellValue) field occurs both in the field list and in the WHERE clause. There's no really compelling way to handle it. One option is to use the above-mentioned subquery approach, with two copies of the subquery. Another is to replace FROM tblCompanies with FROM (SELECT tblCompanies.*, COALESCE(SUM(tblCTerminals.sellValue), 0) AS totalCTerminalSellValue FROM tblCompanies LEFT JOIN tblCTerminals ON tblCTerminals.Company_ID = tblCompanies.Company_ID GROUP BY tblCompanies.Company_ID) AS tblCompanies, which in essence creates a temporary copy of tblCompanies that's augmented with a new field named totalCTerminalSellValue. So, elsewhere in the query, you can replace SUM(tblCTerminals.sellValue) with tblCompanies.totalCTerminalSellValue.

How can I optimize this raw SQL and perhaps implement it via CodeIgniter?

It's been a while since I've written raw SQL, I was hoping someone could help me out in optimizing this SQL query so that it works across, both, MySQL and PostgreSQL.
I would also have to implement this via CodeIgniter (2.x) using ActiveRecord, any help/advice?
SELECT *
FROM notaries, contact_notaries
WHERE notaries.id = contact_notaries.notary_id
AND WHERE ( contact_notaries.city LIKE %$criteria%
OR contact_notaries.state LIKE %$criteria
OR contact_notaries.address LIKE %$criteria%)
Thanks!
Each query can have just one WHERE clause (you don't need the second)
It's much better to put join condition into JOIN rather then WHERE.
Are you sure you really need all the columns from 2 tables (*)?
So I'd refactor it to
SELECT [field_list]
FROM notaries
INNER JOIN contact_notaries ON (notaries.id = contact_notaries.notary_id)
WHERE ( contact_notaries.city LIKE '%$criteria%'
OR contact_notaries.state LIKE '%$criteria'
OR contact_notaries.address LIKE '%$criteria%')
Using a1ex07's query:
SELECT [field_list]
FROM notaries
INNER JOIN contact_notaries ON (notaries.id = contact_notaries.notary_id)
WHERE ( contact_notaries.city LIKE '%$criteria%'
OR contact_notaries.state LIKE '%$criteria'
OR contact_notaries.address LIKE '%$criteria%')
Active record:
$this->db->select(); // Leave empty to select all fields
$this->db->join('contact_notaries', 'notaries.id = contact_notaries.notary_id', 'inner');
$this->db->like('contact_notaries.city', 'criteria');
$this->db->like('contact_notaries.state', 'criteria');
$this->db->like('contact_notaries.address', 'match');
$results = $this->db->get('notaries');
To specify a list of fields you can do $this->db->select('field_1, field_2, ...');.
http://codeigniter.com/user_guide/database/active_record.html

What's wrong with this mySQL query?

Consider following two tables:
tag_names (tag_id, tag_name)
tag_links (tag_id, image_id)
An image can have multiple tags, I want to select all tags for a specific image id.
I am trying following query, but it doesnt seem to select correctly (selects only one row), What is wrong with it?
SELECT tag_name
FROM tag_names
LEFT JOIN tag_links.tag_id = tag_names.tag_id
WHERE tag_links.image_id = $image_id
Edit: I'm using CodeIgniter Active record query, but I wrote in basic SQL format so that if someone is not fimiliar with CodeIgniter can help. However, this query works fine with simple mysql format (without using CodeIgniter) but strangely does not work with CodeIgniter, even there is no any problem with the syntax, it just selects one row.
Here is CodeIgniter Syntax:
$this->db->select('tag_name');
$this->db->from('tag_names');
$this->db->join('tag_links', 'tag_links.tag_id = tag_names.tag_id', 'left');
$this -> db -> where('tag_links.image_id', (int)$image_id);
$query = $this->db->get();
Try this:
SELECT tag_name
FROM tag_names
LEFT JOIN tag_links
ON tag_links.tag_id = tag_names.tag_id
WHERE tag_links.image_id = $image_id
IMHO you forgot to join table (properly with ON statement) you are using.
EDIT: I have 2 ideas how to get rid of the problem:
First:
Change the line with SELECT
$this->db->select('tag_names.tag_name');
Second:
Use select() function with complete query:
$this->db->select($query, false);
$this->db->select() accepts an optional second parameter. If you set
it to FALSE, CodeIgniter will not try to protect your field or table
names with backticks. This is useful if you need a compound select
statement.
from: http://codeigniter.com/user_guide/database/active_record.html#select
It seems that you have a syntax error (you forgot tag_links in JOIN clause). By the way in my opinion you don't need LEFT JOIN for this purpose otherwise you may get incorrect results.
SELECT tag_name
FROM
tag_names
JOIN tag_links ON tag_links.tag_id = tag_names.tag_id
WHERE tag_links.image_id = $image_id
SELECT tag_names.tag_name
FROM tag_links
LEFT JOIN tag_names.tag_id = tag_links.tag_id
WHERE tag_links.image_id = $image_id
tag_names is only going to have single entry for a given ID, which means your query will return a single result. You need to primarily select from tag_links and then join the name of the tag on top of it, so you correctly select from the table with the multiple entries.

MySql - Best way to do this kind of query

I need to return a single row with some datas taken from some tables not related each others.
So, for example, my actual queries are these (I done it trought a PHP script) :
$query=mysql_query("SELECT trackid FROM tracklist WHERE usersub='".$_SESSION['nickname']."'",$mydb);
echo mysql_num_rows($query);
$query=mysql_query("SELECT trackid FROM comments WHERE usercom='".$_SESSION['nickname']."'",$mydb);
echo mysql_num_rows($query);
$query=mysql_query("SELECT vote FROM vote WHERE uservote='".$_SESSION['nickname']."'",$mydb);
echo mysql_num_rows($query);
$query = mysql_query("SELECT datereg FROM users WHERE nickname='".$_SESSION['nickname']."'",$mydb);
echo mysql_result($query,0,'datereg');
But this will call the MySql server 4 times.
Whats your suggestion to better this situation?
If the tables are not related then you will have to make 4 seperate calls
If the tables COULD be related by foreign keys then you could join them in some way and possibly cut down your sql calls
Ultimately though if you need all of the data then you'll have to request it from the database
You could use a UNION. And, btw, mysql_result is poor. And FFS don't forget to sanitize your inputs!
<?php
$nickname = mysql_escape_string($_SESSION['nickname']);
$sql = "
SELECT COUNT(trackid) AS n FROM tracklist WHERE usersub='{$nickname}'
UNION
SELECT COUNT(trackid) FROM comments WHERE usercom='{$nickname}'
UNION
SELECT COUNT(vote) FROM vote WHERE uservote='{$nickname}'
UNION
SELECT datereg FROM users WHERE nickname='{$nickname}'
";
$result = mysql_query($sql, $db);
while ($row = mysql_fetch_assoc($result)) {
echo $row['n'];
}
?>
I wouldn't really recommend this as it's a bit of a mess combining "count" values with a date in the same column, but you can do it. It's the direct answer to your question.
Well, you could create a fifth table and use it as an index.
If all the values { trackid, vote, datareg } are integers, the index table could contain three columns - nickname, value, and table. When you add records to one of the other tables, add a corresponding record to the index table.
For example,
INSERT INTO vote (vote, uservote, ...) VALUES (123, 'abc', ...);
INSERT INTO myindex (nickname, nvalue, ntable) VALUES ('abc', 123, 'vote');
(I wouldn't actually store the table name as a string but as a numeric value, but you get the idea)
Then on a query, you just SELECT nvalue, ntable FROM myindex WHERE nickname = 'abc';
You will possibly get more than one row.
I think that this is a lot of work and you are better off sticking with the four original queries.
Have you tried combining the select statement together like
SELECT .. Actually.
Maybe you should normalise your database and set up links between your tables...
Edit :: And i'm not sure how you're preparing yourself against mysql injection, but be careful with where your $_SESSION[] comes from
If all the selects return a single row:
$query=mysql_query("
(SELECT trackid FROM tracklist WHERE usersub='".$_SESSION['nickname']."'") as tracklist,
(SELECT trackid FROM comments WHERE usercom='".$_SESSION['nickname']."'") as trackid,
(SELECT vote FROM vote WHERE uservote='".$_SESSION['nickname']."'") as vote,
(SELECT datereg FROM users WHERE nickname='".$_SESSION['nickname']."'") as datereg
"