mySQL Check if all link items are a specified value - mysql

I want to check if all linked Items that are a specified value. I am not sure how to properly explain it but the example is pretty self-explanatory in my opinion.
Example Database (simplified)
Table Groups
PK_Groups int
Groupname varchar
Table Person
PK_Person int
Name varchar
isAdult tinyint(1)
FK_Groups int
How can I check in which groups are only adults (1 query that returns all groups)?
Is there a way to do it in SQL or do I have to do "manually"?
Thanks in advance
SELECT PK_Groups
FROM Groups
INNER JOIN Person on PK_Group = FK_Groups
WHERE isAdult = 0
WHERE doesn´t work because there are some that are adult
and grouping by name and checking if adult doesnt work either because its inside the group
Couting non adults for each group would work (If Count <=0) but only for each group and not all groups at once

Check the minimum value of isAdult for each group. If they're all adults, the minimum value is 1.
SELECT PK_Groups
FROM Groups
INNER JOIN Person on PK_Groups = FK_Groups
GROUP BY PK_Groups
HAVING MIN(isAdult) = 1
Note that this isn't the general way to test that all values in a set are a specific value. It only works when the value you're trying to check for is the highest possible value (you can also test for the lowest value by changing MIN() to MAX()).
The more general condition can be tested with:
HAVING SUM(column = value) = COUNT(*)

Related

SELECT grouping by value in field

Given the following (greatly simplified) example table:
CREATE TABLE `permissions` (
`name` varchar(64) NOT NULL DEFAULT '',
`access` enum('read_only','read_write') NOT NULL DEFAULT 'read_only'
);
And the following example contents:
| name | access |
=====================
| foo | read_only |
| foo | read_write |
| bar | read_only |
What I want to do is run a SELECT query that fetches one row for each unique value in name, favouring those with an access value of read_write, is there a way that this can be done? i.e- such that the results I would get are:
foo | read_write |
bar | read_only |
I may need to add new options to the access column in future, but they will always be in order of importance (lowest to highest) so, if possible, a solution that can cope with this would be especially useful.
Also, to clarify, my actual table includes other fields than these, which is why I'm not using a unique key on the name column; there will be multiple rows by name by design to suit various criteria.
The following will work on your data:
select name, max(access)
from permissions
group by name;
However, this orders by the string values, not the indexes. Here is another method:
select name,
substring_index(group_concat(access order by access desc), ',') as access
from permissions
group by name;
It is rather funky that order by goes by the index but min() and max() use the character value. Some might even call that a bug.
You can create another table with the priority of the access (so you can add new options), and then group by and find the MIN() value of the priority table:
E.g. create a table called Priority with the values
| PriorityID| access |
========================
| 1 | read_write |
| 2 | read_only |
And then,
SELECT A.Name, B.Access
FROM (
SELECT A.name, MIN(B.PriorityID) AS Most_Valued_Option -- This will be 1 if there is a read_write for that name
FROM permissions A
INNER JOIN Priority B
ON A.Access = B.Access
GROUP BY A.Name ) A
INNER JOIN Priority B
ON A.Most_Valued_Option = B.PriorityID
-- Join that ID with the actual access
-- (and we will select the value of the access in the select statement)
The solution proposed by Gordon is sufficient for the current requirements.
If we anticipate a future requirement for a priority order to be other than alphabetical string order (or by enum index value)...
As a modified version of Gordon's answer, I would be tempted to use the MySQL FIELD function and (its converse) ELT function, something like this:
SELECT p.name
, ELT(
MIN(
FIELD(p.access
,'read_only','read_write','read_some'
)
)
,'read_only','read_write','read_some'
) AS access
FROM `permissions` p
GROUP BY p.name
If the specification is to pull the entire row, and not just the value of the access column, we could use an inline view query to find the preferred access, and a join back to the preferences table to pull the whole row...
SELECT p.*
FROM ( -- inline view, to get the highest priority value of access
SELECT r.name
, MIN(FIELD(r.access,'read_only','read_write','read_some')) AS ax
FROM `permissions` r
GROUP BY r.name
) q
JOIN `permissions` p
ON p.name = q.name
AND p.access = ELT(q.ax,'read_only','read_write','read_some')
Note that this query returns not just the access with the highest priority, but can also return any columns from that row.
With the FIELD and ELT functions, we can implement any ad-hoc ordering of a list of specific, known values. Not just alphabetic ordering, or ordering by the enum index value.
That logic for "priority" can be contained within the query, and won't rely on an extra column(s) in the permissions table, or the contents of any other table(s).
To get the behavior we are looking for, just specifying a priority for access, the "list of the values" used in the FIELD function will need to match the "list of values" in the ELT function, in the same order, and the lists should include all possible values of access.
Reference:
http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_elt
http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_field
ADVANCED USAGE
Not that you have a requirement to do this, but considering possible future requirements... we note that...
A different order of the "list of values" will result in a different ordering of priority of access. So a variety of queries could each implement their own different rules for the "priority". Which access value to look for first, second and so on, by reordering the complete "list of values".
Beyond just reordering, it is also possible to omit a possible value from the "list of values" in the FIELD and ELT functions. Consider for example, omitting the 'read_only' value from the list on this line:
, MIN(FIELD(r.access,'read_write','read_some')) AS ax
and from this line:
AND p.access = ELT(q.ax,'read_write','read_some')
That will effectively limit the name rows returned. Only name that have an access value of 'read_write' or 'read_some'. Another way to look at that, a name that has only a 'read_only' for access will not be returned by the query.
Other modifications to the "list of values", where the lists don't "match" are also possible, to implement even more powerful rules. For example, we could exclude a name that has a row with 'read_only'.
For example, in the ELT function, in place of the 'read_only' value, we use a value that we know does not (and cannot) exist on any rows. To illustrate,
we can include 'read_only' as the "highest priority" on this line...
, MIN(FIELD(r.access,'read_only','read_write','read_some')) AS ax
^^^^^^^^^^^
so if a row with 'read_only' is found, that will take priority. But in the ELT function in the outer query, we can translate that back to a different value...
AND p.access = ELT(q.ax,'eXcluDe','read_write','read_some')
^^^^^^^^^
If we know that 'eXcluDe' doesn't exist in the access column, we have effectively excluded any name which has a 'read_only' row, even if there is a 'read_write' row.
Not that you have a specification or current requirement to do any of that. Something to keep in mind for future queries that do have these kinds of requirements.
You can use distinct statement (or Group by)
SELECT distinct name, access
FROM tab;
This works too:
SELECT name, MAX(access)
FROM permissions
GROUP BY name ORDER BY MAX(access) desc

