CREATE TABLE once but INSERT INTO twice - mysql

I have tried repeatedly to use a combination of "CREATE TABLE" and "ALTER TABLE...ADD" and "UPDATE..SET...WHERE" to produce an online survey which has two parts. Part one you add some user input and part two is just the same -- you input some values. It has become clear to me that, for whatever reason, when I use the above combination of SQL statements, the user input from the second portion of the questionnaire does NOT get fed into MySQL. The fields for the second part of the survey remain BLANK. I have literally tried it dozens of times. Failing everything else, I decided to CREATE TABLE with ALL the input text fields (in other words, I did not just the use the text fields from the first portion and then use ALTER TABLE to add columns from the second portion -- no -- as I mentioned, this does NOT work). What I tried instead was, on my first PHP file, I had:
$sql = "CREATE TABLE hollywoodmovies (ID INT(10), Year INT(10),
LeadActor VARCHAR(60), StudioName VARCHAR(20), PRIMARY KEY(ID))";
And then the second PHP file deals with the online survey part one:
$sql = "INSERT INTO hollywoodmovies (ID, Year, LeadActor, StudioName)
VALUES ('$uniqueid', '$year', '$actorname', '$studiolist')";
This is despite the fact that online survey part one has only two form fields: ID and YEAR
Now, the second portion of the survey, I did EXACTLY the same as the above:
$sql = "INSERT INTO hollywoodmovies (ID, Year, LeadActor, StudioName)
VALUES ('$uniqueid', '$year', '$actorname', '$studiolist')";
And this is despite the fact that I again have only two form fields for the second portion of the questionnaire/survey: LEADACTOR and STUDIONAME.
What is happening at run-time is that ALL desired fields ARE being allocated their respective values -- unlike when I used ALTER TABLE and UPDATE, at which time the input from the second portion of the survey went missing. So this is good -- up to a point. What is happening, however, is that I am getting TWO records (rows), not one. In the first record, the first two columns have user input appearing in them, but the last two are blank. And in the second row, the first two columns are blank, but the last two columns have data. Is there ANY way I can have just one record, and not two? (Would it be possible to use the word NULL somewhere to make the blanks disappear, giving me just one record?) Remember, my particular SQL server is VERY troublesome -- I cannot use ALTER or UPDATE or whatever else because my server just doesn't like it. I can only use CREATE in combination with "INSERT INTO...VALUES". Ay ideas, please?

INSERT will certainly create 2 rows, especially if you don't have a PK restraing on the ID column and are using that in both INSERT statements.
on your second statement to populate LEADACTOR and STUDIONAME do an UPDATE
e.g. UPDATE hollywoodmovies SET StudioName = $studioname, LeadActor = $leadactor WHERE ID = $uniqueid

Related

How to search in parts of entries within a mysql database?

I'm working with a database that contains some "special" columns:
There are "cells" of a table containing a single Name of e.g. a painter like "Turner, William"; some other contain an ID like "ID123" connected with a table of persons.
But some cells contain two or more entries, like: "ID123 ; Turner, William" - they are always separated by ";"
My question is:
Normaly I can use something like "SELECT - FROM - LEFT JOIN" for simple selections with one entry. Is there any possibility for working with more than one entry?
Something like
SELECT artwork.nameid, artwork.artist,
person.fullname
FROM artworks
LEFT JOIN person ON person.id = [Part of String artwork.artist]
One of the important principles of a relational database is that each column contains a single value, not a composite value like you describe. And the values in a given column have the same type, not variable types.
So you should solve your problem by having two columns, one for an ID and the other for a Name. Don't try to store them together in the same column with a semicolon separator.
CREATE TABLE artworks (
...
PersonID VARCHAR(5), -- example: ID123
Name VARCHAR(100), -- example: Turner, William
...
);
That said, you might be able to do what you describe using some MySQL string functions.
For example you can use LOCATE(';' artwork.artst) to detect if there's a semicolon present in a given string. You can use SUBSTRING_INDEX(artwork.artist, ';', 2) to extract the second "field" from a semicolon-separate field.
The expression needed to solve your problem is bound to become terribly complex if you need to handle a variety of cases, like what if a column has the ID first instead of second? What if there are three or more fields separated by semicolons?
Please take the recommendation that it will be far easier to restructure your table so you always have one value in each column.

