How to implement SQL statement to create either/or functionality - php

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

Related

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

LIMIT result on join MySQL

take the case you have 2 table, for example tbCostumers and tbOrders.
I would like to display a summary list with all costumers, related orders and display them with a paginator.
Doing a join I can extract costumers list and all orders for each costumer, the result is something like:
idCostumer | name | ... | idProduct | productName | price | ...
Where the first n columns are all equal if the costumer has more than 1 order. So I can have:
1 | will | ... | 12 | product1 | 123 | ...
2 | bill | ... | 23 | product2 | 321 | ...
2 | bill | ... | 24 | product3 | 231 | ...
And so on
I'm trying to use LIMIT to extract only n records and using them with a paginator.
First question: if a costumer has more than 1 order, with this query I'll see n records, equal in the first column (id, name, ... and other costumer info) but different at the end, where there are products info. Is this 'correct'? Is there another way to extract this informations?
Second question: if I do that and I use a LIMIT, I could "cut" the result table between 2 (or more) records that represent the same customer; so, for example in the small table above, if I limit with 2 the third row will be lost, even if it's part of the row above, because is just another order of the same costumer.
I would like to limit the number of different idCostumer, in order to take exactly n costumers, even if they appear more than 1 times in the result table. Something like n different idCostumer, no matter if they are repeated.
Is this possible?
I hope it's clear, it was not easy to explain what I would like to achieve :)
Thank you!
You might want to have something like this:
SELECT * FROM (
(SELECT * FROM tbCustomers LIMIT 3) AS c
INNER JOIN tbOrders AS o ON o.customer = c.idcustomer
);
You can substitute the first asterisk with named columns and only receive your desired columns in the order you prefer (ie: SELECT c.name, o.price FROM...) .
Hope this works for you!
EDIT: changing the value of the LIMIT clause changes the number of the picked customers, of course.
EDIT 2: As Alvaro Pointed out, you'll probably need an order clause in the tbCustomers query.

Combining multiple mysql queries into one result

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')

Pull records in order of most unique occurrences of dynamic array?

I am trying to pull the most relevant jobs based on a user defined keywords list on my website. So as a user, if I specify the following keywords:
builder
bricks
concrete
I want to work out how to search all jobs in the database that have at least one of these, but order them by the jobs that contains all three of these words.
My database table is as follows -
job_id INT
job_title VARCHAR
job_description TEXT
So I want to check job_description field and if it finds all 3 of these keywords it orders this at the top, then those jobs where 2 of the 3 are in job_description somewhere then 1.
Horrible hack, but with some client-side processing of the source array, you can dynamically build a query that'd look like:
SELECT
LOCATE('red', your_text_field) +
LOCATE('green', your_text_field) +
etc...
LOCATE('purple', your_text_field) AS color_count
FROM ...
ORDER BY color_count DESC
If a particular color doesn't exist, the LOCATE returns 0 and doesn't contribute to the sum.
I would use a full text search for the first part of your problem, the second part for ranking on unique occurrences is a bit harder.
Example:
SELECT SQL_CALC_FOUND_ROWS
something_tbl.*,
MATCH(something_tbl.field_1, something_tbl.field_2)
AGAINST (:keywords) AS score
FROM something_tbl
WHERE MATCH(something_tbl.field_1, something_tbl.field_2)
AGAINST (:keywords IN BOOLEAN MODE)
ORDER BY score DESC
Without giving more details on how your table structure looks like, this is only vagely answerable.
However, consider using a fulltext index, if your data is based on string-like datatypes.
Basic example. your_field is in a fulltext index.
+----+-------------------------------------------------------------+
| id | your_field |
+----+-------------------------------------------------------------+
| 1 | red |
| 2 | green red |
| 3 | black red |
| 4 | yellow red green blue orange |
| 5 | black blue |
+----+-------------------------------------------------------------+
And now the SQL:
SELECT *,
MATCH (your_field)
AGAINST ('+yellow +red +green +blue +orange' IN BOOLEAN MODE) AS 'val'
FROM yourtable
WHERE MATCH (your_field)
AGAINST ('+yellow +red +green +blue +orange' IN BOOLEAN MODE)
ORDER BY val DESC;
More information can be found here in the manual.

How to count and limit record in a single query in MYSQL?

