MYSQL - Help with a more complicated Query - mysql

I have two tables:
tbl_lists and tbl_houses
Inside tbl_lists I have a field called HousesList - it contains the ID's for several houses in the following format:
1# 2# 4# 51# 3#
I need to be able to select the mysql fields from tbl_houses WHERE ID = any of those ID's in the list.
More specifically, I need to SELECT SUM(tbl_houses.HouseValue) WHERE tbl_houses.ID IN tbl_lists.HousesList -- and I want to do this select to return the SUM for several rows in tbl_lists.
Anyone can help ?
I'm thinking of how I can do this in a SINGLE query since I don't want to do any mysql loops (within PHP).

If your schema is really fixed, I'd do two queries:
SELECT HousesList FROM tbl_lists WHERE ... (your conditions)
In PHP, split the lists and create one array $houseIDs of IDs. Then run a second query:
SELECT SUM(HouseValue) FROM tbl-Houses WHERE ID IN (.join(", ", $houseIDs).)
I still suggest changing the schema into something like this:
CREATE TABLE tbl_lists (listID int primary key, ...)
CREATE TABLE tbl_lists_houses (listID int, houseID int)
CREATE TABLE tbl_houses (houseID int primary key, ...)
Then the query becomes trivial:
SELECT SUM(h.HouseValue) FROM tbl_houses AS h, tbl_lists AS l, tbl_lists_houses AS lh WHERE l.listID = <your value> AND lh.listID = l.listID AND lh.houseID = h.houseID
Storing lists in a single field really prevents you from doing anything useful with them in the database, and you'll be going back and forth between PHP and the database for everything. Also (no offense intended), "my project is highly dynamic" might be a bad excuse for "I have no requirements or design yet".

normalise http://en.wikipedia.org/wiki/Database_normalization

Related

Summing-up For an Arbitrary List of Rows

I wish I could write something like this:
SELECT sum(weight) FROM items WHERE itemID IN (4217,4575,6549,4217)
where itemID is the primary key, and SQLite and MySQL would process it as expected, i.e. add the 4217-th row to the sum twice, but they don't.
is there something at least in MySQL I'm not aware of that's intended for cases like this? if not, what would a workaround be like? any row can have any number of duplicates in the list. the list can be big although in most cases is small.
I don't know any out of the box functionality that can do this. Here is the workaroud. You can insert those values into temp table and use a join:
CREATE TEMPORARY TABLE List(ID INT);
INSERT INTO List(ID) VALUES(4217),(4575),(6549),(4217);
SELECT SUM(i.weight)
FROM items i
JOIN List l ON i.itemID = l.ID;

Two-way partial search using SQL

