my sql questions - mysql

THIS is the table for the questions
+---------+----------+------+
| owner | species | sex |
+---------+----------+------+
| Harry | cat | f |
| Gwen | dog | m |
| Adlai | rat | m |
| Alex | bird | f |
| Harry | dog | f |
| Gwen | cat | m |
| Gwen | dog | f |
+---------+----------+------+
I have tried >
SELECT owner,MAX(count(species)) FROM pet GROUP BY owner;
did not get it.
i managed to get >
SELECT owner, count(species) FROM pets GROUP BY owner;
+---------+----------------+
| owner | count(species) |
+---------+----------------+
| Gwen | 3 |
| Adlai | 1 |
| Harry | 2 |
| Alex | 1 |
+---------+----------------+
but it needs only one...
please help!
Thanks!

1.
SELECT owner
FROM pet
WHERE species IN ('cat', 'dog')
GROUP BY owner
HAVING COUNT(*) = 2
This query assumes the owner can only have a single cat and a single dog.
1a.
SELECT p1.owner
FROM pet p1
INNER JOIN p2 ON p1.owner = p2.owner
AND p2.pet = 'dog'
WHERE p1.pet = 'cat'
GROUP BY p1.owner
This query is slightly better since it doesn't care of how many pets every owner has.
2.
SELECT owner, count(species) cnt
FROM pets
GROUP BY owner
ORDER BY count(species) DESC
LIMIT 1
PS: and try to do your homework next time by yourself ;-)