Mysql Obtaining actual value through FK when Selecting all rows

I need to select * FROM sections and get the column values for every row to fill a JTable. My problem is that my adviserId column on section table is an INT
And because I'm getting the result set of every column on every row, I cannot issue a WHERE clause. I thought of subquery but since Id is different on every row, no predetermined Id can be supplied on WHERE clause.
So If I run my stored procedure, I get just an int value for adviserId instead of the teacher's name.
I have teachers and sections table.
Teacher
id PK INT
lastName
firstName
middleName
isAdviser
status
Sections
id PK
name
adviserId FK-- REFERENCING `id` column ON teacher table
What would be the best approach? I hope you can help.
Thanks.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
I've created the final stored procedure based on everyone's suggestion. (THANKS AGAIN all.)
CREATE DEFINER=`root`#`localhost` PROCEDURE `getAllSectionsInfo`()
BEGIN
SELECT
s.`name` AS `Section Name`,
s.`session` AS `Session`,
CONCAT(t.lastName,',',t.firstName,' ',t.middleName) AS Adviser,
s.yearLevel AS `Year Level`,
CONCAT(syStart,'-',syEnd) AS SchoolYear
FROM sections s
INNER JOIN
teacher t on s.adviserId = t.id;
END
Yes I also think the same, that a simple inner join will do your job. Try the below example..
create table JTable as select T.id as Tid,T.lastName,T.firstName,T.middleName,T.isAdviser,T.status,S.id as Sid,S.name,S.adviserId
from Sections as S
inner join Teachers as T on T.id = S.adviserId
You can apply left join here to make sure that you have all records of Section table either related to Teachers data or with null data.
So, now the JTable will have all the columns in that you have put on the selection list.
Below is solution for db data selection
SELECT * FROM sections s INNER JOIN teacher on s.adviserId = t.id

Strange behavior in SQL with CASE WHEN EXIST in subquery (MySQL 5.5)