#1109 - Unknown table 'ConcertDetails' in field list

I was attempting to run a simple Insert into my CustomerOrders table and got the error: #1109 - Unknown table 'ConcertDetails' in field list
I did some searches on this and looked at about 7 different stack overflow posts on it but still not sure what is wrong. I also looked up information on triggers and there seems to be different syntax on different sites. The weird thing is this trigger used to work just fine, not sure what has been altered since 4 days ago.
I tried changing some things, for instance I removed my trigger and it let me insert, but when I put the trigger back I couldn't insert any more, so there must be something wrong with the trigger. This is what I have for the trigger:
DROP TRIGGER IF EXISTS `alterPurchasePrice`;
DELIMITER //
CREATE TRIGGER `alterPurchasePrice` BEFORE INSERT ON `CustomerOrders`
FOR EACH ROW BEGIN
IF new.DiscountCode = 'yes' THEN
SET new.PurchasePrice = ConcertDetails.Cost - 10;
END IF;
END
//
DELIMITER ;
The purpose of the trigger is to lower the price by $10 if the user types 'yes' into the DiscountCode field.
This involves the tables:
CustomerOrders: ConcertID, CustomerName, Discount Code, OrderID,
PurchasePrice ConcertDetails: ConcertDate, ConcertID, Cost
I think you need a SELECT to retrieve values from other tables.
To get the value of "cost" from "ConcertDetails" table, for a specific concert, we could write a query like this:
SELECT ConcertDetails.cost
FROM ConcertDetails
WHERE ConcertDetails.concertid = ?
assuming that "concertid" is the primary key (or unique key) of the ConcertDetails table, we would be guaranteed that the query would return at most one row.
To put that to use in the trigger, we should be able to do something like this
SET NEW.PurchasePrice
= ( SELECT d.cost - 10 AS discount_price
FROM ConcertDetails d
WHERE d.concertid = NEW.concertid
);
The value of the concertid column of the row to be inserted from the CustomerOrders table will be supplied for the query through the reference to NEW.concertid. The return from that query will be assigned to the purchaseprice column.
If the query returns more than one row (which could happen if we don't have any kind of guarantee that "concertid" is unique in the "ConcertDetails" table), the trigger will throw a "too many rows" error.
If there are no rows returned, we'd expect a NULL value to be assigned would be assigned to "purchaseprice". We would also get a NULL returned if the "cost" column is set to NULL.
Is there some sort of guarantee that "cost" will never be less than 10? If the returned Cost is 6, then the value assigned to "purchaseprice" would be -4. If we want the value assigned to "purchaseprice" to never be less than zero, we could do something like this:
SET NEW.purchaseprice
= ( SELECT GREATEST(d.cost-10,0) AS discount_price
FROM ConcertDetails d
WHERE d.concertid = NEW.concertid
);
Other notes:
I'd recommend a different naming convention for your triggers. With multiple tables and multiple triggers, it can get kind of confusing, when looking for triggers on CustomerOrders table, to remember that the name of the BEFORE INSERT trigger is "alterPurchasePrice". Especially if you (or someone else) is coming back to work on the system six months or six years from now.
The convention we follow for trigger names is to use the name of the table, followed by an underscore, followed by one of: bi, bu, bd, ai, au, ad (for Before/After Insert/Update/Delete). (Since MySQL doesn't allow more than one trigger for each of those, we don't get naming collisions. And it makes it easier to check whether a BEFORE INSERT trigger exists on a table, before someone writes a BEFORE INSERT trigger that does something else.)
I also mention, in regards to the use of CamelCase table names... the MySQL Reference Manual says this:
To avoid problems ... it is best to adopt a consistent convention, such as always creating and referring to databases and tables using lowercase names. This convention is recommended for maximum portability and ease of use.
https://dev.mysql.com/doc/refman/5.7/en/identifier-case-sensitivity.html
Then again, these are just conventions. We also follow a convention to name tables by naming what a single row represents. If we had a requirement to create this table, we would assign the name customer_order. And the BEFORE INSERT trigger on the table would be named customer_order_bi

SQL to INSERT-DELETE disjoint rows into MySQL?

I have a food_table and a person_table. Then I have a third table fav_food_table that stores the relation between food and person using food_id and person_id.
When the person goes to account info and updates his favourite food, the input data is passed to the PHP (HTTP) script as an array of selected food_id. A person can have multiple fav_food, the relation is one-to-many.
The naïve way to update fav_food_table is to delete from fav_food_table all that belongs to person_id then re-insert all the rows again. Thus, using 2 statements.
Is there a single statement that can do the same thing?
PSUEDO CODE:
CREATE TABLE food_table (food_id, food_name);
CREATE TABLE person_table (person_id, person_name);
CREATE TABLE fav_food (person_id, food_id);
You have a m:n relationship between person_table and food_table. This means you have multiple records in your relationship table related to the same person. When a person updates their favourite foods, any combination of four independent cases can occur:
Food A was favourite before, but is not anymore (DELETE FROM fav_food_table)
Food B was favourite before and still is! (do nothing)
Food C was no favourite before, but now is a favourite (INSERT INTO fav_food_table)
Food D was no favourite before, and still is no favourite! (do nothing)
To correctly keep your database up to date, you have to handle all four cases. Cases 2 and 4 are covered easily. Just don't do anything :)
That means, you have to do at least two steps to keep your database up to date: 1 and 3.
Your goal seems to be to reduce the number of sql statements, that have to be executed.
Deleting all favourites for one person from the table can always be done with a single statement:
DELETE
FROM fav_food_table
WHERE person_id = ?;
To delete selected favourites for one person, only an AND food_id IN (?,?,?) has to be added to the WHERE clause.
Inserting into the table can also be done with a single statement:
INSERT INTO fav_food_table (person_id,food_id)
VALUES (?,?),
(?,?),
(?,?),
.....;
Summary as of right now:
No matter, whether we delete all old records for this person and then insert all new records, or whether we only delete selected records and insert new ones: We can do it with two statements!
In the second case (the "smart" case), however, we need to know not only the new state of the relation, but also the old state to compute the difference between the two. This will result in either one more SELECT statement or needs some smart "client" (PHP) logic.
Your two step process doesn't seem to be naive, but easy and effective to me. There is no way to reduce this process to less than two statements.
If you want to reduce the number of times you have to effectively send commands from PHP to the server, you can either look into mysqli_multi_query() or into creating a stored procedure which holds both your DELETE and INSERT statement. But bottom line, this will be the same thing as executing the two queries on its own.
You can also look into MySQL Transactions to implement a safer process and be able to rollback your DELETE command should an error occur later on.
May I suggest something different. Instead of deleting, updating?
Deleting data in common is something that should be thought off. Deleting data cannot be retrieved. So check this out.
We are adjusting your code a bit.
CREATE TABLE food_table (food_id, food_name);
CREATE TABLE person_table (person_id, person_name);
CREATE TABLE fav_food (person_id, food_id, fav_food_active);
/* see the last column i added. It should be a bit type and can only hold the values 0 and 1. */
Now you technically can update this everytime. No need for deleting the values. This statement is
/* deletion */
UPDATE Fav_Food
SET fav_food_active = 0
WHERE food_id = (your food_id)
AND person_id = (your person_id)
/* activating it again */
UPDATE Fav_Food
SET fav_food_active = 1
WHERE food_id = (your food_id)
AND person_id = (your person_id)
So now you switch between those 2 for activating and deleting it, without having the consequences of deleting hard data. Overal, you can just call it like this in your code
SELECT *
FROM fav_food
WHERE (here your where clause on which you wanna search for)
AND fav_food_active = 1
Remember when you enter something in the database, you should always add it as 1. You can do that in PHP myadmin as auto value, or hard code it in your INSERT statement.
I am not sure what your backend code is (okay taking that back, its PHP, i read your post) looking at your question you shooting everything through an array, but try to work in some checks that foreach fav_food entry, check first in the db if the query excists. If it does, update it, if not, insert it. and let that run in a loop.
So something like
foreach ($food_id as $value){
// check here the overal statement if it excists in your db
// give back count
if ($count == 1){
// update query to delete it.
}else{
// create your insert query here
}
}
Hope this helps. Happy coding!

Joining across 3000+ tables

Ok, strange one here
I have a database for customer data. My customers are businesses with their own customers.
I have 3000 tables (one for each business) with several thousand email addresses in each. Each table is identical, save the name.
I need to find a way to find where emails cross over between businesses (ie appear in multiple tables) and the name of the table that they sit in.
I have tried collating all entries and table names into one table and using a "group by", but the volume of data is too high to run this without our server keeling over...
Does anyone have a suggestion on how to accomplish this without running 3000 sets of joins?
Also, I cannot change the data structure AT ALL.
Thanks
EDIT: In response to those "helpful" restructure comments, not my database, not my system, I only started a couple of months ago to analyse the data
Multiple tables of identical structure almost never makes sense, all it would take is a business field to fix this structure. If at all possible you should fix the structure. If it has been foisted upon you and you cannot change it, you should still be able to work with it.
Select the distinct emails and the table name from each table either UNION ALL or pull them into a new table, then use GROUP BY and HAVING to find emails with multiple tables.
SELECT email
FROM Combined_Table
GROUP BY email
HAVING COUNT(sourc_table) > 1
So, you say you can't change the data structure, but you might be able to provide a compatible upgrade.
Provide a new mega table:
CREATE TABLE business_email (
id_business INT(10) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
PRIMARY KEY id_business, email
) ENGINE = MYISAM;
Myisam engine so you don't have to worry about transactions.
Add a trigger to every single business table to duplicate the email into the new one:
DELIMITER \\
CREATE TRIGGER TRG_COPY_EMAIL_BUSINESS1 AFTER INSERT OR UPDATE ON business1 FOR EACH ROW
BEGIN
INSERT INTO `business_email` (`id_business`, `email`) VALUES (NEW.`id_business`, NEW.`email`) ON DUPLICATE KEY UPDATE `id_business`=NEW.`id_business`;
END;
\\
DELIMITER ;
Your problem is to add it dynamically whenever a new table is created. It shouldn't be a problem since apparently there's already dynamic DDL in your application code.
Copy all existing data to the new table:
INSERT INTO `business_email` (`id_business`, `email`)
SELECT email FROM business1
UNION
SELECT email FROM business2
...
;
COMMIT;
proceed with your query on the new business_email table, that should be greatly simplified:
SELECT `id_business` FROM `business_email`
WHERE
GROUP BY `email`
HAVING COUNT(`email`) > 2;
This query should be easy to cope with. If not, please detail the issue as I don't think properly indexed tables should be a problem even for millions of rows (Which I don't believe is the case since we talk about emails)
The advantage of this solution is that you stay up to date all the time, while you don't change the way your application works. You just add another layer to provide additional business value.

