SQL: Select data based on multiple criteria? - mysql

Example question:
I have a table called Candy, with thousands of records, and has 10 columns with information about the candy. I want to pull up the data for 3 records, and I have the following information:
Candy 1: Color = Yellow, Type = Soft, Flavor = Lemon
Candy 2: Color = Yellow, Type = Hard, Flavor = Lemon
Candy 3: Color = Red, Type = Hard, Flavor = Cherry
How do I do this? In Psuedo-CodeEnglish, it would be:
Select *
From Candy
WHERE (Color = Yellow, Type = Soft, Flavor = Lemon)
AND (Candy 2: Color = Yellow, Type = Hard, Flavor = Lemon)
AND (Color = Red, Type = Hard, Flavor = Cherry);
But that doesn't work for me. I'm using PL/SQL.

The basic idea is that you want to find any row where any line is true, but all conditions on that line are true. That means you need something that's of the following structure: ___ OR ___ OR ___ where each ___ is itself a list of ands: ___ AND ___ AND ____:
Select *
From Candy
WHERE (Color = Yellow and Type = Soft and Flavor = Lemon)
or (Color = Yellow and Type = Hard and Flavor = Lemon)
or (Color = Red and Type = Hard and Flavor = Cherry);

While #Denise's answer is fine, in certain cases there is a better way to solve this type of problem using a relational database. If you are doing a single query using a where statement is fine. But you might have different requirements. For example, saving queries or storing candy requirements for many different users, etc.
Create a selection table. This table has 3 columns (color, type and flavor) in the same type as your candy table. Also add a unique identifier. Then add the following data to the table:
id color type flavor
1 Yellow Soft Lemon
2 Yellow Hard Lemon
3 Red Hard Cherry
Now you can select your candy with the following statement
SELECT *
FROM Candy C
JOIN Selection S ON C.color = S.color AND C.type=S.type AND C.flavor=S.flavor
Of course this can be expanded... if you want data for users add a user column to selection and then add the following to the above
WHERE S.User = 'Hogan'
to get the candies I like
Or maybe you want history. You could add a date column to the above and then say
WHERE S.date = '2014-01-01'
to get the new year's candies.
etc.