I am searching for records in a table as follows:
SELECT Id, Name FROM my_table WHERE Name LIKE '%prashant%' LIMIT 0, 10;
Now, I am adding LIMIT to maintain my paging. But when user searches for word 'prashant' then total records I have is 124 for 'prashant'. But as the limit applied to the query so it only fetches 10 records in my PHP script and when I am count the mysql variable in PHP code it returns total records found is 10.
So basically I want to count and Limit using a single query, by making some modification in the above query, I want the total count (124) of records. I don't want to run a separate count(*) query for just counting the total result found by the query.
Thanks.
SELECT SQL_CALC_FOUND_ROWS
Id, Name
FROM my_table
WHERE
Name LIKE '%prashant%'
LIMIT 0, 10;
# Find total rows
SELECT FOUND_ROWS();
more info
MySQL supports doing this using SQL_CALC_FOUND_ROWS as mentioned by Ionut. However, it turns out that in many cases it's actually faster to do it the old fashioned way using two statements, where one of them is a regular count(). This does however require that the counting can be done using an index scan, which won't be the case for this very query, but I thought I'd mention it anyway.
This is for others with the same need (considering it's been 3 years from the time of this question).
I had a similar issue and I didn't want to create 2 queries. So what I did was to create an additional column for the total number and moved the LIMIT and OFFSET clauses at the end:
SELECT SQL_CALC_FOUND_ROWS * FROM (
SELECT `id`, `name`
FROM `my_table`
WHERE `name` LIKE '%prashant%'
) res,
(SELECT /*CEIL(FOUND_ROWS()/10) AS 'pages',*/ FOUND_ROWS() AS 'total') tot
LIMIT 0, 10
So the result is something like
| id | name | total |
+-----+----------------+-------+
| 1 | Jason Prashant | 124 |
| 2 | Bob Prashant | 124 |
| 3 | Sam Prashant | 124 |
| 4 | etc. prashant | 124 |
and I think this solution has no disadvantage in timing because it fetches the result only once, and then uses the already calculated row count for the additional column.
In case of huge tables and selecting multiple fields (not just Id, Name as in your example) i would recommend to use 2 queries. Selecting count(0) first with all those WHERE clauses and only then build the pagination, selecting data etc.
It will work really faster on some popular eCommerce website, for example.
Don't use SQL_CALC_FOUND_ROWS and FOUND_ROWS()
there are the bugs reported
here: https://bugs.mysql.com/bug.php?id=18454
and here: https://bugs.mysql.com/bug.php?id=19553
while on small tables BENCHMARK is dependent more on other things and the resulting time your SELECT will take will be roughly the same as COUNT(0) - SQL_CALC_FOUND_ROWS still puts restraints on LIMIT and ORDER BY database optimizations so if you depend on them don't use SQL_CALC_FOUND_ROWS
on large tables the BENCHMARK difference becomes huge where a COUNT(0) might take 0.003 sec the same SQL_CALC_FOUND_ROWS might now take 20 sec
By all metrices COUNT(0) is the superior choice
COUNT(0) SYNTAX:
(SELECT COUNT(0) FROM tableNames WHERE condition) AS alias
// alias is optional but needed if you need to use the result later
So your total query would look like this
(SELECT COUNT(0) FROM my_table WHERE name LIKE '%prashant%') AS countResults;
SELECT Id, Name FROM my_table WHERE Name LIKE '%prashant%' LIMIT 0, 10;
And then just call countResults whereever you need it elsewhere...
Another advantage of using COUNT(0) is you can use it for searching both the same table as here or you can use it to search a different table...
So for instance if each person found also has 3, 2, 1, 5 diffenrent jobs respectively... you could just add a
(SELECT COUNT(0) FROM my_jobs_table WHERE name = name_row_in_jobs_table) AS jobs
inside your original SELECT like this
SELECT Id, Name (SELECT COUNT(0) FROM my_jobs_table WHERE name = name_row_in_jobs_table) AS jobs FROM my_table WHERE Name LIKE '%prashant%' LIMIT 0, 10;
Giving you:
--------------------
| id | Name | jobs |
--------------------
| 1 | Alice| 3 |
--------------------
| 2 | John | 2 |
--------------------
| 3 | Bob | 1 |
--------------------
| 4 | Jill | 5 |
--------------------
So when showing name[3] (ie. Bob) you could also show that Bob has 1 job by calling jobs[3]...

Categories