I have a table containing the names, emails, positions, etc of a students, as well as their "status" (which can be one of Y or N.) I want to write a query that counts the number of each type of position, as well as the number of Y AND the number of N within each type using JOIN. (That is, it would be a table with three columns: Position, StatusIsYes, and StatusIsNo.)
I have already done this using the CASE clause the following way, but I can't figure out how to do it using the JOIN clause.
SELECT position,
COUNT(CASE WHEN status = 'Y' THEN 1 ELSE NULL END) AS StatusIsYes,
COUNT(CASE WHEN status = 'N' THEN 1 ELSE NULL END) AS StatusIsNo
FROM
students GROUP BY crd
I appreciate any suggestions!
EDIT: I know it can be done without using JOIN, but I want to know how it is possible to do it with a JOIN.
You don't need a join:
SELECT
position,
SUM(status = 'Y') AS StatusIsYes,
SUM(status = 'N') AS StatusIsNo
FROM students
GROUP BY position
Note the rather funky dispensing of the CASE, because in mysql (only) true is 1 and false is 0, so sum() of a condition counts how many times it is true :)
You can use SELF JOIN in the case when you want to fetch records from same table.
For ex:
Table Name: employee
Fields : EmpId,EmpName,ManagerId
Now if you want to get the details of Empolyees who are in Manager Position for that we need to write query like this:
SELECT e1.EmpId, e1.EmpName FROM EmployeeDetails e1, EmployeeDetails e2 where e1.EmpId=e2.ManagerId;
Hope it will help you.
Fro more information please check this link.
Try ::
SELECT
position,
COUNT(status = 'Y' ) AS StatusIsYes,
COUNT(status = 'N' ) AS StatusIsNo
FROM
students GROUP BY POSITION
Related
I have a table called Access_Status containing values such as below:
I would like the table to only return the Active users which are Tom Sullivan and John Martin.
It should NOT return the Active row for Marta Jenkins since Marta is actually suspended.
What's the best approach for this?
You can filter on the latest row per name with a correlated subquery, and ensure that the status is active:
select a.*
from access_status a
where
t.last_updated_date = (
select max(a1.last_updated_date)
from access_status a1
where a1.initial_id = a.initial_id
)
and a.access = 'ACTIVE'
For performance, you want an index on (intial_id, last_updated_date). Adding access as a last column the index might also help.
List of latest rows which are "ACTIVE":
SELECT ast1.*
FROM Access_Status ast1
LEFT JOIN Access_Status ast2 ON
ast1.Initial_ID = ast2.Initial_ID AND ast1.Last_Updated_Date < ast2.Last_Updated_date
WHERE ast2.Initial_ID IS NULL
AND ast1.Access = 'ACTIVE'
-- if the intent is to have latest rows that are NOT 'SUSPENDED'
-- AND ast1.Access <> 'SUSPENDED'
;
One of the way to get ACTIVE users:
select A.* from Access_Status A where A.Access = 'ACTIVE'
and A.Name not in (select Name from Access_Status WHERE Access != 'ACTIVE');
Thanks for all your attempts. Actually, I realize that I did not give enough information. There was an additional column end_date which will be null for all active users. So I can use ACCESS='ACTIVE' and end_date is null
This approach would help you for getting rows as your need
SELECT * FROM Access_Status GROUP BY Name WHERE Access="ACTIVE";
Is there any way to do that in a single query? Or do I have to manage it externally? It is not a JOIN of any kind.
SELECT
IF (
(SELECT indicator FROM configuration_table) = 1,
(SELECT series_id FROM series_table LIMIT 1),
''
) as if_exp
FROM
series_table
This executes but returns the first ID over and over, and if I take out the LIMIT 1, it doesn't work as it expects only one result. But what I need is that, if this condition is met:
(SELECT indicator FROM configuration_table) = 1,
Then I need all this data returned:
SELECT series_id, series_code, series_name FROM series_table
Is it possible somehow? Should I be doing two queries and managing the data from php? Thank you very much.
The easiest way would be:
IF ((SELECT indicator FROM configuration_table) = 1) THEN
SELECT series_id, series_code, series_name FROM series_table
END IF
You did not show us what to do, when the condition is false. We do not know the relationship between configuration_table and series_table, so we can't find a way to make it in a single query.
I have copied this answer from IF Condition Perform Query, Else Perform Other Query this answer.
SELECT CASE WHEN ( (SELECT indicator FROM configuration_table) = 1 )
THEN
SELECT series_id, series_code, series_name FROM series_table
ELSE
<QUERY B>
END
Here Query B should replaced by your desired query.
I have the following stored procedure
BEGIN
SELECT kids.*, SUM(point) as `point_sum`
FROM kids
LEFT JOIN tasks
ON kids.id = tasks.kid_id
WHERE kids.user_id = IN_user_id
GROUP BY kids.name;
END
This statement works fine.
My Question: the SUM(point) for new users are typically NULL because there is no submitted value yet to be summed.
What I want is if SUM(point) is NULL then it should return value like 0 but otherwise it should present the sum. I have looked around and not sure how to fix it, any good ideas?
You could use the coalesce function:
SELECT kids.*, COALESCE(SUM(point), 0) as `point_sum`
FROM kids
LEFT JOIN tasks
ON kids.id = tasks.kid_id
WHERE kids.user_id = IN_user_id
GROUP BY kids.name;
All you really need is IFNULL():
SELECT kids.*, IFNULL(SUM(point), 0) AS point_sum
That converts NULL to the supplied value, in this case 0.
I'm working with a little display complication here. I'm sure there's an IF/ELSE capability I'm just overlooking.
I have 2 tables I'm querying (customers, addresses). The first has the main record, but the second may or may not have a record to LEFT JOIN to.
I want to display a zero if there is no record in the addresses table.
And I want to only display 1, if a record exists.
What I've attempted so far:
SELECT c.name, COALESCE(a.addressid,0) AS addressexists
FROM customers c
LEFT JOIN addresses a ON c.customerid = a.customerid
WHERE customerid = 123
This first example does not do it. But I may be utilizing COALESCE wrong.
How can I display a 0, if null, and a 1, if something exists?
Instead of COALESCE(a.addressid,0) AS addressexists, use CASE:
CASE WHEN a.addressid IS NOT NULL
THEN 1
ELSE 0
END AS addressexists
or the simpler:
(a.addressid IS NOT NULL) AS addressexists
This works because TRUE is displayed as 1 in MySQL and FALSE as 0.
SELECT c.name, IF(a.addressid IS NULL,0,1) AS addressexists
FROM customers c
LEFT JOIN addresses a ON c.customerid = a.customerid
WHERE customerid = 123
Careful if you're coming from C/C++ and expecting this to work:
select if(name, 1, 0) ..
Even if 'name' is not NULL, unlike in C, a false-condition still triggers and the above statement returns 0. Thus, you have to remember to explicitly check for NULL or empty string:
select if(name is null or name = '', 0, 1)
PS Eugen's example up above is correct, but I wanted to clarify this nuance as it caught me by surprise.
SELECT
c.name,
CASE WHEN a.addressid IS NULL THEN 0 ELSE 1 END AS addressexists
FROM customers c
LEFT JOIN addresses a ON c.customerid = a.customerid
WHERE customerid = 123
Another method without WHERE, try this..
Will select both Empty and NULL values
SELECT ISNULL(NULLIF(fieldname,'')) FROM tablename
It will set null if it is an empty string, then be true on that also.
You can actually use an IF statement in the latest versions of MySQL.
IF(expr,if_true_expr,if_false_expr)
IE:
SELECT name, IF(ISNULL(name), 'robot', 'human') AS type
FROM visitors
If within TSQL, you can try :
SELECT IIF(a.addressid IS NULL, 0, 1) AS addressexists
SQL Server should work
Let's say I have a table of people and a table of attributes related to the people. (No, I'm not building a dating site, it's just a good example.) The table of people looks like this:
id integer
name varchar(100)
The table of attributes looks like this:
person_id integer
name varchar(100)
value varchar(100)
I can fetch all the attributes of a person very easily:
SELECT * FROM attribute WHERE person_id = 5;
If I want to find all the people who are more than 60 inches tall, that is easy too:
SELECT person_id FROM attribute WHERE name = 'height' AND value > 60;
And of course I can add a join to these queries to get the person's name.
But if I need to find the people more than 60 inches tall who are ALSO over 40 (note the need for an AND here), the simplest solution that comes to mind is (indented for readability):
SELECT p.id FROM person p
INNER JOIN attribute a1 ON a1.person_id = p.id AND a1.name = 'height' AND a1.value > 60
INNER JOIN attribute a2 ON a2.person_id = p.id AND a2.name = 'age' AND a2.value > 40;
Note that I'm joining to the same table twice.
Am I missing a straightforward way to do this without joining repeatedly, or is that pretty much the most efficient way to go about it? Can I repeat the join numerous times without getting into trouble?
I am aware of one hypothetical alternative: adding the individual attributes (height, age, etc.) as columns in the person table. Unfortunately in my application people are constantly adding new attributes on the fly (think machine tags in flickr). It is not feasible to add them all as columns, even if I had privileges to alter the database schema on the fly.
Thanks!
what you have there is probably the most efficient you're going to get with your data structure. although it's still a little bizarre.... unless a person can have multiple heights or ages, it might make more sense to merge those tables.
if nothing else, have a person table and personattribute table that houses some common elements as columns instead of rows. you can still have attribute table for other more obscure attributes that you can share with other objects.
personattribute
personid
height
age
weight
shoesize
waistsize
someotherbodypart_size
sometimes storage overhead can be offset by performance gains. in this case, you're not really wasting much storage either if every "person" has these common attributes.
just my 2 cents.
The way you are doing it is not the only simple way to do this - event if it may be the most efficient. Simpler ways to do this would include:
Use union:
SELECT person_id FROM attribute WHERE name = 'height' AND value > 60
union
SELECT person_id FROM attribute WHERE name = 'age' AND value > 40
Use boolean logic (and eliminate duplicates using the distinct keyword):
SELECT distinct person_id FROM attribute
WHERE (name = 'height' AND value > 60) OR (name = 'age' AND value > 40)
You can use either of these as subqueries to fin the names, instead of the IDs, if you wish.
If you have an index on (name, value) including the id, these might even be efficient
I can't concretely answer this question without setting up a test db, but I think you need to go the other way. Start with your attributes table and join to users. So:
SELECT p.id ,a.name, a.value
FROM attribute a
INNER JOIN person p ON a.person_id = p.id
WHERE (a.name = 'height' AND a.value > 60)
AND (a.name = 'age' AND a.value > 40)
Something like that ... you may have to tweak it a bit.
EDIT:
As pointed out that won't work ,see I told you I didn't try it. So you have two other choices, using EXISTS or PIVOT. Here is an example of a pivot, although I don't know how this performs in MySQL... if you have millions of rows in your attributes table that may be a problem.
SELECT distinct
u.id
FROM users u
inner JOIN (
SELECT
user_id,
GROUP_CONCAT(if(name = 'age', value, NULL)) AS 'age',
GROUP_CONCAT(if(name = 'height', value, NULL)) AS 'height'
FROM attributes
GROUP BY user_id) a on u.id = a.user_id
WHERE a.age > 40
and a.height > 60;
On MS-SQL 2000 can be done this way:
SELECT person_id
FROM attribute
INNER JOIN (
SELECT this_id=person_id
, summ_is=SUM( CASE name
WHEN 'att1' THEN 1
WHEN 'att2' THEN 1
WHEN 'att3' THEN 1
WHEN 'att4' THEN 1
WHEN 'att5' THEN 1
ELSE 0 END
FROM attribute
GROUP BY person_id
) tab
ON person_id=this_id
AND summ_is=5
For any type of comparision:
SUM(
CASE WHEN name > attr1 THEN 1 ELSE 0 END
+CASE WHEN name = attr2 THEN 1 ELSE 0 END
+CASE WHEN name < attr3 THEN 1 ELSE 0 END
+CASE WHEN name != attr4 THEN 1 ELSE 0 END
+CASE WHEN name LIKE '%'+attr5+'%' THEN 1 ELSE 0 END
)