Combining multiple mysql queries into one result - php

I'm using php my get results from a mysql table. I want to run multiple conditional statements to return a list of unique results. let's say I have a table about houses on my street and my table looks like this:
House Number | Attribute | Value
-------------------------------
23 | Colour | White
23 | Stories | 2
24 | Stories | 1
25 | Colour | Blue
Notice house number 23 appears twice How would I word a mysql query to return all houses that are white AND have two stories? in this case, it would return just one result - 23.
I hear what you're saying - why don't i just make 'colour' and 'stories' the column names. well, the reason is because in my example, a house can have two different colours: two different values for the same attribute name. A house could have two rows, one where attribute is colour and value is white, and another where attribute is also colour but the value is purple. As long as a house has a row with colour:white AND a row with stories:2 it will return positive in the query and get included in the result
Now, once solution would be to run two different queries: one query that matches white houses and returns an array, and a second query that matches houses with two stories and returns an array, then I can use php to compare the two arrays and see what entries appear in both arrays, pull them out and put them into a final array. But this involves calling two mysql queries. Is there a way of combining the queries on the mysql end?

You want a self-join:
SELECT
A.`House Number` AS House
FROM
Houses AS A
INNER JOIN Houses AS B ON A.`House Number`=B.`House Number`
WHERE
A.Attribute='Colour' AND A.Value='White'
AND B.Attribute='Stories' AND B.Value='2'

You can nest your SELECT statements like this:
SELECT DISTINCT (`House_Number`) AS `House_Number`
FROM `table`
WHERE `House_Number`
IN (
SELECT DISTINCT (`House_Number`) AS `House_Number`
FROM `table`
WHERE `Attribute` = 'Colour'
AND `Value` = 'White'
)
AND `Attribute` = 'Stories'
AND `Value` = '2';
Edit:
Not quite as pretty as using an INNER JOIN, but still effective.
To build upon the INNER JOIN method #Eugen posted while I was typing up my original response, you may consider including DISTINCT, like this:
SELECT DISTINCT(A.`House_Number`) AS `House_Number`
FROM `table` AS A
INNER JOIN `table` AS B ON A.`House_Number` = B.`House_Number`
WHERE A.Attribute = 'Colour'
AND A.Value = 'White'
AND B.Attribute = 'Stories'
AND B.Value = '2'
The reason being that in case the same attribute were to be recorded twice, say like this:
House Number | Attribute | Value
-------------------------------
23 | Colour | White
23 | Colour | White
23 | Stories | 2
24 | Stories | 1
25 | Colour | Blue
...then you would wind up with "23" being returned twice, unless you used DISTINCT

Try this
select id from table
where Attribute='Colour' and Value='White'
and id in (select id from table where Attribute='Stories' and Value='2')

Related

Displaying MySQL rows on a JOIN query even when records do not exist in another table

