How to relate subquery to outer query for update - mysql

Hello I have a database that looks something like this.
uniqueid description name phonenumber
66370 SALES John_Doe_Cell 555-5555
87296 SALES John_Doe_Home 555-4444
66786 ACCOUNTING Jane_Doe_Cell 555-3333
67897 ACCOUNTING Jane_Doe_Home 555-2222
I am trying to run a query that will pull phonenumber for %_Cell and transfer that phone number to %_Home. So for example in the table above I need John_Doe_Cell phonenumber to be put into John_Doe_Home phonenumber, same goes for Jane_Doe_Cell and Jane_Doe_Home.
The queue I have thus far is the following but I do not feel like it will work. I need to be able to pass the Name it finds during the lookup to be applied to %_Home so that it updates the correct name with the corrent phone number.
UPDATE `some_table` SET phonenumber=(SELECT phonenumber WHERE `name` LIKE '%_Cell')
WHERE queue_name LIKE '%_Home'

Your schema is odd, to say the least, and could stand to be normalized, but to answer your question as asked, your issue is two-fold.
The sub-select returns more than one result for the update, and
the sub-select result is not related to the UPDATE set.
From your data, let's try out the subselect:
> SELECT phonenumber WHERE `name` LIKE '%_Cell';
+-------------+
| phonenumber |
+-------------+
| 555-5555 |
| 555-3333 |
+-------------+
To update then, you need to both get your subselect to return 1 row, and you'll want to relate it to the outer query's row set. For example, this would work to give you one row for the subselect, but will give you incorrect data:
> UPDATE `some_table` AS upd SET phonenumber = (
SELECT phonenumber
FROM `some_table` AS inn
WHERE
`name` LIKE '%_Cell'
LIMIT 1
)
WHERE queue_name LIKE '%_Home';
What row will that inner query pick for each outer row? (Hint: try it and see, perhaps in a transaction so that you can rollback the results.) To connect the inner and outer query, I'm guessing this is what you might want:
> UPDATE `some_table` AS upd SET phonenumber = (
SELECT phonenumber
FROM `some_table` AS inn
WHERE
`name` LIKE '%_Cell'
AND inn.description = upd.description
)
WHERE queue_name LIKE '%_Home';
Note the inn.description = upd.description, which given the question's provided data, is the only piece of data that uniquely connects the rows you want.
Very generally (with definite exceptions), joining on text columns and other non-indexed fields is indicative of poor schema design. If this is for a serious project, I highly suggest you look into schema normalization to at least 3rd normal form. (I'll leave googling as an excercise to the reader.)

Related

SQL Query Will Time Out

I don't know how to stop my query from timing out. I have two tables. One with payment details including a postcode field. Example of payments table;
id (PRI) | company_name | amount | postcode
1 ACME 10000 AB1 1AA
2 Some Int. 15000 ZY9 8XW
The other table is a lookup table which assigns geographical regions to the postcodes. Example of postcode table;
postcode | country | county | local_authority | gor
AB1 1AA S99999 E2304 X 45
AB1 1AB S99999 E2304 X 45
So if a user does a search on country = S99999 it will return all the payments for that country.
The payments table has 40,000 rows. The postcode table has over 2,500,000. Even this really simple query will time out;
SELECT t1.company_name, t1.amount, t1.postcode, t2.country, t2.county, t2.local_authority, t2.gor
FROM `payments` as t1 LEFT JOIN `postcodes` AS t2 ON t1.`postcode` = t2.`postcode`
I have an INDEX on both postcode fields on both tables. I cannot manually add the lookup fields on to my payments table because their is a different lookup for each quarter.
I am limited in my experience here. I cannot think of alternatives or ways around this. Any ideas?
I'll make an answer from my comments as well:
I think your 'LEFT JOIN' is the killer. For now, change it into a normal 'JOIN'. Does it still time out? :)
And you might want to create a view with the query. The first time it will be slow but then the data will (probably) be available in a cache. But you might want to read the documentation of the DBMS about that. :)
Does a user always have to do a search to trigger this query? You might want to try this:
SELECT * FROM payments WHERE postcode IN (SELECT postcode FROM postcode WHERE (country='S99999'));
Edit: the nested query might take more memory. :)
It is not clear what DB you using, but in case of SQL server:
Limit the count of returned recors (TOP clause)
use hint NO LOCK to improve speed of the query
increase command timeout of your query, but no more that web page timeout
don't use LEFT JOIN, it is slow
use temp table (or in memory table), where to store you data from both tables and than just filter the data