Quickly Find Value in One Column, Change Value in Another

So I made a little mistake when I initially setup on of my Access tables and need help fixing it (as easily as possible).
I have one table called Locations (small table, 10s of rows) and another called Encounter Data (large table, 100,000s rows). The Encounter Data has a lookup column that points to Locations.
My mistake I made was to store the location name and not its primary key in the Encounter Data column. Now, obviously, if I make a change in the Locations table to the name of a particular item, the lookup in Encounter Data fails. What I should have down was store the Primary Key (then I can edit all the Locations information freely).
I am to fix that.
But I am not sure how to go about inserting the correct ID in a new column in place of the name.
My thought was to simply add a new column, create the lookup correctly (store the primary key), and then to either: [1] sort my Encounter Data by location and do a quick-drag/auto-fill like I would in excel to match up the correct IDs with the stored names or [2] do a find/replace ... only find a value in LocationWhoopsColumn and enter the correct ID in the LocationCorrectColumn.
But neither of these methods seems feasible. I have very few location but a lot of rows to fix ... how do I do this quickly? Manually changing row-by-row would take hours if not days.
Thanks!~
Create the new column LocationId in Encounter Data and then run
UPDATE [Encounter Data] a INNER JOIN Locations b ON a.LocationName = b.Name
SET a.LocationId = b.id
check the results and then drop column LocationName when satisfied.
"... find a value in LocationWhoopsColumn and enter the correct ID in the LocationCorrectColumn"
Use DLookup to retrieve the primary key value from Locations based on matching the location name field to LocationWhoopsColumn.
UPDATE YourBigTable
SET LocationCorrectColumn =
DLookup(
"pkey",
"Locations",
"location_name ='" & LocationWhoopsColumn & "'");
I guessed "pkey" and "location_name" as Locations field names; replace with your real names.
That UPDATE may not be fast, but hopefully that won't be a deal breaker since you need do this one time only.