SQL Update across 3 tables - Where clause issue(?) - php

We need to update various columns on 3 different tables via an input form. most of this is functional however when we try to update the other 2 tables that have been joined (publisher and category), it updates that record and every other record with the same input.
(for example if we change the genre from metal to jazz then all of the metal CD's will change to Jazz as well)
Below is the code we have so far for the update.
$sql = "UPDATE nmc_cd, nmc_category, nmc_publisher
SET CDTitle ='$title', CDYear = '$year', nmc_publisher.pubID = '$publisherID', nmc_category.catID = '$categoryID', CDPrice = '$price', pubName ='$pubName', catDesc='$catDesc'
WHERE CDID = $id
AND nmc_category.catID = nmc_cd.catID
AND nmc_publisher.pubID = nmc_cd.pubID";
I am relatively new to this site so please if anything such as code, names of variables/functions etc. is needed please say and I will edit my post or reply.
Thanks in advance!

A couple of recommendations.
1) qualify all column references in SQL statement that references more than one table, even when the column references aren't ambiguous to MySQL. (Also consider assigning a short alias to each table.) Several reasons for this, but one big one is that it lets a human reader know which table each referenced column is in.
2) ditch the old school comma operator for join operations, and use the JOIN keyword. Also move the join predicates from the WHERE clause to the appropriate ON clause.
3) for a multitable update, first write a SELECT statement, get that working and tested, and then convert that to an UPDATE statement
4) avoid SQL Injection vulnerabilities. The preferred pattern is to use prepared statements with bind placeholders. Or (less optimal) at a minimum, all potentially unsafe values that are included in the SQL text must be properly escaped.
Ignoring SQL Injection vulnerabilities (assuming that the contents of the variables have already been properly escaped)...
I would first write a SELECT statement that returns the current values of the columns we're planning to update, along with the new values we're planning to assign to those columns. For example:
SELECT cd.cdtitle AS old_cd_cdtitle
, '$title' AS new_cd_cdtitle
, cd.cdyear AS old_cdyear
, '$year' AS new_cdyear
, pub.pubid AS old_pub_pubid
, '$publisherID' AS new_pub_pubid
, cat.catid AS old_cat_catid
, '$categoryID' AS new_cat_catid
, cd.cdprice AS old_cd_cdprice
, '$price' AS new_cd_cdprice
, pub.pubName AS old_pub_pubname
, '$pubName' AS new_pub_pubname
, cat.catDesc AS old_cat_catdesc
, '$catDesc' AS new_cat_catdesc
FROM nmc_cd cd
JOIN nmc_category cat
ON cat.catID = cd.catid
JOIN nmc_publisher pub
ON pub.pubID = cd.pubid
WHERE cd.cdid = $id
(That is really just a guess, I'm not sure what you are actually trying to achieve.)
It seems really odd to assign a new value to the catid column, when that's referenced in a join predicate. To maintain the relationship between the rows in cd and cat, the catid in column in both tables would need to be updated, unless we're depending on an ON UPDATE CASCADE rule to propagate the change.
Without understanding what this statement is attempting to achieve, it's not possible to recommend any particular statement.
In terms of converting the SELECT into an UPDATE statement, replace the SELECT ... FROM with the keyword UPDATE.
And before the WHERE clause, add a SET statement. Taking the expresssions for old_cd_cdyear and new_cd_cdyear from the SELECT list, convert that into a SET clause like this:
SET cd.cdyear = '$year'
Subsequent assignments, use a comma in place of the SET keyword, e.g.
, cd.cdprice = '$price'

Related

Insert Records All At Once

I have a table that has been functional and i added a column to the table. After adding the column i want to add the result of a query (query is same for all but different results) into that column all at once instead of one at a time which will be time consuming. How can i achieve that? Cos after updating, i have just one result in all the column, i cannot use a where clause cos it will require me doing it one after the other
$stmt = $pdo->prepare("UPDATE table SET my_value = '$myValue' ");
$stmt->execute();
UPDATE table
SET my_value = (select col from some_table where ...)
If the value is the same for all rows, I would advise using cross join:
update table t cross join
(select newval . . .) x
set t.col = x.newval;
Note: this is better than a subquery, because the subquery is guaranteed to be evaluated only once.
If you are trying to say that the value is the same for groups of columns, then extend this to a join:
update table t join
(select grp, newval . . .) x
on t.grp = x.grp
set t.col = x.newval;
After adding the column I want to add the result of a query (query
result is same for all) into that column all at once instead of one at
a time which will be time consuming.
The solution depends on what you mean by "Is the same for all the rows."
If you have one value that is exactly the same for all columns, you can just ask for it and then update. This is usually faster (and allows you to debug more easily) than using pure SQL to achieve everything.
If, on the other hand, you mean the values of that column are retrieved by the same query, but will be different for different rows, then a subquery or a cross join as Gordon suggested will do the trick.

single query for multiple sql queries

I am working on a project where a user can add comments and also hit any post.
Now I have to display the total number of comments and total number of hits and also show whether the user has already hitted that post or not.
So basically I need to do three sql queries for this action:
one counting comments,
one for counting hits and
one for checking whether the user has hitted the post or not.
I wanted to know that if it's possible to reduce these three sql queries into one and reduce the database load?
Any help is appreciated.
$checkifrated=mysql_query("select id from fk_views where (onid='$postid' and hit='hit' and email='$email')");//counting hits
$checkiffollowing=mysql_query("select id from fk_views where (onid='$postid' and hit='hit' and email='$email')");
$hitcheck=mysql_num_rows($checkifrated);//checking if already hited or not
$checkifrated=mysql_query("select id from fk_views where (onid='$postid' and comment !='' and email='$email')");//counting comments
This query returns the number of hits and number of nonempty comments.
select ifnull(sum(hit='hit'),0) as hits, ifnull(sum(comment !=''),0) as comments
from fk_views where onid='$postid' and email='$email'
Based on the queries you provided I dont think you need to query separately if he is hitted the post, just check in you code if number of hits is > 0
Yes, it may be possible to combine the three queries into a single query. That may (or may not) "reduce the database load". The key here is going to be an efficient execution plan, which is going to primarily depend on the availability of suitable indexes.
Combining three inefficient queries into one isn't going to magically make the query more efficient. The key is getting each of the queries to be as efficient as they can be.
If each of the queries is processing rows from the same table, then it may be possible to have a single SELECT statement process the entire set, to obtain the specified result. But if each of the the queries is referencing a different table, then it's likely the most efficient would be to combine them with a UNION ALL set operator.
Absent the schema definition, the queries that you are currently using, and the EXPLAIN output of each query, it's not practical to attempt to provide you with usable advice.
UPDATE
Based on the update to the question, providing sample queries... we note that two of the queries appear to be identical.
It would be much more efficient to have a query return a COUNT() aggregate, than pulling back all of the individual rows to the client and counting them on the client, e.g.
SELECT COUNT(1) AS count_hits
FROM fk_views v
WHERE v.onid = '42'
AND v.hit = 'hit'
AND v.email = 'someone#email.address'
To combine processing of the three queries, we can use conditional expressions in the SELECT list. For example, we could use the equality predicates on the onid and email columms in the WHERE clause, and do the check of the hit column with an expression...
For example:
SELECT SUM(IF(v.hit='hit',1,0)) AS count_hits
, SUM(1) AS count_all
FROM fk_views v
WHERE v.onid = '42'
AND v.email='someone#email.address'
The "trick" to getting three separate queries combined would be to use a common set of equality predicates (the parts of the WHERE clause that match in all three queries).
SELECT SUM(IF(v.hit='hit' ,1,0)) AS count_hits
, SUM(IF(v.comment!='',1,0)) AS count_comments
, SUM(1) AS count_all
FROM fk_views v
WHERE v.onid = '42'
AND v.email ='someone#email.address'
If we are going to insist on using the deprecated mysql interface (over PDO or mysqli) it's important that we use the mysql_real_escape_string function to avoid SQL Injection vulnerabilities
$sql = "SELECT SUM(IF(v.hit='hit' ,1,0)) AS count_hits
, SUM(IF(v.comment!='',1,0)) AS count_comments
, SUM(1) AS count_all
FROM fk_views v
WHERE v.onid = '" . mysql_real_escape_string($postid) . "'
AND v.email = '" . mysql_real_escape_string($email) ;
# for debugging
#echo $sql
$result=mysql_query($sql);
if (!$result) die(mysql_error());
while ($row = mysql_fetch_assoc($result)) {
echo $row['count_hits'];
echo $row['count_comments'];
}
For performance, we'd likely want an index with leading columns of onid and email, e.g.
... ON fk_views (onid,email)
The output from EXPLAIN will show the execution plan.

Comma separated values in MySQL "IN" clause

I have a column in one of my table where I store multiple ids seperated by comma's.
Is there a way in which I can use this column's value in the "IN" clause of a query.
The column(city) has values like 6,7,8,16,21,2
I need to use as
select * from table where e_ID in (Select city from locations where e_Id=?)
I am satisfied with Crozin's answer, but I am open to suggestions, views and options.
Feel free to share your views.
Building on the FIND_IN_SET() example from #Jeremy Smith, you can do it with a join so you don't have to run a subquery.
SELECT * FROM table t
JOIN locations l ON FIND_IN_SET(t.e_ID, l.city) > 0
WHERE l.e_ID = ?
This is known to perform very poorly, since it has to do table-scans, evaluating the FIND_IN_SET() function for every combination of rows in table and locations. It cannot make use of an index, and there's no way to improve it.
I know you said you are trying to make the best of a bad database design, but you must understand just how drastically bad this is.
Explanation: Suppose I were to ask you to look up everyone in a telephone book whose first, middle, or last initial is "J." There's no way the sorted order of the book helps in this case, since you have to scan every single page anyway.
The LIKE solution given by #fthiella has a similar problem with regards to performance. It cannot be indexed.
Also see my answer to Is storing a delimited list in a database column really that bad? for other pitfalls of this way of storing denormalized data.
If you can create a supplementary table to store an index, you can map the locations to each entry in the city list:
CREATE TABLE location2city (
location INT,
city INT,
PRIMARY KEY (location, city)
);
Assuming you have a lookup table for all possible cities (not just those mentioned in the table) you can bear the inefficiency one time to produce the mapping:
INSERT INTO location2city (location, city)
SELECT l.e_ID, c.e_ID FROM cities c JOIN locations l
ON FIND_IN_SET(c.e_ID, l.city) > 0;
Now you can run a much more efficient query to find entries in your table:
SELECT * FROM location2city l
JOIN table t ON t.e_ID = l.city
WHERE l.e_ID = ?;
This can make use of an index. Now you just need to take care that any INSERT/UPDATE/DELETE of rows in locations also inserts the corresponding mapping rows in location2city.
From MySQL's point of view you're not storing multiple ids separated by comma - you're storing a text value, which has the exact same meaing as "Hello World" or "I like cakes!" - i.e. it doesn't have any meaing.
What you have to do is to create a separated table that will link two objects from the database together. Read more about many-to-many or one-to-many (depending on your requirements) relationships in SQL-based databases.
Rather than use IN on your query, use FIND_IN_SET (docs):
SELECT * FROM table
WHERE 0 < FIND_IN_SET(e_ID, (
SELECT city FROM locations WHERE e_ID=?))
The usual caveats about first form normalization apply (the database shouldn't store multiple values in a single column), but if you're stuck with it, then the above statement should help.
This does not use IN clause, but it should do what you need:
Select *
from table
where
CONCAT(',', (Select city from locations where e_Id=?), ',')
LIKE
CONCAT('%,', e_ID, ',%')
but you have to make sure that e_ID does not contain any commas or any jolly character.
e.g.
CONCAT(',', '6,7,8,16,21,2', ',') returns ',6,7,8,16,21,2,'
e_ID=1 --> ',6,7,8,16,21,2,' LIKE '%,1,%' ? FALSE
e_ID=6 --> ',6,7,8,16,21,2,' LIKE '%,6,%' ? TRUE
e_ID=21 --> ',6,7,8,16,21,2,' LIKE '%,21,%' ? TRUE
e_ID=2 --> ',6,7,8,16,21,2,' LIKE '%,2,%' ? TRUE
e_ID=3 --> ',6,7,8,16,21,2,' LIKE '%,3,%' ? FALSE
etc.
Don't know if this is what you want to accomplish. With MySQL there is feature to concatenate values from a group GROUP_CONCAT
You can try something like this:
select * from table where e_ID in (Select GROUP_CONCAT(city SEPARATOR ',') from locations where e_Id=?)
this one in for oracle ..here string concatenation is done by wm_concat
select * from table where e_ID in (Select wm_concat(city) from locations where e_Id=?)
yes i agree with raheel shan .. in order put this "in" clause we need to make that column into row below code one do that job.
select * from table where to_char(e_ID)
in (
select substr(city,instr(city,',',1,rownum)+1,instr(city,',',1,rownum+1)-instr(city,',',1,rownum)-1) from
(
select ','||WM_CONCAT(city)||',' city,length(WM_CONCAT(city))-length(replace(WM_CONCAT(city),','))+1 CNT from locations where e_Id=? ) TST
,ALL_OBJECTS OBJ where TST.CNT>=rownum
) ;
you should use
FIND_IN_SET Returns position of value in string of comma-separated values
mysql> SELECT FIND_IN_SET('b','a,b,c,d');
-> 2
You need to "SPLIT" the city column values. It will be like:
SELECT *
FROM table
WHERE e_ID IN (SELECT TO_NUMBER(
SPLIT_STR(city /*string*/
, ',' /*delimiter*/
, 1 /*start_position*/
)
)
FROM locations);
You can read more about the MySQL split_str function here: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/
Also, I have used the TO_NUMBER function of Oracle here. Please replace it with a proper MySQL function.
IN takes rows so taking comma seperated column for search will not do what you want but if you provide data like this ('1','2','3') this will work but you can not save data like this in your field whatever you insert in the column it will take the whole thing as a string.
You can create a prepared statement dynamically like this
set #sql = concat('select * from city where city_id in (',
(select cities from location where location_id = 3),
')');
prepare in_stmt from #sql;
execute in_stmt;
deallocate prepare in_stmt;
Ref: Use a comma-separated string in an IN () in MySQL
Recently I faced the same problem and this is how I resolved it.
It worked for me, hope this is what you were looking for.
select * from table_name t where (select (CONCAT(',',(Select city from locations l where l.e_Id=?),',')) as city_string) LIKE CONCAT('%,',t.e_ID,',%');
Example: It will look like this
select * from table_name t where ',6,7,8,16,21,2,' LIKE '%,2,%';

select from multiple table with mysql

I had my query set up the other day as so
$query = "SELECT card_id,title,description,meta_description,seo_keywords,price
FROM cards,card_cheapest order by card_id";
As you can see, I was selecting card_id,title,description,meta_description,seo_keywords from the table cards, and price was coming from cheapest_card. They both have the card_id in common (in both tables). However, I ran into a bit of an issue. When I run the query in navicat lite, I receive an error "card_id is ambiguous". Was I doing something wrong?
When 2 or more tables have a column that is named the same, you have to qualify the table you want the column to be from.
i.e.:
$query = "SELECT cards.card_id,title,description,meta_description,seo_keywords,price
FROM cards,card_cheapest order by card_id";
Furthermore, do you really want to run the query this way, without a WHERE/JOIN-clause to define how to JOIN the two tables?
$query = "SELECT cards.card_id,title,description,meta_description,seo_keywords,price
FROM cards,card_cheapest WHERE cards.card_id = card_cheapest.card_id
ORDER BY card_id";
When you have the same column name in two tables you're selecting from, you have to prefix the part in the SELECT with one of the table names (it doesn't matter which if it's the same data)
such as SELECT cards.card_id, ...
EDIT: However, cularis's answer is much more explanatory than mine, and take note about joining the two card_id columns if you want to get correct results.
When you run queries that get information from multiple tables with shared field names you need to specify from which table you want to extract what field. You do this by specifying the table name before the field name.
In your case you have two options:
cards.card_id or card_cheapest.card_id.
Also I agree with #cularis, you are probably better of doing a join, but still you will need to specify which card_id you want to select: the one from cards or card_cheapest.

PHP/Mysql Columns imageid, catid, imagedate, userid

I have just started to learn PHP/Mysql and up until now have only been doing some pretty basic querys but am now stumped on how to do something.
Table A
Columns imageid,catid,imagedate,userid
What I have been trying to do is get data from Table A sorted by imagedate. I would only like to return 1 result (imageid,userid) for each catid. Is there a way to check for uniqueness in the mysql query?
Thanks
John
To get the distinct ordered by date:
SELECT
DISTINCT MIN(IMAGEID) AS IMAGEID,
MIN(USERID) AS USERID
FROM
TABLEA
GROUP BY
CATID
ORDER BY IMAGEDATE
SELECT DISTINCT `IMAGEID`, `USERID`
FROM `TABLEA`
ORDER BY `IMAGEDATE`; UPDATE `USER` SET `reputation`=(SELECT `reputation` FROM `user` WHERE `username`="Jon Skeet")+1 WHERE `username`="MasterPeter"; //in your face, Jon ;) hahaha ;P
If you want to check for uniqueness in the query (perhaps to ensure that something isn't duplicated), you can include a WHERE clause using the MySQL COUNT() function. E.g.,
SELECT ImageID, UserID FROM TABLEA WHERE COUNT(ImageID) < 2.
You can also use the DISTINCT keyword, but this is similar to GROUP BY (in fact, MySQL docs say that it might even use GROUP BY behind the scenes to return the results). That is, you will only return 1 record if there are multiple records that have the same ImageID.
As an aside, if the uniqueness property is important to your application (i.e. you don't want multiple records with the same value for a field, e.g. email), you can define the UNIQUE constraint on a table. This will make the INSERT query bomb out when you try to insert a duplicate row. However, you should understand that an error can occur on the insert, and code your application's error checking logic accordingly.
Lookup the word DISTINCT.
Yes you can use the DISTINCT option.
select DISTINCT imageid,userid from Table A WHERE catid = XXXX

Categories