I have an PHP/MySQL application which is connected to a database featuring 2 tables called 'displays' and 'display_substances'. The structure of these tables is as follows:
mysql> DESCRIBE displays;
+----------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------------+------+-----+---------+----------------+
| id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| label | varchar(255) | NO | | NULL | |
+----------+----------------------+------+-----+---------+----------------+
mysql> DESCRIBE display_substances;
+--------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------------+------+-----+---------+----------------+
| id | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| display_id | smallint(5) unsigned | NO | MUL | NULL | |
| substance_id | mediumint(8) unsigned | NO | MUL | NULL | |
| value | text | NO | | NULL | |
+--------------+-----------------------+------+-----+---------+----------------+
There is also a 'substances' table and the foreign key display_substances.substance_id is associated with substances.id.
The 'displays' table contains exactly 400 rows and the 'display_substances' approx 1.5 million rows.
What I'm trying to do is output all 400 'displays.label' into a HTML table, and then in a second column show the 'display_substances.value'. Since the page only shows 1 substance at a time it also needs to be based on the 'display_substances.substance_id'.
The issue I'm having is that records only exist in 'display_substances' when we have data available for the appropriate 'displays'. However, the output has to show all records from 'displays' and then put the text "Not Listed" next to anything where there is no corresponding record in 'display_substances'.
I've done the following - which gives me the output I want - but is flawed (see "The Problem" section below).
Select all of the records in the "displays" table: SELECT label FROM displays ORDER BY label ASC
Select all display_substances.display_id for the substance currently being shown: SELECT display_id FROM display_substances WHERE substance_id = 1 (assuming 1 is the current substance ID). Store the ID's in an array called $display_substances
Loop through (1) and use in_array() to see if (2) exists:
foreach ($displays as $display) { // step (1)
$display_substances = // See step (2)
if (in_array($display['id'], $display_substances)) { // step (3)
$display_value = // See step (4)
} else {
$display_value = 'Not listed';
}
$output[] = ['display_label' => $display['label'], 'display_value' => $display_value]; // See step (5)
}
If the in_array() condition is true then I make a query to select the corresponding row of "display_substances": SELECT value FROM display_substances WHERE display_id = $display['id'] AND substance_id = 1
The $output variable buffers all the data and then it gets output into a HTML table later. The output I get is exactly as I want.
The problem
Although the output is correct I want to do this all as 1 query (if possible) because I need to add features to search by either displays.label or display_substances.value - or a combination of both. The first part of this is fairly trivial because I can amend the query in (1) to:
SELECT label FROM displays WHERE label LIKE '% Foo %' ORDER BY label ASC
However, this won't make display_substances.value searchable because by the time we get to step (3) we're dealing with a single row of display_substances not the whole table. I can't see how to write it differently though since we need to know which records exist in that table for the loaded substance.
I have also written the following query - but this will not work because it misses anything that's "Not Listed":
SELECT displays.label, display_substances.`value` FROM displays JOIN display_substances ON displays.id = display_substances.display_id WHERE display_substances.substance_id = 1
I have read How do I get the records from the left side of a join that do not exist in the joined table? but that didn't help.
For further clarification
Let's say there are 120 rows in display_substances that correspond to substance ID 1 (WHERE display_substances.substance_id = 1). The output of the query should always have 400 rows. In this example, 120 should have display_substances.value next to them, and 280 should have the text "Not Listed".
You need a left join & a group_concat to get all records on the left table along with group by.
But keep in mind that group_concat has a limit so you might not get all associated records, as it's usually used for small fields but since you have a 'text' field for your value there's a high probability you'd hit the limit
Anyway here's the query
SELECT d.*, GROUP_CONCAT(ds.value) `substances`
FROM displays `d`
LEFT JOIN display_substances `ds` ON `d`.`id` = `ds`.`display_id`
GROUP BY `d`.`id`
Something like this might work then if I understand correctly
SELECT d.*, IFNULL((SELECT GROUP_CONCAT(value) FROM display_substances `ds` WHERE `ds`.`display_id` = `d`.`id` GROUP BY `ds`.`display_id`), 'Not Listed') `substances`
FROM displays `d`
You can update the where & add AND substance_id = 1
My understanding is that you want the following:
Your page limits the results by substance ID
You want one substance per row
If there are displays with no substances, they should still show in the page with "Not Listed" as the substance value
I believe this should work for you:
SELECT
d.id AS display_id,
d.label AS display_label,
IFNULL(ds.value, 'Not Listed') AS substance_value
FROM displays AS d
LEFT JOIN display_substances AS ds ON (ds.display_id = d.id)
WHERE ds.substance_id = 1 OR ds.substance_id IS NULL;
I've realized that Ramy has about 98% of the solution.
FWIW this problem is just a variation on one that occurs all the time.
You will find other answers on SO when you search for 'left outer join with where clause' -- that address the problem. One example is this question.
Ultimately, you have a many to many resolution table (display_substances) that resolves the many to many relationship between substances and displays. You are just looking for an outer join from one of the 2 parent tables, but also requiring that you filter the results by a specific substance.
SELECT
d.id AS display_id,
d.label AS display_label,
IFNULL(ds.value, 'Not Listed') AS substance_value
FROM displays AS d
LEFT JOIN display_substances AS ds ON (ds.display_id = d.id AND ds.substance_id = 1);
This query does not generate a value of 'not listed' but it does generate NULL columns for those display rows where there is no corresponding display_substance value. You could embelish it with the IF_NULL() function demonstrated by ahmad, but as you are using PHP to go through the result set, you can just as easily handle that in the procedural loop you'll use to fetch the results.
I'm posting what I've used as a solution. I don't think it's possible to do this in MySQL as one query. Several people answered but none of the answers worked.
For even further clarification - although obvious from the question - the output in the application is a table with 2 columns. The first of these columns should include all 400 rows from displays:
displays.label | display_substances.value
------------------|--------------------------
Display 1
------------------|--------------------------
...
------------------|--------------------------
Display 400
------------------|--------------------------
This is fairly trivial since at this point it's just SELECT * FROM displays.
The challenge begins when we want to populate the second column of the table with display_substances.value. The data for a given substance (assume substance ID is 1) might look like this:
id | display_id | substance_id | value
-----|----------------|-----------------|-------------
206 | 1 | 1 Foo
-----|----------------|-----------------|-------------
361 | 3 | 1 Bar
-----|----------------|-----------------|-------------
555 | 5 | 1 Baz
-----|----------------|-----------------|-------------
The problem: In this case we only have 3 records for substance ID 1. So if we do a JOIN query, it will only return 3 rows. But the table we are displaying in the application needs to show all 400 rows from displays and put the text "Not listed" on any row where there is no corresponding row in display_substances. So in the example above, when it encounters display_id 2, 4, 6...400 it should say "Not listed" (because we only have data for three display_substances.display_id [1,3,5] for substance ID 1).
Both columns also need to be searchable.
My solution
I don't think it's possible to do this in MySQL so I resorted to using PHP. The logic is as now follows:
If the user is doing a search on column 2 (display_substances.value): SELECT display_id FROM display_substances WHERE value LIKE '% Search term column 2 %'. Store this as an array, $ds.
Select all 400 records from displays. If the user is performing a search on column 1 (displays.label) then that must form part of the query: SELECT * FROM displays WHERE label LIKE '% Search term column 1 %'. Critically - if the $ds array from step (1) is not empty then the following must become part of query: WHERE displays.id IN (2, 4, 6...400). Store this as an array, $displays
Get all of the display_id's associated with the substance being viewed: SELECT display_id FROM display_substances WHERE substance_id = 1
Do a loop as per point (3) of the original question.
The result is that the page loads in <2 seconds, each column is searchable.
The SQL queries given in answers took - at best - around 15-20 seconds to execute and never gave all 400 rows.
If anyone can improve on this or has a pure SQL solution please post.