MySql comma seperated values and using IN to select data

I store destinations a user is willing to ship a product to in a varchar field like this:
"userId" "destinations" "product"
"1" "US,SE,DE" "apples"
"2" "US,SE" "books"
"3" "US" "mushrooms"
"1" "SE,DE" "figs"
"2" "UK" "Golf Balls"
I was hoping this query would return all rows where US was present. Instead it returns only a single row.
select * from destinations where destinations IN('US');
How do I get this right? Am I using the wrong column type? or is it my query that's failing.
Current Results
US
Expected Results
US,SE,DE
US,SE
US
Try with FIND_IN_SET
select * from destinations where FIND_IN_SET('US',destinations);
Unfortunately, the way you've structured your table, you'll have to check for a pattern match for "US" in your string at the beginning, middle, or end.
One way you can do that is using LIKE, as follows:
SELECT *
FROM destinations
WHERE destinations LIKE ('%US%');
Another way is using REGEXP:
SELECT *
FROM destinations
WHERE destinations REGEXP '.*US.*';
Yet another is using FIND_IN_SET, as explained by Sadkhasan.
CAVEAT
None of these will offer great performance or data integrity, though. And they will all COMPOUND their performance problems when you add criteria to your search.
E.g. using FIND_IN_SET, proposed by Sadkhasan, you would have to do something like:
SELECT * FROM destinations
WHERE FIND_IN_SET('US',destinations)
OR FIND_IN_SET('CA',destinations)
OR FIND_IN_SET('ET',destinations);
Using REGEXP is a little better, though REGEXP is innately slow:
SELECT *
FROM destinations
WHERE destinations REGEXP '.*US|CA|ET.*';
SO WHAT NOW?
Your best bet would be switching to a 3NF design with destinations applying to products by splitting into 2 tables that you can join, e.g.:
CREATE TABLE products (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
userId INT NOT NULL REFERENCES users(id),
name VARCHAR(255) NOT NULL
) TYPE=InnoDB;
Then you would add what's called a composite key table, each row containing a productId and a single country, with one row per country.
CREATE TABLE product_destinations (
productId INT NOT NULL REFERENCES products(id),
country VARCHAR(2) NOT NULL,
PRIARY KEY (productId, country)
) TYPE=InnoDB;
Data in this table would look like:
productId | country
----------|--------
1 | US
1 | CA
1 | ET
2 | US
2 | GB
Then you could structure a query like this:
SELECT p.*
FROM products AS p
INNER JOIN product_destinations AS d
ON p.id = d.productId
WHERE d.country IN ('US', 'CA', 'ET')
GROUP BY p.id;
It's important to add the GROUP (or DISTINCT in the SELECT clause), as a single product may ship to multiple countries, resulting in multiple row matches - aggregation will reduce those to a single result per product id.
An added bonus is you don't have to UPDATE your countries column and do string operations to determine if the country already exists there. You can let the database do that for you, and INSERT - preventing locking issues that will further compound your problems.
You can use this if your destinations have just two caracters of the countries.
SELECT * FROM destinations WHERE destinations LIKE ('%US%')
to add other country
SELECT * FROM destinations WHERE destinations LIKE ('%US%')
AND destinations LIKE ('%SE%')
^^^--> you use AND or OR as you want the result.

Search MySql db with multiple "WHERE IN" and "OR" not working

