I'm restructuring some code, this is a PHP - MYSQL project. So i'm trying to update some tables in a single query or at least two.
Is this possible to achieve without Joins? What is the best choice ?
UPDATE document D SET D.status = 2,
CASE
WHEN D.type 'A' THEN (UPDATE table_1 SET active = 1 WHERE id_table1 = D.id_document)
WHEN D.type 'B' THEN (UPDATE table_2 SET active = 1 WHERE id_table2 = D.id_document)
WHEN D.type 'C' THEN (UPDATE table_3 SET active = 1 WHERE id_table3 = D.id_document)
END
WHERE D.id_document = %s
I don't mind to separate the first table " document D ", but I need the " case when " to update multiple tables in a single query because they are like 11 tables.
Are you trying to put it all in one query to improve performance? That won't help in this case, since it will take the same amount of time as it would if each subquery were executed separately.
You would get much cleaner code and better performance if you determined the table name in your php code and then passed that onto a much simpler query string.
Not to leave the question unanswered. Based on what I researched.
No, you can not do an "update" in a subquery.
No, you can not use the "case" like that.
The only thing that can be done is:
Use joins to update multiple tables
UPDATE table_1
INNER JOIN table_2 ON table_1.id = table_2.id
INNER JOIN table_3 ON table_3.id = table_3.id
SET table_1.column = '', table_2.column = ''
WHERE any condition;
And the "case" can only be used in a single way
UPDATE table SET
column = CASE columX WHEN 'A' THEN '2' ELSE column END,
column2 = CASE columX WHEN 'B' THEN '2' ELSE column2 END
Combining the two, I was able to achieve what I needed.
Remember that you can make different types of join and play with the "where". In the "case" clause, updating the column is mandatory, not optional, so I use the same column in "else" to leave it with the default value when I do not need to update that column.
Related
I have been struggling with a certain query all day and I decided to ask for help.
I need a query that checks an entire table row and all its table cells for a value 'yes' and then it needs to give me the names of the columns where this is true.
To clarify with an example:
in the table above I need to check the fruit row for all the cells with the value 'yes' and have the corresponding column names returned to me. In this case the result would be 'banana', 'apple' and 'pear'.
I hope that example helps.
-- pseudo code
SELECT `COLUMN_NAMES`
FROM `My_Table`
ROW `fruit`
WHERE `value` = 'yes';
I'd also like to note that I can't rearrange my database. The db structure can't be changed because we are already doing queries like SELECT 'property' WHERE 'Banana' = 'yes'(which would return 'fruit'). I need it to work the other way around too.
It sounds like you already have other queries that depend on this table structure, but I would still seriously consider changing the structure anyway, even if it meant rewriting some queries.
I assume the query Barmar wrote will work to get you what you want. It looks like it should, and from what I've seen, nothing is impossible for Barmar with SQL. But consider the effect this structure will have on the rest of your application.
What if you want to add an item? (Like another fruit or something.) Normally adding an item to a database would be done with an INSERT query. In this case you would have to use ALTER TABLE instead, and add a column. After you have done that, you would also have to alter the query that selects items that have a certain property (the one you're asking about here) to include the new column. You'd have to either change your application code any time you added an item, or come up with some way to dynamically include every column in the query.
What if you want to select items that have multiple properties? Obviously there aren't many liquid fruits, (unless you leave them in the fridge too long) but I assume those two aren't the only properties you're going to have. The CONCAT approaches will have trouble dealing with that. I'm sure there are ways to work around it, but you really are just working around a problem instead of fixing it.
Those are a couple of examples, but there are numerous other issues that will make this structure a real pain to deal with.
A less cumbersome way to deal with a many-to-many relationship like this is to use three tables. One for items, one for properties, and another to link items to their properties. Like this:
items properties item_properties
id item_name id name item_id property_id
1 banana 1 fruit 1 1
2 apple 2 liquid 2 1
3 pear 3 1
4 beef 5 2
5 water
Then you can select all the properties of an item:
SELECT p.name
FROM properties p
INNER JOIN item_properties ip ON p.id = ip.property_id
INNER JOIN items i on ip.item_id = i.id
WHERE i.name = 'banana'
Or all the items with a certain property:
SELECT i.name
FROM items i
INNER JOIN item_properties ip ON i.id = ip.item_id
INNER JOIN properties p ON ip.property_id = p.id
WHERE p.name = 'fruit'
I understand if you don't want to change your existing application at this point. But for anyone who comes across this in the future, I'd really recommend a structure more like this to begin with.
Use CONCAT_WS() to concatenate all the column names where the value is yes. NULL doesn't get included by CONCAT_WS(), so returning that for the no columns will leave them out of the result.
SELECT CONCAT_WS(',', IF(Banana = 'yes', 'Banana', NULL),
IF(Apple = 'yes', 'Apple', NULL),
IF(Pear = 'yes', 'Pear', NULL),
...) AS fruits
FROM yourTable
WHERE property = 'fruit';
i recommend you try to change the requirement to return 'banana, apple, pear'. if you can do that, then you can hard code if statements into your query in the following pattern:
select concat(
if (`banana`='yes', 'banana,', ''),
if (`apple` ='yes', 'apple,', ''),
if (`pear` ='yes', 'pear,', ''),
if (`beef` ='yes', 'beef,', ''),
if (`water` ='yes', 'water,', '')
) as properties
from table1
where ...
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'
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.
I try to use different columns within different tables.
Like I want it to run the query If or Where [table.column]
users.username = 'ExampleUsername' AND users.cardnumber = ''
I don't think I can use NULL instead of '', because its an empty text string?
users.cardnumber = NULL
Anyways, I couldn't come further as this:
INSERT INTO users (cardnumber, hasone)
WHERE users.username = 'ExampleName' AND users.cardnumber = ''
SELECT number, sethasone
FROM cards
WHERE cards.used = '0'
LIMIT 1
I'm a bit of new with SQL, but after I got it right I could put the code into my php script.
-
SOLVED! :
I've used two queries for each column.
update users
set hasone=(select sethasone from cards where used='0' LIMIT 1)
where username='TestUser'
and
update users
set cardnumber=(select number from cards where used='0' LIMIT 1)
where username='TestUser'
then I just deleted the row from cards and I was done.
delete from cards
where used = '1'
LIMIT 1
I gave the user a cardnumber from the table cards and delete that row in cards.
I think you are trying to write a nested query but you didn't know how to write it. If you want to write select query within insert or update query so before doing this Click here to read about sub-query or nested query.
Well, I think that you're trying to re-create a JOIN between 2 table. What you need to do is to add a "card_id" field into the users table. Then to get the user AND the card you can do something like :
SELECT * FROM users u LEFT JOIN cards c ON c.id = u.card_id
I can't seem to grasp how I can select records when the records of one user span multiple rows.
Here is the schema of the table.
user_id key value
------------------------------------------
1 text this is sample text
1 text_status 0
2 text this is sample text
2 text_status 1
from the above table/row you can see that each user has info that has multiple rows. So in this case how do I select say "All the IDs, text value where text_status is "1"?
And to complicate it 1 step further, I need the email address of these accounts which is on another table. How can I write 1 select statement to pull in the email address as well? I know there is a JOIN statement for this but it's a bit complicated for me especially I can't even figure out the first part.
Added Note I must state that this table schema is a Wordpress default table wp_usermeta..
SELECT t1.*
FROM tbl t2
INNER JOIN tbl t1 ON t1.user_id = t2.user_id
AND t1.key = 'text'
WHERE t2.key = 'text_status'
AND t2.value = '1'
I think you've set up your table incorrectly. Make text_status and value exist within the same row.
The way it is right now, you would have to conduct two queries to get to your end result. Where as, the correct way needs only one.
This arbitrary key:value list scheme is alluring because of its flexibility. But it complicates queries obviously. Depending on the structure of your second table you could get away with:
SELECT key, value FROM user_table WHERE user_id=123
UNION ALL
SELECT 'email' as key, email as value FROM email_table WHERE user_id=123
But that pretty much only returns a list still, not a set of fields.
key and value looks wrong. SQL already gives you "key" (in the column name) and multiple "values" (in the values given per column in each row).
You've designed your table in a way that contravenes the way Database Management Systems are designed to work, which is leading to your problem. Read about database normalization.
Ideally your table would look something like this:
user_id text_status text
------------------------------------------
1 0 this is sample text
2 1 this is sample text
Then your query looks like:
SELECT `user_id`, `text` FROM `table` WHERE `text_status` = '1';
As your table stands now, you'll need something like (untested):
SELECT `table`.*
FROM `table` LEFT JOIN
(SELECT `user_id`
FROM `table`
WHERE `key` = "text_status"
AND `value` = "1"
) AS `_` USING(`user_id`)
WHERE `table`.`key` = "text"