I am trying to fetch a list of salesmen with following details
EmpId, Salesman who got rating A for consecutive 3 year.
I've written the following query but getting an error.
SELECT salesman, empid
FROM Sales_temp
where rating = 'A'
AND(select max(year), min(year) from Sales_temp
having (max(year)-min(year) = 3))
AND Count(year)=3
To filter data of 3 consecutive years for 'A' rating I have used the following logic:
difference of max(year) for A rating minus min(year) for A rating = 3
and count(year)=3. However, I'm getting an error:
scalar sub query can't have more than one column
please suggest.
The only way I think of to do this in MySQL would be to self join your table twice, and then aggregate by salesman. Then, assert whether or not a salesman has at least one record with three consecutive A ratings:
SELECT
s1.salesman, s1.empid
FROM Sales_temp s1
INNER JOIN Sales_temp s2
ON s1.empid = s2.empid AND s2.year = s1.year + 1
INNER JOIN Sales_temp s3
ON s2.empid = s3.empid AND s3.year = s2.year + 1
GROUP BY
s1.salesman, s1.empid
HAVING
SUM(CASE WHEN s1.rating = 'A' AND s2.rating = 'A' AND s3.rating = 'A'
THEN 1 ELSE 0 END) > 0;
Demo
The intermediate joined table would be quite ugly, but it suffices to say that it gives each record in Sales_temp a chance to pair up with the next year, and then the year after that. If a triplet can happen, and all three ratings are A quality, then that salesman would appear in the result set.
If you are interested in the years A was achieved
drop table if exists sales_temp;
create table sales_temp(salesman int,empid int, rating varchar(1),year int);
insert into sales_temp values
(1,1,'a',2018),(1,1,'a',2017),(1,1,'a',2016),(1,1,'a',2015),
(2,2,'a',2018),(2,2,'a',2017),
(3,3,'b',2018),(3,3,'a',2017),(3,3,'a',2016),(3,3,'a',2015);
select salesman,empid,r1,r2,r3
from
(
select s.salesman,s.empid,year r1,
(select year from sales_temp s1 where s1.salesman = s.salesman and s1.empid = s.empid and s1.year = s.year -1 and rating = 'a') r2,
(select year from sales_temp s1 where s1.salesman = s.salesman and s1.empid = s.empid and s1.year = s.year -2 and rating = 'a') r3
from sales_temp s
where s.rating = 'a'
) s
where s.r1 is not null and s.r2 is not null and s.r3 is not null ;
+----------+-------+------+------+------+
| salesman | empid | r1 | r2 | r3 |
+----------+-------+------+------+------+
| 1 | 1 | 2018 | 2017 | 2016 |
| 1 | 1 | 2017 | 2016 | 2015 |
| 3 | 3 | 2017 | 2016 | 2015 |
+----------+-------+------+------+------+
3 rows in set (0.00 sec)
Related
Table Structure :
Registration :
uuid | name | total
Rate :
uuid | type | rate
Registration_Rate :
registration | rate
Initial Request is :
select * from registration r
join registration_rate rr on rr.registration = r.uuid
join rate rt on rt.uuid = rr.rate
group by r.name, rt.type
My SQL result from two table (registration & rate ) is :
uuid | name | rate | type
1 | AAA | 15 | U
2 | BBB | 20 | U
3 | CCC | 300 | F
4 | AAA | 250 | F
I would like to have something like this (if a rate's type 'F' exists then display instead)
uuid | name | rate | type
2 | BBB | 20 | U
3 | CCC | 300 | F
4 | AAA | 250 | F
Thanks
Edited :
I have tried another solution which works
select uuid, name, rate, (case rt.type when 2 then 2 else 1 end ) as type
from registration r
join registration_rate rr on rr.registration = r.uuid
join rate rt on rt.uuid = rr.rate
group by r.name, rt.type
If it's an F row return it. Or, use NOT EXISTS to verify no other row with same name has an F.
select t1.*
from tablename t1
where type = 'F'
or not exists (select * from tablename t2
where t2.name = t1.name
and t2.type = 'F')
Alternative solution:
select t1.*
from tablename t1
join (select name, min(type) type
from tablename
group by name) t2
ON t1.name = t2.name and t1.type = t2.type
Try this (I suggest main idea)
SELECT t.uuid,
t.name,
IFNULL(MAX(t.F_type), MAX(t.not_F_type)) AS "type",
IFNULL(MAX(t.F_rate), MAX(t.not_F_rate)) AS "rate"
FROM
(
SELECT r.uuid,
r.name,
CASE rt.type WHEN 'F' THEN rt.type END AS F_type,
CASE WHEN rt.type <> 'F' THEN rt.type END AS not_F_type,
CASE rt.type WHEN 'F' THEN rt.rate END AS F_rate,
CASE WHEN rt.type <> 'F' THEN rt.rate END AS not_F_rate
FROM registration AS r
JOIN registration_rate AS rr ON rr.registration = r.uuid
JOIN rate AS rt ON rt.uuid = rr.rate
) as t
GROUP BY t.uuid, t.name;
So, you need to split appropriate columns ("rate", "type") according to your rule (if a rate's type 'F' exists then display instead of others) into two new separate columns using case statement: the first column contains value for F type and the second one contains value for others types. I did it for "type" and "rate" columns. Then I glued together these columns (and records) using group by, aggregation functions and IFNULL statement (you can use others statement here: case, IF, etc).
As I understand the question, this is what you need.
Following up on the question "Select new or returning items for a specified year", I would now like to create statistics on how many items per year are new or returning. Here's my example table:
+---------------------+----------+
| date | item |
+---------------------+----------+
| 2008-11-30 11:15:59 | Plums |
| 2012-11-08 19:42:37 | Lemons |
| 2013-01-30 18:58:07 | Apples |
| 2013-02-12 13:44:45 | Pears |
| 2014-06-08 11:46:48 | Apples |
| 2014-09-01 20:28:03 | Oranges |
+---------------------+----------+
I'm looking for a query which will return the amount of items that have not appeared in previous years, and the amount of items that have already appeared in previous years.
The result should look something along the lines of this:
Year New Items Returning Items
2008 1 0
2012 1 0
2013 2 0
2014 1 1
How can this be achieved?
Well, using the queries you are linking to, I came up with the following (SQL Fiddle):
SELECT m1.UniqYear,
CASE WHEN m2.NewItems IS NULL THEN 0 ELSE m2.NewItems END AS NewItems,
CASE WHEN m3.ReturningItems IS NULL THEN 0 ELSE m3.ReturningItems END AS ReturningItems
FROM
(
SELECT DISTINCT YEAR(s.date) AS UniqYear
FROM MyTable s
) m1 LEFT JOIN
(
SELECT YEAR(s1.date) AS Year, COUNT(*) AS NewItems
FROM MyTable s1
LEFT JOIN MyTable s2 ON s1.item = s2.item AND YEAR(s2.date) < YEAR(s1.date)
WHERE s2.date IS NULL
GROUP BY YEAR(s1.date)
) m2 ON m1.UniqYear = m2.Year
LEFT JOIN
(
SELECT YEAR(s3.date) AS Year, COUNT(*) AS ReturningItems
FROM MyTable s3
INNER JOIN MyTable s4 ON s3.item = s4.item
WHERE YEAR(s4.date) < YEAR(s3.date)
GROUP BY YEAR(s3.date)
) m3 ON m1.UniqYear = m3.Year
ORDER BY m1.UniqYear;
I am using the CASE statements to return 0 instead off null. The first sub query (m1) is used to get the distinct years. The second sub query (m2) is used to get the New Items count. The third sub query (m3) is used to get the Returning Items count. Notice I had to add a group by statement to get the count per year. I had to LEFT JOINed m2 and m3 to m1 because you are not always going to have a NewItems count or a Returning Items for every year.
I'm having some difficulty getting to the bottom of this sql query.
Tables:
--Tickets-- --Finance-- --Access--
id_tickets id_finance id_access
name_tickets id_event id_event
cat_tickets id_tickets id_tickets
sold_finance scan_access
Finance and Access both contain a row for multiple of each ticket type as listed in tickets.
and I'm trying to get:
cat_tickets | total_sold | total_scan
-------------------------------------
single | 3043 | 2571
season | 481 | 292
comp | 114 | 75
-------------------------------------
total | 3638 | 2938
The closest I've been to the result I've used:
SELECT tickets.cat_tickets, COALESCE(SUM(finance.sold_finance), 0) AS total_sold, COALESCE(SUM(access.scan_access), 0) AS total_scan
FROM finance INNER JOIN tickets ON finance.id_tickets = tickets.id_tickets
INNER JOIN access ON access.id_tickets = tickets.id_tickets
WHERE access.id_event = 235 AND finance.id_event = access.id_event
GROUP BY tickets.cat_tickets
ORDER BY tickets.cat_tickets DESC
but that just returns:
cat_tickets | total_sold | total_scan
-------------------------------------
single | 4945 | 4437
season | 954 | 599
comp | 342 | 375
-------------------------------------
total | 6241 | 5411
Any ideas where I could be going wrong?
Thanks!
The problem is the relation between access and finance tables, you have to join them. Even if you LEFT JOIN the table the predicate finance.id_event = access.id_event will make it INNER JOIN. As a work around, use UNION like this:
SELECT
tickets.cat_tickets,
SUM(CASE WHEN a.Type = 'f' THEN num ELSE 0 END) AS total_sold,
SUM(CASE WHEN a.Type = 'a' THEN num ELSE 0 END) AS total_scan
FROM tickets
LEFT JOIN
(
SELECT 'f' Type, id_tickets, sold_finance num
FROM finance f
WHERE id_event = 1
UNION ALL
SELECT 'a', id_tickets, scan_access
FROM access
WHERE id_event = 1
) a ON a.id_tickets = tickets.id_tickets
GROUP BY tickets.cat_tickets;
SQL Fiddle Demo
Although I am fully clear on what you want, just try this query if the result of this is what you are expecting.
SELECT tickets.cat_tickets, COALESCE(SUM(finance.sold_finance), 0) AS total_sold, COALESCE(SUM(access.scan_access), 0) AS total_scan
FROM finance LEFT JOIN tickets ON finance.id_tickets = tickets.id_tickets
LEFT JOIN access ON access.id_tickets = tickets.id_tickets
WHERE access.id_event = 235
GROUP BY tickets.cat_tickets
ORDER BY tickets.cat_tickets DESC
Disclaimer: This query is not tested due to incomplete data on the question.
SELECT z.Cat_tickets,
COALESCE(x.total_sold,0) total_sold,
COALESCE(y.total_scan,0) total_scan
FROM tickets z
LEFT JOIN
(
SELECT a.id_tickets,
a.cat_tickets,
SUM(b.sold_finance) total_sold
FROM tickets a
INNER JOIN finance b
ON a.id_tickets = b.id_tickets
WHERE id_event = 235
GROUP BY a.id_tickets, a.cat_tickets
) x ON z.id_tickets = x.id_tickets
LEFT JOIN
(
SELECT aa.id_tickets,
aa.cat_tickets,
SUM(bb.scan_access) total_scan
FROM tickets aa
INNER JOIN Access bb
ON aa.id_tickets = bb.id_tickets
WHERE id_event = 235
GROUP BY aa.id_tickets, aa.cat_tickets
) y ON z.id_tickets = y.id_tickets
Ok, I have an example table with the following information and query.
First up is the data, with the question following at the end.
Here's the SQL Dump:
http://pastie.org/private/o7zzajdpm6lzcbqrjolgg
Or you can use the included a visual below:
Purchases Table
| id | brand | date |
1 b1 2000-01-01
2 b1 2000-01-03
3 b2 2000-01-04
4 b3 2000-01-08
5 b4 2000-01-14
Owners Table
id | firstname | lastname | purchaseid | itemCoupon | itemReturned | Accessories
1 Jane Doe 1 yes no 4
2 Jane Doe 2 yes no 2
3 Jane Doe 3 no no 1
4 Jane Doe 4 no no 3
5 Jane Doe 5 no yes 6
The Query
SELECT brand, COALESCE( SUM( inTime.Accessories ) , 0 ) AS acessory_sum
FROM purchases
INNER JOIN owners AS person ON person.purchaseid = purchases.id
AND person.firstname = 'Jane'
AND person.lastname = 'Doe'
LEFT JOIN owners AS inTime ON person.id = inTime.id
AND purchases.date
BETWEEN DATE( '2000-01-01' )
AND DATE( '2000-01-05' )
GROUP BY purchases.brand
This gives the following expected result:
| brand | accessory_sum
b1 6
b2 1
b3 0
b4 0
The question
Now, I would like to add to the query:
WHERE itemCoupon = 'yes' OR itemReturned = 'yes'
But this overrides the last join and when I do the same search above I get:
| brand | accessory_sum
b1 6
b2 1
Similarly I still want it to return No results found for 2000-01-04, 2000-01-08 using WHERE itemCoupon = 'yes' OR itemReturned = 'yes'. Removing the WHERE gives me zeros for all brands if I try to do it another way.
Basically I want to keep the way the WHERE behaves but also keep the format that I described in the first example of the expected output.
As it is now, using WHERE destroys the way the last LEFT JOIN works with COALESCE which fills the remaining brand rows with zeros.
Your WHERE turns the outer join into an inner join.
You need to move your additionally condition into the LEFT JOIN condition:
LEFT JOIN owners as inTime
ON person.id = inTime.id
AND purchases.date between purchases.date DATE ('2000-01-01') and DATE ('2000-01-05')
AND (inTime.itemCoupon = 'yes' or inTime.itemReturned = 'yes')
the ON clause when doing a JOIN is similar to the WHERE clause. So instead of trying to use WHERE, just add another AND to your query (and don't forget to use the parenthesis in the OR clause):
SELECT brand,
COALESCE(SUM(Time.purchasedAccessories),0) as acessory_sum
FROM purchases
INNER JOIN owners AS person
ON person.purchaseid = purchases.id
AND person.firstname = 'Jane'
AND person.lastname = 'Doe'
AND (person.itemCoupon = 'yes' OR person.itemReturned = 'yes')
LEFT JOIN owners AS inTime
ON person.id= inTime.id
AND purchases.date
BETWEEN purchases.date
DATE( '2000-01-01' )
AND
DATE( '2000-01-05' )
GROUP BY purchases.brand
I have a table called user_scores as below:
id | af_id | uid | level | record_date
----------------------------------------
1 | 1.1 | 1 | 3 | 2012-01-01
2 | 1.1 | 1 | 4 | 2012-02-01
3 | 1.2 | 1 | 3 | 2012-01-01
4 | 1.2 | 1 | 5 | 2012-03-01
...
I have another table call user_info as below:
uid | forename | surname | gender
-----------------------------------
1 | Homer | Simpson | M
2 | Marge | Simpson | F
3 | Bart | Simpson | M
4 | Lisa | Simpson | F
...
In user scores uid is the user id of a registered user on the system, af_id identifies a particular test a user submits. A user scores a level between 1 - 5 for each test, which can be submitted every month.
My problem is I need to produce an analysis at the end of the year to COUNT the number of users that have achieved each level for a particular test. The analysis is to show a gender split for male and female.
So for example an administrator would select test 1.1 and the system would generate stats based that would COUNT of the total MAX level achieved by each user in the year, with a gender split.
Any help is much appreciated. Thank you in advance.
-
I think I need to clarify myself a bit. Because a user can complete the test multiple times throughout the year, there will be multiple scores for the same test. The query should take the highest level achieved and include this in the count. An example result would be:
Male Results:
level1 | level2 | level3 | level4 | level5
------------------------------------------
2 | 5 | 10 | 8 | 1
I am not certain I get exactly what you mean, but as always I'll have a go. As I understand it you want to know how many people from each gender reached each level in a certain year.
SELECT MaxLevel,
COUNT(CASE WHEN ui.Gender = 'M' THEN 1 END) AS Males,
COUNT(CASE WHEN ui.Gender = 'F' THEN 1 END) AS Females
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY MaxLevel
I've put some sample data on SQL Fiddle so you see if it is what you were after.
EDIT
To transpose the data so levels are along the top and Gender in the rows the following will work:
SELECT Gender,
COUNT(CASE WHEN MaxLevel = 1 THEN 1 END) AS Level1,
COUNT(CASE WHEN MaxLevel = 2 THEN 1 END) AS Level2,
COUNT(CASE WHEN MaxLevel = 3 THEN 1 END) AS Level3,
COUNT(CASE WHEN MaxLevel = 4 THEN 1 END) AS Level4,
COUNT(CASE WHEN MaxLevel = 5 THEN 1 END) AS Level5
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY Gender
Note, that if there are ever more than 5 levels you will need to add more to the select statement, or start building dynamic SQL.
Assuming record_date holds only dates (without time parts):
SELECT
s.maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid,
MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
GROUP BY
s.maxlevel
That will show you only the maximum levels found in the user_scores table. If you have a Levels table where all possible levels (1 to 5) are listed, you could use that table to get a complete list of levels. If some levels are not present in the requested subset of data, the corresponding rows will show 0s in both columns.
Here's the above script with minor changes to show the complete chart of levels:
SELECT
l.level AS maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid, MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
RIGHT JOIN Levels l ON s.maxlevel = l.level
GROUP BY
l.level
Hope this is what your looking for!
Show number of records group by userid and gender of the max score for af_id '1.1'.
select count(*), info.uid, info.gender, max(score.level)
from user_info as info
join user_scores as score
on info.uid = score.uid
where score.af_id = '1.1'
group by info.uid, info.gender;
EDITED based on your edit.
select sum(if(a.gender="M",1,0)) Male_users, sum(if(a.gender="F",1,0)) Female_users
from myTable a where
a.level = (select max(b.level) from myTable b where a.uid=b.uid)
group by af_id.
I typed this in a rush. But it should work or at least get you where you need to go. E.G. if you need to specify time frame, add that.
You need something like
SELECT
uid,
MAX(level)
WHERE
record_date BETWEEN '2012-01-01' AND '2012-12-31'
AND af_id='1.1'
GROUP BY uid
If you need the gender splits then depending on what stat you need per gender you can either add a JOIN on the user_info table into this query (to get the MAX per gender) to wrap this as a sub-query and JOIN on the whole thing.