Suppose I have a table like this:
Suppose my user inputs that he wants to see all records where gender is male AND eyecolor = grey.
I already have the following SQL for that:
SELECT User, question, answer FROM [Table] WHERE User IN (
SELECT User FROM [table] WHERE (question, answer) IN (
('gender', 'male'),
('eyecolor', 'grey')
)
)
GROUP BY User
HAVING count(distinct question, answer) = 2)
However, what if my user wants to see all records for (gender = male OR gender = female) AND eyecolor = grey ? How would I format the above sql query to get it to be able to find that?
(Keep in mind, this is a searchform, so eyecolor and gender are only a few fields used for searching; I need to be able to search with and/or combo's)
I'm thinking the only way I can get this to work is something like:
SELECT User
FROM [table]
WHERE (gender = male OR gender = female) AND eyecolor = blue
And my php would have to build the query so that if the user enters more fields, the query expands with more WHERE's etc.?
I have been searching all over but have not been able to get it to work.. Admittedly I'm not the world's greatest with this.
http://sqlfiddle.com/#!2/2e112/1/0
select *
from stuff
where ID in (
select ID
from stuff
where (question='gender' and answer in ('male','female')) or
(question='eyecolor' and answer='grey')
group by ID
having count(ID)=2
)
where 2 is the number of conditions in the nested where statement. If you run that nested select on its own, it will give you just a distinct list of ID's that fit the conditions. The outer statement allows the query to return all records for the ID's that fit those conditions.
i edited this because.... i was wrong before
k... http://sqlfiddle.com/#!2/2f526/1/0
select *
from stuff
where (question='gender' and answer in ('male','female')) or
(question='eyecolor' and answer='grey') or
(question='city' and answer in ('madrid','amsterdam'))
for this query, we return one row that matches any of those conditions for any ID. only ID's that satisfy at least one of those conditions will appear in the results.
select ID, count(*) as matches
from stuff
where (question='gender' and answer in ('male','female')) or
(question='eyecolor' and answer='grey') or
(question='city' and answer in ('madrid','amsterdam'))
group by ID;
then we add the group by, so we can see how many rows are returned for each user and how many conditions they met (count(*)).
select ID
from stuff
where (question='gender' and answer in ('male','female')) or
(question='eyecolor' and answer='grey') or
(question='city' and answer in ('madrid','amsterdam'))
group by ID
having count(ID)=3;
the having count(ID)=3; is what makes this query work. we only want ID's that had 3 rows returned because we have 3 conditions.
and.... we can't use and because no row in that table will ever meet more than one of those conditions at a single time. question cannot be gender, eyecolor and city all at the same time. it has to do with your table layout. city will never be both madrid and amsterdam at the same time.... and will give us nothing. so... by using the having and an or... we can do stuff that's happy...?
and to go on a tangent.... if your table looked like this:
ID gender eyecolor city
---------------------------------------------
100 male blue madrid
200 female grey amsterdam
300 male brown somewhere
you would use and because....
select *
from table
where gender in ('male','female') and
city in ('madrid','amsterdam') and
eyecolor = 'grey'
but your table is a special one and didn't want to go that way because you really shouldn't have a column for every question... what if they change or what if you add 20? that'd be hard to maintain.
and....
select ID
from stuff
where question in ('gender','eyecolor','city') and
answer in ('male','female','grey','madrid','amsterdam')
group by ID
having count(ID)=3;
does also work but i would really be cautious with that because.. the questions and answers should stay together and be explicit because.... what if it was a dating service? and male could be an answer for a person's own gender or the gender they want to date and by doing question='gender' and answer in ('male','female') you are specifying exactly what you mean and not assuming that certain information is only a valid answer for one question.

Selecting specific records to run query on

I am trying to select a small number of records in a somewhat large database and run some queries on them.
I am incredibly new to programming so I am pretty well lost.
What I need to do is select all records where the Registraton# column equals a certain number, and then run the query on just those results.
I can put up what the db looks like and a more detailed explanation if needed, although I think it may be something simple that I am just missing.
Filtering records in a database is done with the WHERE clause.
Example, if you wanted to get all records from a Persons table, where the FirstName = 'David"
SELECT
FirstName,
LastName,
MiddleInitial,
BirthDate,
NumberOfChildren
FROM
Persons
WHERE
FirstName = 'David'
Your question indicates you've figured this much out, but are just missinbg the next piece.
If you need to query within the results of the above result set to only include people with more than two children, you'd just add to your WHERE clause using the AND keyword.
SELECT
FirstName,
LastName,
MiddleInitial,
BirthDate,
NumberOfChildren
FROM
Persons
WHERE
FirstName = 'David'
AND
NumberOfChildren > 3
Now, there ARE some situations where you really need to use a subquery. For example:
Assuming that each person has a PersonId and each person has a FatherId that corresponds to another person's PersonId...
PersonId FirstName LastName FatherId...
1 David Stratton 0
2 Matthew Stratton 1
Select FirstName,
LastName
FROM
Person
WHERE
FatherId IN (Select PersonId
From Person
WHERE FirstName = 'David')
Would return all of the children with a Father named David. (Using the sample data, Matthew would be returned.)
http://www.w3schools.com/sql/sql_where.asp
Would this be any use to you?
SELECT * from table_name WHERE Regestration# = number
I do not know what you have done up to now, but I imagine that you have a SQL query somewhere like
SELECT col1, col2, col3
FROM table
Append a where clause
SELECT col1, col2, col3
FROM table
WHERE "Registraton#" = number
See SO question SQL standard to escape column names?.
Try this:
SELECT *
FROM tableName
WHERE RegistrationNo = 'valueHere'
I am not certain about my solution. I would propose You to use view. You create view based on needed records. Then make needed queries and then you can delete the view.
View description: A view contains rows and columns, just like a real table. The fields in a view are fields from one or more real tables in the database.
Example:
CREATE VIEW view_name AS
SELECT column_name(s)
FROM table_name
WHERE condition
For more information: http://www.w3schools.com/sql/sql_view.asp

MySQL Display Table Name Along With Columns

I'm currently debugging a huge MySql call which joins a large amount of tables which share column names such as id, created_at, etc. I started picking it apart but I was wondering if there was a way to do something like:
SELECT * AS table.column_name FROM table1 LEFT JOIN etc etc etc...
In place of having to individually name columns like:
SELECT table1.`column2' AS 'NAME', table1.`column3` AS ...
It would definitely help with speeding up the debugging process if there's a way to do it.
Thanks.
Edit:
Thanks for the answers so far. They're not quite what i'm looking for and I think my question was a bit vague so i'll give an example:
Suppose you have this setup in your MySql Schema:
table: students
fields: INT id | INT school_id | VARCHAR name
table: schools
fields: INT id | INT name
students contains:
1 | 1 | "John Doe"
schools contains:
1 | "Imaginary School One"
Doing the MySql call "SELECT * FROM students LEFT JOIN schools ON (students.school_id = schools.id)" will yield:
id | school_id | name | id | name
1 | 1 | "John Doe" | 1 | "Imaginary School One"
We know better and we know that the first Id and Name columns refer to the students table and the second Id and Name refer to the schools table since the data set is really small and unambiguous with its naming. However, if we had to deal with a result set that contained multiple left joins and columns with similar names, then it would start to get difficult to read and normally you'd have to trace through it by following the joins. We could start doing something like
SELECT school.name AS 'school_name', etc etc etc...
But that gets incredibly impractical once you start dealing with large data sets.
I was wondering though if there was a way to return the result set wherein the column names would look like this instead:
students.id | students.school_id | students.name | schools.id | schools.name
Which would be useful for future references if I need to do something similar again.
What if you select the tables in order, and add a spacer column with the name.
i.e.
select 'table1', t1.*, 'table2', t2.*, 'table3', t3.*
...
At least that way you don't have to name specific columns.
you mean something like?
show tables;
desc <tablename>;
If you want to also return table names along with column names, you can use the CONCAT function.
EXAMPLE:
SELECT CONCAT('tableName', field) FROM tableNAme
Let us know if this is what you are looking for.
Why not use the same dot notation instead of the ambiguous underscores for separating your tables from column names. Just enclose the alias in back-ticks. For example:
SELECT students.id `students.id` FROM students;