I have a PHP snippet that looks up a MySQL table and returns the top 6 closest matches, both exact as well as partial, against a given search string. The SQL statement is:
SELECT phone, name FROM contacts_table WHERE phone LIKE :ph LIMIT 6;
Using the above example, if :ph is assigned, say, %981% it would return every entry that contains 981, e.g. 9819133333, +917981688888, 9999819999, etc. However, is it also possible to return all entries whose values are contained within the search string using the same query? Thus, if the search string is 12345, it would return all of the following:
123456789 (contains the search string)
88881234500 (contains the search string)
99912345 (contains the search string)
123 (is contained within the search string)
45 (is contained within the search string)
2345 (is contained within the search string)
You can do a lookup where the number is LIKE the column:
SELECT * FROM `test`
WHERE '123456' LIKE CONCAT('%',`stuff`,'%')
OR `stuff` LIKE '%123456%';
An index will never be used, though, because an index cannot be used with a preceding %.
An alternate way to do it would be to create a temporary table in memory and insert tokenized strings and use a JOIN on the temporary table. This will likely be much slower than my solution above, but it is a potential option.
You can try the option of dynamic SQL:
SELECT
phone
FROM
contacts_table
WHERE
phone LIKE :ph or
phone = :val1 or
phone = :val2 or
phone = :val3 or
phone = :val4 or
phone = :val5 (so on a so forth)
LIMIT 6;
Where :ph will be your regular input (e.g. %981%) and valX is going to be tokenize input.
It would be good idea if you do the tokenizing smartly (say if input is of length 5 then go for token size of 3 or 4). Try to limit the number of tokens to get better performance.
DEMO
If you using PHP then do something like:
foreach ($phone as getPhoneNumberTokens($input)) {
if ($phone != "") {
$where_args[] = "phone = '$phone'";
}
}
$where_clause = implode(' OR ', $where_args);
You could use three tables. I don't actually know how performant it will be, though. I didn't actually insert anything to test it out.
contact would contain every contact. token would contain every valid token. What I mean is that when you insert into contact, you would also tokenize the phone number and insert every single token into the token table. Tokens would be unique. Kay. So, then you would have a relation table which will contain the many<->many relationship between contact and token.
Then, you would would get all contacts that have tokens that match the input phone number.
Table definitions:
CREATE TABLE contact (id int NOT NULL AUTO_INCREMENT, phone varchar(16), PRIMARY KEY (id), UNIQUE(phone));
CREATE TABLE token (id int NOT NULL AUTO_INCREMENT, token varchar(16), PRIMARY KEY (id), UNIQUE(token));
CREATE TABLE relation (token_id int NOT NULL, contact_id int NOT NULL);
The query:
There might be a better way to write this query (maybe by using a subquery rather than so many joins?), but this is what I came up with.
SELECT DISTINCT contact_list.phone FROM contact AS contact_input
JOIN relation AS relation_input
ON relation_input.contact_id = contact_input.id
JOIN token AS all_tokens
ON all_tokens.id = relation_input.token_id
JOIN relation AS relation_query
ON relation_query.token_id = all_tokens.id
JOIN contact AS contact_list
ON contact_list.id = relation_query.contact_id
WHERE contact_input.phone LIKE '123456789'
Query Plan:
However, this is with no data actually in the database, so the execution plan could change if data were present. It looks promising to me, because of the eq_ref and key usage.
I also made an SQL Fiddle demonstrating this.
Notes:
I didn't add any indexes. You could probably add some indexes and
make it more performant... but indexes might not actually help in
this instance, since you aren't querying over any duplicated rows.
It might be possible to add compiler hints or use LEFT/RIGHT Joins to improve query plan execution. LEFT/RIGHT Joins in the wrong place could break the query, though.
as it currently stands, you'd have to insert the queried number into the contact database and tokenize it and insert into relation and token prior to querying. Instead, you could use a temporary table for the queried tokens, then do JOIN temp_tokens ON temp_tokens.token = all_tokens.token... Actually, that's probably what you should do. But I'm not gonna re-write this answer right now.
Using integer columns for phone and token would perform better, if that is a valid option for you.
An alternate way to do it, which would be better than inserting all the tokens into the table just for a query would be to use an IN (), like:
SELECT DISTINCT contact.phone FROM token
JOIN relation
ON relation.token_id = token.id
JOIN contact
ON relation.contact_id = contact.id
WHERE token.token IN ('123','234','345','and so on')
And here is another, improved fiddle: http://sqlfiddle.com/#!9/48d0e/2

Subquery results more than one values

I have two tables:
table_people
col_name
col_sex
table_gender
col_male
col_female
Suppose the table_people consist three rows, (a,'M'),(b,'M'),(c,'F').
Now I need a query(subquery) to insert this first table value in second table as:
(a,c),(b,'').
If it is possible in mysql?
You table structure is bad.
My suggestion is to drop table_gender because it doesn't make sense at all. You already have a list of person with gender on table table_people.
You can generate a VIEW, if you want to list the gender separately.
CREATE VIEW MaleList
AS
SELECT col_name
FROM table_people
WHERE col_sex = 'M'
and another view for list of females only.
CREATE VIEW FemaleList
AS
SELECT col_name
FROM   table_people
WHERE  col_sex = 'F'
First of all the table structure is bad. I dont see a point of saving M or F in another table when you col_sex in your table_people. Still if you want to do it, first you have to specify a foreign key connecting the two tables.

MYSQL join tables based on column data and table name