I've got a shop with items and itemgroups.
I also got some additional items, from which one should randomly be selected to present it in the cart overview, if that one is not present in the cart allready.
There can be items linked to the items group as well. If there is no item linked to the items in the cart i want one of them, that is not allready inside the cart to be randomly selected
I save relations between items inside table item2item:
itemid INT
additional_item_id INT
I save relations between groups and items in table group2item
groupid INT
additional_item_id INT
To make it a little more simple let's assume my item table looks like this:
itemid INT
name VARCHAR(100)
Here is what i tried to get an additional item:
SELECT
name
FROM
items a
WHERE
(a.itemid) = (
# if we have any additional items linked to the item get one of em randomly, that is not inside of a cart
SELECT
CASE WHEN EXISTS (
SELECT
b.additional_item_id
FROM
item2item b
WHERE
b.additional_item_id NOT IN (10)
AND b.itemid IN (10)
)
THEN (
SELECT
c.additional_item_id
FROM
item2item c
WHERE
c.additional_item_id NOT IN (10)
AND b.itemid IN (10)
ORDER BY
RAND()
LIMIT 1
# else if we have additional items linked to the items group get one of em randomly, that is not inside of a cart
) ELSE (
(
SELECT
d.additional_item_id
FROM
group2item d
WHERE
d.additional_item_id NOT IN (10)
AND
d.groupid IN (1)
ORDER BY RAND()
LIMIT 1
)
)
END as selecteditemid
)
Anyone can explain to me, why i get different amounts of rows with this?
You are asking why you might get different numbers of rows. Here are some thoughts:
items.itemid is not unique, so the duplicates are coming from multiple matches.
The else clause is executed and the where clause filters out all rows.
The else clause is executed and group2item.additional_item_id matches no items.itemid.
I speculate that it might be possible that when the first condition in the case statement is executed, the data might change between the when and then.
If you are looking for a fix to this, then move the subqueries to the from clause and put simpler logic in the where.

Update Based on a common value where there are multiple matches

I've got two tables (MySQL database), one called cities (that has columns 'state_id' and 'stateAB'--state_id is the row I would like to fill, stateAB is the 2-letter code of the state--I want this to serve as the key value).
I have another table called states (that has columns 'id' [this is the value that I want to go into the 'state_id' field of 'cities'], and a 'title' field [2-letter state codes] to be the common-key value).
I wanted to use a simple:
UPDATE cities SET state_id=(SELECT id FROM states WHERE states.title=cities.stateAB)
With the idea being that state_id will be set to the id that is returned where the 2-letter codes match.
The problem is that the following is returned:
#1242 - Subquery returns more than 1 row
I assume this is because there are more than one time per state that the codes match, for the simple reason that there are multiple cities per state (and they all have the same state/codes).
I'm not sure how to change this to make it work--I'm sure it's something obvious I'm just missing, but I don't know how to deal with the issue.
This is your query:
UPDATE cities
SET state_id = (SELECT id FROM states WHERE states.title = cities.stateAB);
You are getting the error because states has duplicates in the title column. You can find these by running:
select title, count(*) as numdups
from states
group by title
having count(*) > 1;
You may not care about the duplicates, happy to select just one id (consistently) when there is a match. If so, you can do:
UPDATE cities
SET state_id = (SELECT MIN(id) FROM states WHERE states.title = cities.stateAB);

How do I make COUNT work how I need it to?

I'm running this query, it works except it doesn't return what I need...
SELECT COUNT(up.profileOwnerUserNumber)
FROM profiles up
INNER JOIN userRatings ur
ON (ur.userRatingTargetUser = up.profileOwnerUserNumber)
WHERE ur.userRatingDateTime>(now()-INTERVAL 1 WEEK)
I get a return of 8 from this query. It's counting all instances found where (ur.userRatingTargetUser = up.profileOwnerUserNumber). But the userRatings table has 4 genuinely different entries in it - the other 4 are duplicates of numbers already found. I want my COUNT to return 4 - the number of distinctly different ur.userRatingTargetUser numbers found, not all 8 entries.
Table userRatings:
userRatingNumber int (autoincrement)
userRatingTargetUser int
Table profiles:
profileNumber int (autoincrement)
profileOwnerUserNumber int
Both userRatingTargetUser and profileOwnerUserNumber have int values that can match because they are set using another table:
Table users:
userNumber int (autoincrement)
How do I change my query so that I no longer count these extra records? SELECT DISTINCT didn't work.
Have you tried GROUP BY:
SELECT COUNT(up.profileOwnerUserNumber)
FROM profiles up
INNER JOIN userRatings ur
ON (ur.userRatingTargetUser = up.profileOwnerUserNumber)
WHERE ur.userRatingDateTime>(now()-INTERVAL 1 WEEK)
GROUP BY up.profileOwnerUserNumber
COUNT is an aggregate function, you should use GROUP BY.
http://dev.mysql.com/doc/refman/5.1/en/group-by-functions.html