The reason your current query isn't working for you is because you're using AND instead of OR. There is not going to be a row in your table where color is both yellow and red. However, you do want to use AND in between each of the three conditions.
Try this instead:
SELECT *
FROM candy
WHERE (color = 'yellow' AND type = 'soft' AND flavor = 'lemon')
OR (color = 'yellow' AND type = 'hard' AND flavor = 'lemon')
OR (color = 'red' AND type = 'hard' AND flavor = 'cherry');
Here's an SQLFiddle example for you. Unfortunately, I don't know all 10 of your columns (and I don't need to) but you'll see that the query does in fact return the 3 rows expected.

Related

Access form checkbox AND OR statements

Recently I started creating a form to filter the database by using checkboxes.
I have the following data:
Category Type Country
Fruit Apple NL
Fruit Apple SU
Fruit Banana NL
Fruit Banana DE
Nuts Cashew NL
Nuts Cashew US
Nuts Almond UK
Nuts Almond GR
Now I build in my form the checkboxes for Fruit & Nuts, for Apple, Banana, Cashew & Almond and for NL, SU, DE, US, UK & GR.
At this moment I use only the OR statement in my SQL script, which works well. But if the user would like to know for example which fruit comes from NL and it marks both Fruit and NL then my output will be everything from Fruit and everything from NL (because of the OR statement).
How should I change my script that the filter is used based on the checkboxes that are checked? In my example: if the user checks both Fruit & NL then it will give the output --> Fruit Apple NL & Fruit Banana NL
Or another example: He wants Banana & Cashew, and gets as output --> Fruit Banana NL & Fruit Banana DE & Nuts Cashew NL & Nuts Cashew US
I hope my problem is clear; if not, do not hesitate to ask for clarification!
From what I understand you have a form with 2 Category check boxes, 4 Type check boxes and 6 Country check boxes.
It doesn't sound like any of these filter themselves, so it's possible try and return Nuts, Apple and UK which would return an empty table.
I think you'll have to make extensive use of the LIKE command so any NULL/FALSE values can be converted to *.
As a checkbox returns TRUE/FALSE rather than Apple/False an IIF statement can be used to change TRUE to Apple (you could probably use CHOOSE or SWITCH instead).
So the WHERE clause here says chkFruit is either Fruit or anything.
SELECT Category, Type, Country
FROM Table2
WHERE Category LIKE (IIF([Forms]![Form1]![chkFruit],'Fruit','*')) AND
Category LIKE (IIF([Forms]![Form1]![chkNuts],'Nuts','*')) AND
Type LIKE (IIF([Forms]![Form1]![chkApple],'Apple','*')) AND
Type LIKE (IIF([Forms]![Form1]![chkBanana],'Banana','*')) AND
Type LIKE (IIF([Forms]![Form1]![chkCashew],'Cashew','*')) AND
Country LIKE (IIF([Forms]![Form1]![chkNL],'NL','*')) AND
Country LIKE (IIF([Forms]![Form1]![chkSU],'SU','*')) AND
Country LIKE (IIF([Forms]![Form1]![chkDE],'DE','*'))
Edit:
Reading your comment below it sounds like you want it to filter by the ticked boxes, or if no boxes are ticked then show all of them (as if all boxes in the Category/Type or Country were ticked).
That could get quite complicated if you have lots of tick boxes.
So each section of check boxes needs breaking down to something similar to below.
WHERE ((
Category = IIF([Forms]![Form1]![chkFruit],'Fruit',NULL) OR
Category = IIF([Forms]![Form1]![chkNuts],'Nuts',NULL)
) OR IIF(NOT [Forms]![Form1]![chkFruit] AND
NOT [Forms]![Form1]![chkNuts],Category LIKE '*',NULL))
The full SQL would be:
SELECT Category, Type, Country
FROM Table2
WHERE ((
Category = IIF([Forms]![Form1]![chkFruit],'Fruit',NULL) OR
Category = IIF([Forms]![Form1]![chkNuts],'Nuts',NULL)
) OR IIF(NOT [Forms]![Form1]![chkFruit] AND
NOT [Forms]![Form1]![chkNuts],Category LIKE '*',NULL))
AND
((
Type = IIF([Forms]![Form1]![chkApple],'Apple',NULL) OR
Type = IIF([Forms]![Form1]![chkBanana],'Banana',NULL) OR
Type = IIF([Forms]![Form1]![chkCashew],'Cashew',NULL) OR
Type = IIF([Forms]![Form1]![chkAlmond],'Almond',NULL)
) OR IIF(NOT [Forms]![Form1]![chkApple] AND
NOT [Forms]![Form1]![chkBanana] AND
NOT [Forms]![Form1]![chkCashew] AND
NOT [Forms]![Form1]![chkAlmond],Type LIKE '*',NULL))
AND
((
Country = IIF([Forms]![Form1]![chkNL],'NL',NULL) OR
Country = IIF([Forms]![Form1]![chkSU],'SU',NULL) OR
Country = IIF([Forms]![Form1]![chkDE],'DE',NULL) OR
Country = IIF([Forms]![Form1]![chkUS],'US',NULL) OR
Country = IIF([Forms]![Form1]![chkUK],'UK',NULL) OR
Country = IIF([Forms]![Form1]![chkGR],'GR',NULL)
) OR IIF(NOT [Forms]![Form1]![chkNL] AND
NOT [Forms]![Form1]![chkSU] AND
NOT [Forms]![Form1]![chkDE] AND
NOT [Forms]![Form1]![chkUS] AND
NOT [Forms]![Form1]![chkUK] AND
NOT [Forms]![Form1]![chkGR], Country LIKE '*',NULL))
I've got a feeling this could be shortened quite a bit but it's starting to hurt my head now.

How to avoid a selection from an SQL-database, if certain values match

I have a normalized database, where a superior table contains different products and a subordinated table characteristics. The subordinate table is linked to the superior table by a foreign key. This means that there may be several occurrences of one and the same characteristic_id (= foreign key) in the subordinate table if several characteristics match the properties of a product, e.g.
Superior table (product_main_table):
product_id product_name
23 apple
24 orange
25 strawberry
Subordinate table (product_characteristics):
characteristic_id product_name characteristic
23 apple green
23 apple sweet
23 apple small
23 apple american
24 orange orange
24 orange sourly
24 orange big
24 orange african
25 strawberry red
25 strawberry sweet
I have an html-form to read out the data and there should also be a possibility to search for all products that DO NOT match certain characteristics.
However, this does not work. When I enter the following request:
SELECT DISTINCT main.product_name
FROM product_main_table main, product_characteristics prodchar
WHERE prodchar.characteristic != 'sweet'
the result is 'apple', 'orange' and 'strawberry', wheresas it should be 'orange', only. The other two products are selected of course, because they have other characteristics than 'sweet' that are selected.
I would be more than happy, if the solution would fit into my general SQL-request
$sql = "SELECT DISTINCT $selection FROM $tabelle WHERE $masterarray";
where $selection, $tabelle and $masterarray get their contents from the html-entries.
How do I have to make my where-statement, so that a product is not selected, if it contains a single characteristic that should not be present?
http://sqlfiddle.com/#!9/7fd63b/5
SELECT pmt.product_name
FROM product_characteristics pc
JOIN product_main_table pmt
ON pmt.product_id = pc.characteristic_id
GROUP BY characteristic_id
HAVING SUM(characteristic IN ('sweet'))=0
I think you could use the NOT IN function:
... WHERE prodchar.characteristic NOT IN (SELECT characteristic FROM product_characteristics WHERE name = main.product_name)
You could do some thing like
SELECT DISTINCT prodchar.product_name
FROM product_characteristics prodchar
WHERE prodchar.product_name not in (select product_name from product_characteristics
where characteristic in ('sweet'))
http://sqlfiddle.com/#!9/ce6f6/7
Performance would be bad, Try using Joins. Join vs. sub-query
SET #characteristic='sweet,sour,bland';
...
WHERE FIND_IN_SET(prodchar.characteristic, #characteristic) = 0
or per your requested example
$sql = "SET #characteristic='sweet,sour,bland';SELECT DISTINCT $selection FROM $tabelle WHERE FIND_IN_SET(prodchar.characteristic, #characteristic) = 0;";
you can also use php string concatenation to echo in your variable into the $sql variable if needed.

GROUP BY How to filter out groups with a similarly written value in another row of same table?

Let's asume for simplicity I have a table with this data:
Color Temp Value
----- ---- -----
red hot 1
red cold 1
red mild 2
red hot 2
red cold 3
blue hot 1
blue cold 2
blue hot 2
blue mild 3
blue hot 3
blue cold 2
green hot 1
yellow hot 1
yellow mild 1
yellow cold 4
yellow hot 3
reddish hot 1
reddish mild 3
reddish cold 4
purple hot 1
purple mild 3
purple cold 2
I want to query the table grouping and counting the occurrences of each color and have this exclusions
1) exclude groups with less than 2 occurences, therefore I would want to exclude the "green" group
2) exclude groups where at least one of the rows of that group contains "4" in the "value" column. Therefore exclude the "yellow" and the "reddish" groups
3) exclude groups when there also exists at least one row in the table with a similarly written color(LIKE 'color%'?) that has "4" in the "value" column. Therefore I would want to exclude the "red" group, because there is a row with color "reddish" and value "4"
So In the example data I put I would expect my query to return only:
Color Count(*)
----- --------
blue 6
purple 3
I suspect this query should use some mix of JOIN clauses, use of variables and/or subqueries with maybe LIKE CONCAT, but all these are kind of new to me so I cannot seem to arrive to a working query. Or maybe it is a lot simpler than that.
Anybody knows how to write this query?
Many thanks.
You can use the HAVING clause to set conditions on aggregation. For your first two conditions, the query would look like this:
SELECT color, COUNT(*) AS colorCount
FROM myTable
GROUP BY color
HAVING COUNT(*) >= 2 AND SUM(val = 4) = 0;
This query will only return the count for groups that have at least 2 rows, and where no rows where value = 4. The last condition is a little tricky, and I do not think you can solve this using a HAVING clause.
What I would do is start by considering this as a separate issue, and then working it in later. To get all rows where val = 4, try this:
SELECT DISTINCT color
FROM myTable
WHERE val = 4;
Once you have that, you can JOIN it to it's related colors like this:
SELECT DISTINCT m.color
FROM myTable m
JOIN(
SELECT DISTINCT color
FROM myTable
WHERE val = 4) tmp ON tmp.color LIKE CONCAT(m.color, '%');
In your example, the above query would return 'red'. So, to exclude red from your final results, you can use the NOT IN operator. I used that operator on the first result set so that the condition is only checked on the resulting aggregated rows:
SELECT color, colorCount
FROM(
SELECT color, COUNT(*) AS colorCount
FROM myTable
GROUP BY color
HAVING COUNT(*) >= 2 AND SUM(val = 4) = 0) tmp
WHERE color NOT IN(
SELECT DISTINCT m.color
FROM myTable m
JOIN(
SELECT DISTINCT color
FROM myTable
WHERE val = 4) tmp ON tmp.color LIKE CONCAT(m.color, '%'));
Here is an SQL Fiddle example with your sample data.