I'm wondering if this its even posible.
I want to join 2 tables based on the data of table 1.
Example table 1 has column food with its data beeing "hotdog".
And I have a table called hotdog.
IS it possible to do a JOIN like.
SELECT * FROM table1 t join t.food on id = foodid
I know it doesnt work but, its even posible, is there a work arround?.
Thanks in advance.
No, you can't join to a different table per row in table1, not even with dynamic SQL as #Cade Roux suggests.
You could join to the hotdog table for rows where food is 'hotdog' and join to other tables for other specific values of food.
SELECT * FROM table1 JOIN hotdog ON id = foodid WHERE food = 'hotdog'
UNION
SELECT * FROM table1 JOIN apples ON id = foodid WHERE food = 'apples'
UNION
SELECT * FROM table1 JOIN soups ON id = foodid WHERE food = 'soup'
UNION
...
This requires that you know all the distinct values of food, and that all the respective food tables have compatible columns so you can UNION them together.
What you're doing is called polymorphic associations. That is, the foreign key in table1 references rows in multiple "parent" tables, depending on the value in another column of table1. This is a common design mistake of relational database programmers.
For alternative solutions, see my answers to:
Possible to do a MySQL foreign key to one of two possible tables?
Why can you not have a foreign key in a polymorphic association?
I also cover solutions for polymorphic associations in my presentation Practical Object Oriented Models In SQL, and in my book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.
Only with dynamic SQL. It is also possible to left join many different tables and use CASE based on type, but the tables would be all have to be known in advance.
It would be easier to recommend an appropriate design if we knew more about what you are trying to achieve, what your design currently looks like and why you've chosen that particular table design in the first place.
-- Say you have a table of foods:
id INT
foodtype VARCHAR(50) (right now it just contains 'hotdog' or 'hamburger')
name VARCHAR(50)
-- Then hotdogs:
id INT
length INT
width INT
-- Then hamburgers:
id INT
radius INT
thickness INT
Normally I would recommend some system for constraining only one auxiliary table to exist, but for simplicity, I'm leaving that out.
SELECT f.*, hd.length, hd.width, hb.radius, hb.thickness
FROM foods f
LEFT JOIN hotdogs hd
ON hd.id = f.id
AND f.foodtype = 'hotdog'
LEFT JOIN hamburgers hb
ON hb.id = f.id
AND f.foodtype = 'hamburger'
Now you will see that such a thing can be code generated (or even for a very slow prototype dynamic SQL on the fly) from SELECT DISTINCT foodtype FROM foods given certain assumptions about table names and access to the table metadata.
The problem is that ultimately whoever consumes the result of this query will have to be aware of new columns showing up whenever a new table is added.
So the question moves back to your client/consumer of the data - how is it going to handle the different types? And what does it mean for different types to be in the same set? And if it needs to be aware of the different types, what's the drawback of just writing different queries for each type or changing a manual query when new types are added given the relative impact of such a change anyway?

Querying for multiple many-to-many associates in MySQL

Background
My program is storing a series of objects, a set of tags, and the many-to-many associations between tags and objects, in a MySQL database. To give you an idea of the structure:
CREATE TABLE objects (
object_id INT PRIMARY KEY,
...
);
CREATE TABLE tags (
tag_name VARCHAR(32) NOT NULL
);
CREATE TABLE object_tags (
object_id INT NOT NULL,
tag_name VARCHAR(32) NOT NULL,
PRIMARY KEY (object_id, tag_name)
);
Problem
I want to be able to query for all objects that are tagged with all of the tags in a given set. As an example, let's say I have a live tree, a dead flower, an orangutan, and a ship as my objects, and I want to query all of those tagged living and plant. I expect to receive a list containing only the tree, assuming the tags match the characteristics of the objects.
Current Solution
Presently, given a list of tags T1, T2, ..., Tn, I am solving the problem as follows:
Select all object_id columns from the object_tags table where tag_name is T1.
Join the result of (1) with the object_tags table, and select all object_id columns where tag_name is T2.
Join the result of (2) with the object_tags table again, and select all object_id columns where tag_name is T3.
Repeat as necessary for T4, ..., Tn.
Join the result of (4) with the objects table and select the additional columns of the objects that are needed.
In practice (using Java), I start with the query string for the first tag, then prepend/append the string parts for the second tag, and so on in a loop, before finally prepending/appending the string parts that make up the overall query. Only then does the string actually get passed into a PreparedStatement and get executed on the server.
Edit: Expanding on my example from above, using this solution I would issue the following query:
SELECT object_id FROM object_tags JOIN (
SELECT object_id FROM object_tags WHERE tag_name='living'
) AS _temp USING (object_id) WHERE tag_name='plant';
Question
Is there a better solution to this problem? Although the number of tags is not likely to be large, I am concerned about the performance of this solution, especially as the database grows in size. Furthermore, it is very difficult to read and maintain the code, especially when the additional concerns/constraints of the application are thrown in.
I am open to suggestions at any level, although the languages (MySQL and Java) are not variables at this point.
I don't know about the performance of this solution, but you can simplify by using pattern matching in MySql to match a set of pipe-delimited tags (or any delimiter). This is a solution I've used before for similar applications with tag tables (#match would be a variable passed in by your Java code, I've harded coded a value for demonstration):
set #match = 'living|plant';
set #numtags =
length(#match) - length(replace(#match, '|', '')) + 1;
select * from objects o
where #numtags =
(
select count(*) from object_tags ot
where concat('|',#match,'|')
like concat('%|',ot.tag_name,'|%')
and ot.object_id = o.object_id
)
Here is a working demo: http://sqlize.com/0vP6DgQh0j