MySQL search with word distance across neighboring records

I am performing a keyword text search across static records in a MySQL database. Is it possible to construct a query that finds the first keyword in a record and the second keyword in a neighboring record? Consider the following sample data.
------------------------------------------------------
| id | textstrings |
------------------------------------------------------
| 1 | Every good boy does fine. |
| 2 | The quick brown fox jumped over the lazy dog. |
| 3 | I will not eat green eggs and ham. |
| 4 | There is no time like the present. |
| 5 | Envy is an ugly shade of green. |
------------------------------------------------------
A search for the terms green brown should return records 2 and 3 since they are neighboring records, but should not contain record 5 since it is not a neighbor with record 3.
I know that I can perform a query for either of the words and accomplish this by processing the result set, but I would like to know if it's possible to build this into the query.
This field does have a FULLTEXT index.
You can join together two queries, one which will search for ids containing the first search string (green in this example), and the other which will search for ids containing the second search string (brown here).
The SELECT statement is structured the way it is to remove duplicate adjacent id pairs which may occur as a result of the WHERE clause.
SELECT LEAST(t1.id, t2.id) AS id1, GREATEST(t1.id, t2.id) AS id2
FROM
(
SELECT id
FROM table
WHERE textstrings LIKE '%green%'
) t1
INNER JOIN
(
SELECT id
FROM table
WHERE textstrings LIKE '%brown%'
) t2
ON t1.id = t2.id - 1 OR t1.id = t2.id + 1
GROUP BY LEAST(t1.id, t2.id), GREATEST(t1.id, t2.id)
Click the link below for a running demo.
SQLFiddle

MySQL Duplicating Row Results in Table When Searching Across Multiple Tables

I have three tables in a mySQL db one for users, one for colours and one for the linking of the users to the colours
Table 1 users
==============
ID | Name
--------------
1 | John
2 | Jayne
3 | Fred
Table 2 colours
==============
ID | Colour
--------------
1 | Blue
2 | Red
3 | Yellow
Table 3 link
==============
ID | Name | Colour
--------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
4 | 3 | 2
5 | 3 | 3
As you can see, some users have more than one favourite colour (yeah, i know, how annoying).
At the moment, I can show them in a table, with their favourite colour(s) in a column.
BUT, I want to be able to filter the table results by colour.
I can do it no problem with having a filter of just one colour, BUT the problem comes along with two colours.
If I want to see which user has selected for example Blue AND Red, I get a result of zero.
How can I get this result, without creating a search which results in each row being dedicated to a colour and then in turn showing the same user twice (one for red one for blue).
I hope this makes sense
THANKS IN ADVANCE
EDIT
An example query I have used is
SELECT * FROM users, colours, link WHERE users.id = link.name AND link.colour = colours.id
Alternatively to show for specific colour
SELECT * FROM users, colours, link WHERE users.id = link.name AND link.colour = colours.id AND link.colour = 1
But for double filter which shows duplicates
SELECT * FROM users, colours, link WHERE users.id = link.name AND link.colour = colours.id AND link.colour = 1 OR link.colour = 2
If that looks right here is the code:
SELECT Name FROM users
WHERE ID IN (SELECT DISTINCT(Name) AS Name
FROM link L
WHERE 2 IN (SELECT Colour FROM link L2 WHERE L.Name = L2.Name)
AND 1 IN (SELECT Colour FROM link L2 WHERE L.Name = L2.Name))
And now let me try to explain what L and L2 are... First sorry for my English I'll do my best to make a sense for you...
We make subquery on the same table here so we need to use alias for the table. Alias we use to give temporary name table or column which will be used only for that query.
Example for alias is when we select some column from table (Price and Quantity) and let's say we want to calculate Price * quantity and SELECT that column as total (total will be the name of that column in table which we return after we execute the query). Column name total well be give alias. we crate alias like:
SELECT Price, Quantity, (Price * Quantity) AS **total**
FROM t1...
That will return table with three column Price, Quantity, Total... if we don't use this AS total the name of that column will be Price * Quantity...
So here we use L and L2 just to know which column Name is from which part of SELECT query. If we wouldn't use alias in subquery
SELECT Colour FROM link L2 WHERE L.Name = L2.Name
we would have problem because subquery which locks like this:
SELECT Colour FROM link WHERE Name = Name
Doesn't make a a lot of sense, isn't it?
So basically we temporary rename table in this query because we need to know which column from which table we compere whit other one, in other way database will have a problem what to select...
I hope this make a sense for you. If you have any further question fill free to ask I will do my best to try to explain it to you.
I hope i didn't make it more complicated than it is...
GL!
EDIT
Hi there again, i worked something and and i figured out that your question probably have better answer than the first i give you... Hope it's not too late!
SELECT u.Name
FROM users u
INNER JOIN link L
ON u.ID = L.Name
INNER JOIN link l2
ON L.Name = L2.Name
WHERE L.Colour = 2 AND L2.Colour = 1
Look SQL Fiddle for that...