Join tables and append columns

I need to join two tables, and append columns where certain data matches. The two tables are setup like so:
Apple | Flavor
Fuji | 1
Red Delicious | 2
Flavor | Quality
1 | Sour
1 | Sweet
2 | Bitter
2 | Sweet
And I need to append a column for each matching flavor quality like so:
Apple | Quality 1 | Quality 2
Fuji | sour | sweet
Red Delicious | bitter | sweet
Notice I also need append an auto-integer to each column header appended.
This seems like a tall task and I'm not sure where to start? Is this even possible? Thanks for the help in advance.
You can group related rows together and concatenate the Quality values for the respective group. This produces one column, a string with comma-separated values. See GROUP_CONCAT().
SELECT a.Apple, GROUP_CONCAT(f.Quality) AS Qualities
FROM apples a JOIN flavors f ON a.flavor = f.flavor
GROUP BY a.Apple;
This doesn't do exactly what you asked, because it doesn't separate the Quality values in to separate columns. But it's a pretty simple query, and you can explode() the string of qualities after you fetch it back into your application.
You can't have a variable number of columns like that. Use one query to fetch the apples, then loop over the result set to fetch their qualities. You can merge the result sets together using your server-side scripting language if you like.
Against what Mark said, you can have a variable number of columns. Though I'm demonstrating here just how nonsense it actually is.
Usually you would have a single statement like
SELECT * FROM apples a JOIN flavors f ON a.flavor = f.flavor;
then you'd do little voodoo on application level and that's it. Here's how this simple query would look like to get a static number of columns:
select
Apple,
max(case when my_quality_numbering = 1 then Quality else null end) as Quality1,
max(case when my_quality_numbering = 2 then Quality else null end) as Quality2
from (
select
a.Apple,
f.Quality,
case when (if(#prev_apple != a.Apple, #quality_no := 1, #quality_no := #quality_no + 1)) is null then null
when (#prev_apple := a.Apple) is null then null
else #quality_no end as my_quality_numbering
from
apples a
join flavors f on a.Flavor = f.Flavor
, (select #quality_no := 0, #prev_apple := NULL) v
order by a.Apple
) sq
group by Apple
see it working live in an sqlfiddle
And this is only half the work. To have variable number of columns, you'd have to write a stored procedure, that reads from the inner query, how much columns are needed. Then you'd have to build your query string for the outer query and execute all that via a prepared statement. Pretty much work for a simple query. Again, I did above query just for fun, but in general it's really not worth the trouble.

sql select in many-to-many relation not working as expected, EAV design data-model

sql select in relation many-to-many not works as expected
i have the following tables in relation many-to-many:
table product:
prd_cod (pk)
cat_cod (fk)
prd_nome
table description_characteristic:
prd_cod(fk)
id_characteristic(fk)
description
table characteristic:
id_characteristic(pk)
name
we suppose that the cat_cod will be 1, so i want to show all the products that have the category code equals 1,that will be provided dynamically in php for parameter ...
I have done this select below to solve my problem:
select p.prd_cod,p.prd_name,c.name_characteristic,dc.description
from product p,description_characteristic dc, characteristic c
where p.prd_cod = dc.prd_cod and
dc.id_ccharacteristic = c.id_characteristic and
p.cat_cod = 1
but the data were shown this way:
Prd_cod Prd_name name_characteristic descript
1 pen Color pink
1 Pen manufacturer kingston
1 Pen type brush
1 Pen weight 0.020
I want to show the result this way:
Prd_cod Prd_name name_characteristic descript name_characteristic descript
1 pen Color pink type brush
2 Pen-drive manufacturer kingston weight 0.020
I would like to show all the characteristics of the same product, and not just two as above...
I can not do a select to solve this
please i need help
Thank you all
As stated by Tom H., your database layout may generally be a bad idea and you might reconsider it. That said, there is no really clean solution producing the mysql result set you want. But you might use something crazy with GROUP_CONCAT like this, reproducing the real data with some PHP-side postprocessing:
SELECT p.prd_cod, p.prd_name, GROUP_CONCAT(c.name_characteristic), GROUP_CONCAT(dc.description)
from product p,description_characteristic dc, characteristic c
where p.prd_cod = dc.prd_cod and
dc.id_ccharacteristic = c.id_characteristic and
p.cat_cod = 1
GROUP BY p.prd_cod
This should return something like
Prd_cod Prd_name name_characteristic descript
1 pen Color,manufacturer pink,Apple
this result set should be post-processed like this:
$out = array();
foreach ($result as $res) {
$p = array_combine(
explode(",", $res["name_characteristic"]),
explode(",", $res["descript"]));
$p["prd_cod"] = $res["prd_cod"];
$out[] = $p;
}
I would not call this a robust solution, though - for example, commas in your values screw things up. If you want to avoid madness like this, you'll have to postprocess the result you already get.
But if you want to show some mad sql aggregation skills (and you seem to be interested as you've chosen this database layout), this one is for you.