Instead of hard-coding specific species, what it looks like you are asking for is... Of the owners of pets, I want to know who has the most distinct species regardless of sexual gender (m/f). Whatever that count is, you want to list all owners who had that # of species. Start first with getting the highest distinct species count per ANY owner, ANY species...
However, by re-reading, it looks like you are considering Gwen as 3 species even though 2 are dogs, 1 is a cat, but the dogs are one male, one female... What if someone has 4 dogs and they are all male and 5 cats all female. Would that count as 2 species?
Anyhow, based on my original based on distinct SPECIES (regardless of gender), I have a solution
select
PreQuery.owner,
PreQuery.SpeciesCount,
#HighCount := if( #HighCount < PreQuery.SpeciesCount,
PreQuery.SpeciesCount, #HighCount ) as KeepCount
from
( select
owner,
count( distinct species ) as SpeciesCount
from
pets
group by
owner
order by
count( distinct species ) desc ) PreQuery,
( select #HighCount := 0 ) sqlvars
having
SpeciesCount = KeepCount
at SQL Fiddle
The first query pre-aggregates the number of DISTINCT species regardless of gender and orders with the highest count coming first. By using MySQL Variables (via #varName), I am applying the first record returned in the result set to the high count value, then keep it there for all subsequent records.
Finally, the "HAVING" clause allows it to retain only those that had that max count.. in this scenario, it had TWO people with two distinct species.
If you DO want it based on species AND sex, we can revise the query, but the principle will be the same.
Another query based on distinct species AND sex
I just changed to
count( distinct concat(species, sex) )
in both places

Related

WHERE/GROUP By Condition - One Name but multiple values

I have the following table:
Name Product
Bob Car
Bob Apples
Bob Pears
Bob Car
John Apples
John Pears
Whoever has bought a Product Car, I want to keep separate from everyone else. So, I create a flag:
Name Product Flag
Bob Car 1
Bob Apples 0
Bob Pears 0
Bob Car 1
John Apples 0
John Pears 0
But the problem with my flag is that even if I do a where condition and say, show me the consumer WHERE flag !=1, it'll pick Bob. Which is incorrect as Bob owns a car.
I would still like to GROUP by Product.
How do I separate the above table into two groups?
Thanks!
Use below query :-
select name from table where flag!=1
and name not in (select name from table where flag = 1)
group by name
"show me the consumer WHERE flag !=1, it'll pick Bob" that is because you are asking for rows where flag != 1. Instead you'll need something a little more complicated, like:
SELECT DISTINCT Name
FROM tableTable
WHERE Name NOT IN (SELECT Name FROM theTable WHERE Product = 'Car')
alternatively, you can do a LEFT JOIN, which may or may not be faster depending on the amount of data you have and how its values are distributed.
SELECT DISTINCT a.Name
FROM theTable a
LEFT JOIN theTable b ON a.Name = b.Name AND b.Product = 'Car'
WHERE a.Product != 'Car' AND b.Product IS NULL
;
This gets all the rows with products other than cars, and then uses the LEFT JOIN in conjunction with the IS NULL condition to find which did not also have a 'Car' row.
I think you want your table's data displayed, just with "People who bought cars" partitioned (not grouped) separately somehow - this could be done with an ORDER BY OwnsACar clause, for example.
Step 1: Identify the people who have bought cars:
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
Step 2: Join on this data to generate a calculated "OwnsACar" column:
SELECT
yourTable.Name,
yourTable.Product,
ISNULL( carowners.Name ) AS OwnsACar
FROM
yourTable
LEFT OUTER JOIN
(
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
) AS carowners ON carowners.Name = yourTable.Name
ORDER BY
OwnsACar ASC,
yourTable.Name ASC
You can use these two queries. The additional Flag column is not required.
-- do not have Car
SELECT *
FROM products
WHERE Name not in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
-- have Car
SELECT *
FROM products
WHERE Name in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
Illustration:
-- table
SELECT * FROM products;
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
| John | Apples |
| John | Pears |
+------+---------+
-- query for people that do not have Car
+------+---------+
| Name | Product |
+------+---------+
| John | Apples |
| John | Pears |
+------+---------+
-- query for people having 'Car'
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
+------+---------+
Try with :
SELECT `t`.`Name`, `t`.`Product`, SUM(`t`.`Flag`) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;
Although you can go without the flag column by going :
SELECT `t`.`Name`, `t`.`Product`, SUM(IF(`t`.`Product` = 'Car', 1, 0)) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;

How to search husband and wife data with one MySQL query?

I'm trying to find the most efficient and optimized way of querying husband and wife data for a search function in a finance application. The clients can be single or married.
Currently, when the data is created, there is a table for the household that shares information such as username, password, address, location, etc...
There is a separate table that stores individual information about the husband and wife in separate rows including birth dates and income.
The app has a search function where a user can search using criteria such as location, husband age range and income range and wife age range and income range and should return individual household results.
For instance, a user can search for clients that are located within 20 miles where the husband is between 50 and 60 years old and the wife is between 40 and 50 years old with an income range of $30,000 to $40,000.
The result would produce all results for singles and couples.
Here is just an idea of what the tables and results may look like. Keep in mind that the location data would actually use lat and long but for the purpose of this example, we are just using actual miles to keep it simple.
Table Users:
ID | Username | Location | Password | Email | Status
-------------------------------------------
1 | singleclient | 5 miles | 24##$#dls | user1#email.com | Single
2 | marriedclient | 7 miles | $#$sls33 | user2#email.com | Married
Table UserDetails
ID | User_ID | Gender | Name | Age | Income
----------------------------------
1 | 1 | Male | John Smith | 55 | 32000
2 | 2 | Male | Mike Jones | 53 | 37000
3 | 2 | Female | Cindy Jones | 47 | 31000
Result:
ID | Username | Distance | Status | Male Name | Female Name | Male Age | Female Age | Male Income | Female Income
----------------------------------------
1 | singleclient | 5 miles | Single | John Smith | null | 55 | null | 32000 | null
2 | marriedclient | 7 miles | Married | Mike Jones | Cindy Jones | 53 | 47 | 37000 | 31000
First, in many countries, the assumption that a marital unit consists of a single male and a single female is not true. I would try to avoid building this assumption into the data model or application.
I think you can approach this question using aggregation with a having clause:
select ud.user_id
from UserDetails ud
group by ud.user_id
having sum(case when ud.gender = 'Male' and ud.age between 50 and 60) = 1 and
sum(case when ud.gender = 'Female' and ud.age between 40 and 50 and ud.income between 30000 and 40000) = 1;
This gives you the user_ids that match. You can then format it however you like.
The above is quite generic. You might find that this version works faster:
select ud1.*, ud2.*
from UserDetails ud1 join
UserDetails ud2
on ud1.user_id = ud2.user_id
where ud1.gender = 'Male' and ud1.age between 50 and 60 and
ud2.gender = 'Female' and ud2.age between 40 and 50 and ud2.income between 30000 and 40000;
Which is faster depends on the size of your data and how indexes are set up.
You can join the same table twice under different names, and use this to populate all the fields.
You could do this by selecting the male person and the female person, but this will obviously get you in trouble when you need to deal with parties where both members are of the same sex. It might be better then to just pick the lowest ID and the highest ID in the database, or the youngest and oldest person, or whatever.
Query would look something like this (untested)
SELECT u.id, u.username, u.distance, u.status, p1.name, p2.name, p1.age, p2.age, p1.income, p2.income
FROM Users u
INNER JOIN UserDetails p1 ON u.id = p1.user_id AND p1.id = (SELECT MIN(id) FROM UserDetails WHERE user_id = u.id)
RIGHT JOIN UserDetails p2 ON u.status == 'married' AND u.id = p2.user_id AND p2.id = (SELECT MAX(id) FROM UserDetails WHERE user_id = u.id)
Adding in the "status == married" in the second (right) join will make sure that the second query will not show the same person twice, but will instead just return a row of nulls.
You'll probably need to do the query twice (so that each person can be searched as p1 and as p2) if one of the two spouses should be "50-60" and the other "40-50" or one should make $10.000 and the other $20.000, because you don't know in which order they'll come out.

SQL SELECT Query

Suppose I have a SQL table "Company" with three columns: "department_id", "employee", "job". Something like this:
DEPARTAMENT_ID | EMPLOYEE | JOB
--------------------------------------
1 | Mark | President
1 | Robert | Marketing Manager
1 | Rose | Administration Assitant
2 | Anna | Programmer
2 | Michael | Programmer
2 | Celia | Sales Manager
3 | Jhon | Sales Manager
3 | Donna | Programmer
3 | David | Marketing Manager
I would like to write a query that returns the departments id where at least 50% of their jobs are the same.
Result i need in my example would be just:
DEPARTAMENT_ID |
--------------------------------------
2 |
How do I write this SQL query? I think i tried all kind of stuff but i dont get it :(.
This is a bit tricky. You need to compare the total number of people on a job in a department to the total number. So, one method uses two aggregations:
select department_id
from (select department_id, count(*) as numemp
from t
group by department_id
) d join
(select department_id, max(numemp) as numemp
from (select department_id, job, count(*) as numemp
from t
group by department_id, job
) d
group by department_id
) dj
on d.numemp <= 2 * dj.numemp;
You might get duplicates if you have one department that is exactly split between two jobs. In that case, use select distinct.

SQL sorting does not follow group by statement, always uses primary key

I have a SQL database with a table called staff, having following columns:
workerID (Prim.key), name, department, salary
I am supposed to find the workers with the highest salary per department and used the following statement:
select staff.workerID, staff.name, staff.department, max(staff.salary) AS biggest
from staff
group by staff.department
I get one worker shown from each department, but they are NOT the workers with the highest salary, BUT the biggest salary value is shown, even though the worker does not get that salary.
The person shown is the worker with the "lowest" workerID per department.
So, there is some sorting going on using the primary key, even though it is not mentioned in the group by statement.
Can someone explain, what is going on and maybe how to sort correctly.
Explanation for what is going on:
You are performing a GROUP BY on staff.department, however your SELECT list contains 2 non-grouping columns staff.workerID, staff.name. In standard sql this is a syntax error, however MySql allows it so the query writers have to make sure that they handle such situations themselves.
Reference: http://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html
In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause.
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause.
The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate.
Starting with MySQL 5.1 the non-standard feature can be disabled by setting the ONLY_FULL_GROUP_BY flag in sql_mode: http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_only_full_group_by
How to fix:
select staff.workerID, staff.name, staff.department, staff.salary
from staff
join (
select staff.department, max(staff.salary) AS biggest
from staff
group by staff.department
) t
on t.department = staff.department and t.biggest = staff.salary
In the inner query, fetch department and its highest salary using GROUP BY. Then in the outer query join those results with the main table which would give you the desired results.
This is the usual case group by with a aggregate function does not guarantee proper row corresponding to the aggregate function. Now there are many ways to do it and the usual practice is a sub-query and join. But if the table is big then performance wise it kills, so the other approach is to use left join
So lets say we have the table
+----------+------+-------------+--------+
| workerid | name | department | salary |
+----------+------+-------------+--------+
| 1 | abc | computer | 400 |
| 2 | cdf | electronics | 200 |
| 3 | gfd | computer | 400 |
| 4 | wer | physics | 300 |
| 5 | hgt | computer | 700 |
| 6 | juy | electronics | 100 |
| 7 | wer | physics | 400 |
| 8 | qwe | computer | 200 |
| 9 | iop | electronics | 800 |
| 10 | kli | physics | 800 |
| 11 | qsq | computer | 600 |
| 12 | asd | electronics | 300 |
+----------+------+-------------+--------+
SO we can get the data as
select st.* from staff st
left join staff st1 on st1.department = st.department
and st.salary < st1.salary
where
st1.workerid is null
The above will give you as
+----------+------+-------------+--------+
| workerid | name | department | salary |
+----------+------+-------------+--------+
| 5 | hgt | computer | 700 |
| 9 | iop | electronics | 800 |
| 10 | kli | physics | 800 |
+----------+------+-------------+--------+
My favorite solution to this problem uses LEFT JOIN:
SELECT m.workerID, m.name, m.department, m.salary
FROM staff m # 'm' from 'maximum'
LEFT JOIN staff o # 'o' from 'other'
ON m.department = o.department # match rows by department
AND m.salary < o.salary # match each row in `m` with the rows from `o` having bigger salary
WHERE o.salary IS NULL # no bigger salary exists in `o`, i.e. `m`.`salary` is the maximum of its dept.
;
This query selects all the workers that have the biggest salary from their department; i.e. if two or more workers have the same salary and it is the bigger in their department then all these workers are selected.
Try this:
SELECT s.workerID, s.name, s.department, s.salary
FROM staff s
INNER JOIN (SELECT s.department, MAX(s.salary) AS biggest
FROM staff s GROUP BY s.department
) AS B ON s.department = B.department AND s.salary = B.biggest;
OR
SELECT s.workerID, s.name, s.department, s.salary
FROM (SELECT s.workerID, s.name, s.department, s.salary
FROM staff s
ORDER BY s.department, s.salary DESC
) AS s
GROUP BY s.department;

Join 2 tables, print data from 2nd table when joined rows occur

The case:
I have 2 tables, 'contracts' and 'members' tied with contract.id = members.cid.
Each contract has one main member and several secondary members (usually the children and spouse of the main member). The main member's details (name, address etc) are stored in table contracts whereas, extra members details are kept in table members. (bad logic, i know but this was a mess to begin with and i need time to redesign the db and website)
The desired output:
When I run a batch print of all contracts (lets say, every Friday) I need to also print a copy of the contract for each member, too but with the member's details on the contract instead of the main member.
The question:
How does this translate into a mysql query? Ok, its a left join, but how do I say "print data from table members instead of contracts for the joined rows"?
Main fields that occur in the 2 tables are name + surname, those should be enough for a draft query example.
Example tables and data:
contracts
-------------------------
id | name | surname |
-------------------------
1 | Tom | Jones |
2 | Jamie | Oliver |
members
--------------------------------
id | cid | name | surname |
--------------------------------
1 | 1 | Jack | Jones |
2 | 1 | Anne | Jones |
3 | 2 | Cathy | Wilson |
So the results I want shoudld be:
cid | name | surname |
--------------------------
1 | Tom | Jones |
1 | Jack | Jones |
1 | Anne | Jones |
2 | Jamie | Oliver |
2 | Cathy | Wilson |
If i write
SELECT c.name as name, c.surname as surname, m.name as name, m.surname as surname
FROM contracts c
join members m on c.id = m.cid
I simply end up with
name and name_1, surname and surname_1 but I want ALL names to fall under name and likewise for all other matching columns.
Hope this works :::
select c1.id, c1.name, c1.surname
from contracts c1
union
(Select m.id, m.name, m.surname
from
members m left join contracts c on (c.id = m.cid))
This is what I finally did and it worked (field names are different but the syntax is what matters):
SELECT u.id, u.onoma_u, u.name_u,
coalesce(u.programa, aa.programa) as programa,
coalesce(u.barcode, aa.barcode) as barcode,
coalesce(u.plan, aa.plan) as plan,
coalesce(u.im_exp, aa.im_exp) as im_exp,
coalesce(u.symb, aa.symb) as symb
FROM (SELECT a1.id, a1.onoma_u, a1.name_u, a1.programa, a1.barcode, a1.plan, a1.im_exp, a1.symb
FROM aitisi a1
UNION
SELECT a2.id, m.name, m.surname, NULL, NULL, NULL, NULL, NULL
FROM members m
JOIN aitisi a2 ON a2.id = m.symbid) u
JOIN aitisi aa ON aa.id = u.id;
I used aliases and NULLS as dummy fields to fill in the blanks.