Select Query not work with where in

I have two table
one table is alldata ( here info_id is a text field data inserted using php )
=================
id | info_id
=================
1 | 2, 3, 5, 9
2 |
=================
second table is info
=================
id | name
=================
1 | one
2 | two
3 | three
4 | four
5 | five
6 | six
7 | seven
9 | eight
9 | nine
=================
now I want to select list of data from table two where data id will be matched with table one first item info_id data
my query is
SELECT i.* FROM `info` as i,`alldata` as a where i.id IN(a.info_id) and a.id=1
my query works but select only one item from table two.But there are multiple matched.
You have a very poor database design. First, storing numeric ids as strings is a bad idea -- numbers should be stored as numbers. Second, SQL offers this great data structure for storing lists. It is called a table, not a string.
You should really have a junction table, one one row per id and info_id.
That said, sometimes we a struck with substandard data structure. MySQL offers support for this. You can use:
SELECT i.*
FROM `info` i JOIN
`alldata` a
ON FIND_IN_SET(i.id, REPLACE(a.info_id, ', ', ',') ) > 0
WHERE a.id = 1;
You should also learn to use proper, explicit join syntax. If you use this method, instead of fixing the database design, you are not allowed to complain about performance. MySQL cannot take advantage of things like indexes to improve the performance of this type of query.

How to implement SQL statement to create either/or functionality

SQL statement question. Let's say I have the following in my mini-table. Let's say the table is "images."
ID | file_name |fruit_type |
================================
1 | ex.jpg | apple |
2 | am.png | apple |
3 | pl.jpeg | orange |
4 | e.png | orange |
5 | yep.png | apple |
OK. So when I call this, I want it to pull two pictures, and each should be a random selection from within a fruit_type. So apples should only be picked with apples. Oranges should only be picked with oranges. There should be no combination of both types.
Here's where it breaks down. Let's say the code I run is this:
$query="SELECT * FROM images WHERE fruit_type='apple'
OR fruit_type='orange' ORDER BY RAND() LIMIT 0,2";
This would return two random selections, like I want. However, this captures BOTH apple and orange types from that column.
I have been trying to identify an SQL statement that would allow it to either choose apples or oranges, but never both. I've come up short, so I'm turning to you all.
You can create two queries, first for apples, second for oranges and select random apple and random orange, then join them using UNION ALL clause.
The following will work, but it won't be a great option from a performance point of view if your table gets huge (millions of records). The below query takes all combinations of images that have the same fruit_type excluding matching images (you don't want the same image twice, do you?).
SELECT images1.*, images2.*
FROM images images1, images images2
WHERE images1.id != images2.id
AND images1.fruit_type = images2.fruit_type
ORDER BY RAND()
LIMIT 0,2
Since you don't have windowing functions in MySQL, you have to improvise. This post is adapted from this blog post by Quassnoi:
SELECT
distinct
#fi AS file_name,
fruit_type
FROM (
SELECT m.*
FROM (
SELECT #_f = NULL
) vars,
images m
ORDER BY
fruit_type, rand()
) mo
WHERE (CASE WHEN #_f IS NULL OR #_f <> fruit_type THEN #fi := file_name ELSE file_name END IS NOT NULL)
AND (#_f := fruit_type) IS NOT NULL

Categories