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.
Related
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)
Here is the table ihave, i was trying days between by joining the same table with left join and group by with min difference. I was not so successful.
Customer|Order|Date
1 | 1 |Date1
1 | 2 |Date2
1 | 3 |Date3
1 | 4 |Date4
2 | 1 |Date1
2 | 2 |Date3
2 | 3 |Date6
3 | 1 |Date3
3 | 2 |Date5
Required is:
Customer|Order|Date |diff
1 | 1 |Date1| 0
1 | 2 |Date2| days_betwen(Date2, Date1)
1 | 3 |Date3| days_betwen(Date3, Date2)
1 | 4 |Date4| days_betwen(Date4, Date3)
2 | 1 |Date1| 0
2 | 2 |Date3| days_betwen(Date3, Date1)
2 | 3 |Date6| days_betwen(Date6, Date3)
3 | 1 |Date3| 0
3 | 2 |Date5| days_betwen(Date5, Date3)
I need suggestion with the logic part!
EDIT: What if the order numbers are not sequential?
In first, you need to join the table to itself by Customer and Order fields. Then use DATEDIFF() function to get days number between two dates.
If the Order column is numbered sequentially then solution is simplest:
SELECT
cur.`Customer` AS `Customer`,
cur.`Order` AS `Order`,
cur.`Date` AS `Date`,
DATEDIFF(cur.`Date`, IFNULL(prv.`Date`, cur.`Date`)) AS `DaysPassed`
FROM
MyTable cur
LEFT JOIN
MyTable prv
ON cur.`Customer` = prv.`Customer` AND cur.`Order` = prv.`Order`+ 1;
If the Order column is not numbered sequentially, but next Order value is greater than previous, then you could use greater than or less than operators. Use GROUP BY clause and an aggregate function to return single row for each order. Note, maybe it will be long!
SELECT
comb.`Customer` AS `Customer`,
comb.`curOrder` AS `Order`,
comb.`curDate` AS `Date`,
DATEDIFF(comb.`curDate`, IFNULL(pr.`Date`, comb.`curDate`)) AS `DaysPassed`
FROM
(SELECT
cur.`Customer` AS `Customer`, cur.`Order` AS curOrder, cur.`curDate` AS `Date`, max(prv.`Order`) AS `prvOrder`
FROM
MyTable cur
LEFT JOIN
MyTable prv
ON cur.`Customer` = prv.`Customer` AND cur.`Order` > prv.`Order`
GROUP BY cur.`Order`, cur.`Customer`) comb
LEFT JOIN
MyTable pr
ON pr.`Customer` = comb.`Customer` AND pr.`Order` = comb.prvOrder;
If you use random order number, then it is possible to use Date column instead of Order in the comb subquery to join records by nearest order dates of same customer.
Good luck!
Oh, my title is not the best one and as English is not my main language maybe someone can fix that instead of downvoting if they've understood the issue here.
Basically i have two tables - tourneyplayers and results. Tourneyplayers is like a side table which gathers together tournament information across multiple tables - results, tournaments, players etc. I want to check duplicates from the results table over column day1_best, from single tournament and return all the tourneyplayers who have duplicates.
Tourneyplayers contain rows:
Tourneyplayers
tp_id | resultid | tourneyid
1 | 2 | 91
2 | 21 | 91
3 | 29 | 91
4 | 1 | 91
5 | 3 | 92
Results contains rows:
Results:
r_id | day1_best
1 | 3
2 | 1
3 | 4
.. | ..
21 | 1
.. | ..
29 | 2
Now tourney with id = 91 has in total 4 results, with id's 1,2,21 and 29. I want to return values which have duplicates, so currently the result would be
Result
tp_id | resultid | day1_best
1 | 2 | 1
2 | 21 | 1
I tried writing something like this:
SELECT *
FROM tourneyplayers
WHERE resultid
IN (
SELECT r1.r_id
FROM results AS r1
INNER JOIN results AS r2 ON ( r1.day1_best = r2.day1_best )
AND (
r1.r_id <> r2.r_id
)
)
AND tourneyid =91
But in addition to values which had the same day1_best it chose two more which did not have the same. How could i improve my SQL or rewrite it?
First you JOIN both tables, so you know how the data looks like.
SELECT *
FROM tourney_players t
JOIN results r
ON t.`resultid` = r.`r_id`;
Then using the same query you GROUP to see what tourneyid, day1_best combination has multiple rows
SELECT `tourneyid`, `day1_best`, count(*) as total
FROM tourney_players t
JOIN results r
ON t.`resultid` = r.`r_id`
GROUP BY `tourneyid`, `day1_best`;
Finally you use the base JOIN and perform a LEFT JOIN to see what rows has a match and show only those rows.
SELECT t.`tp_id`, r.`r_id`, r.`day1_best`
FROM tourney_players t
JOIN results r
ON t.`resultid` = r.`r_id`
LEFT JOIN (SELECT `tourneyid`, `day1_best`, count(*) as total
FROM tourney_players t
JOIN results r
ON t.`resultid` = r.`r_id`
GROUP BY `tourneyid`, `day1_best`
HAVING count(*) > 1) as filter
ON t.`tourneyid` = filter.`tourneyid`
AND r.`day1_best` = filter.`day1_best`
WHERE filter.`tourneyid` IS NOT NULL;
SQL DEMO
OUTPUT
Please try this :
Select tp.tp_id , tp.resultid ,r.day1_best from (Select * from Tourneyplayers
where tourneyid = 91)as tp inner join (select * from Result day1_best in(select
day1_best from result group by day1_best having count(*)>1 ) )as r on tp.resultid
= r.r_id ;
I've written a query that builds a small table of information from a couple of data sources, it uses a self made table to reference the vehicle model for the final group by which is how the data needs to be viewed, however when I group by vehicle it misses out figures in the subquery column from the group by, i.e. if I group by Prefix it shows the correct numbers, grouped by Vehicle hides off some of the data.
The Prefix can relate to a couple of like vehicle models and hence the need to group by vehicle. Can anyone see what I've done wrong easily from the SQL query below please.
SELECT Vehicle, COUNT(`Chassis-No`) AS Stock,
ROUND((100/COUNT(`Chassis-No`)) * SUM(CASE WHEN `Vehicle Age` > '182' THEN 1 ELSE 0 END),1) AS Perc6Months,
ROUND((100/COUNT(`Chassis-No`)) * SUM(CASE WHEN `Vehicle Age` > '365' THEN 1 ELSE 0 END),1) AS Perc12Months,
(SELECT COUNT(VIN_Prefix) FROM Orderdownload
INNER JOIN VehicleMatrix ON (`VIN_Prefix` LIKE 'S%' AND Prefix = LEFT(`VIN_Prefix`,2)) OR (`VIN_Prefix` NOT LIKE 'S%' AND Prefix = LEFT(`VIN_Prefix`,1)) WHERE DealerCode = 'AA12345' AND `VIN_Prefix` = IF(LEFT(`Chassis-No`,1)='S',LEFT(`Chassis-No`,2),LEFT(`Chassis-No`,1))) As Qty
FROM DealerAgedStock
INNER JOIN VehicleMatrix AS VM
ON (`Chassis-No` LIKE 'S%' AND Prefix = LEFT(`Chassis-No`,2)) OR (`Chassis-No` NOT LIKE 'S%' AND Prefix = LEFT(`Chassis-No`,1))
WHERE `DL Dealer Code` = 'AA12345'
GROUP BY Vehicle
Grouped on Vehicle I get the following:
Vehicle | Perc6Months | Perc12Months | Qty
Mondeo | 37.5 | 0 | 2
Grouped on Prefix I get the following:
VIN_Prefix | Perc6Months | Perc12Months | Qty
S1 | 25 | 0 | 2
S2 | 50 | 0 | 2
Ideally it should look this this:
Vehicle | Perc6Months | Perc12Months | Qty
Mondeo | 37.5 | 0 | 4
Where S1 and S2 are relative to the Vehicle Mondeo, thus it gives me the first instance of subquery rather than adding them together.
My question is: why does the Group By not add the figures together properly from the subquery? I need it to add them to have the correct figures...
Consider:
SELECT(count(c.id),
case when(count(c.id) = 0)
then 'loser'
when(count(c.id) BETWEEN 1 AND 4)
then 'almostaloser'
when(count(c.id) >= 5)
then 'notaloser'
end as status,
...
When all is said and done, the query as a whole produces a set of results that look similar to this:
Count | status
--------|-------------
2 | almostaloser //total count is between 2 and 4
--------|-------------
0 | loser // loser because total count = 0
--------|-------------
3 | almostaloser //again, total count between 2 and 4
--------|-------------
What I would like to achieve:
a method to reatain the information from the above table, but add a third column that will give a total count of each status, something like
select count(c.id)
case when(count(c.id) = 0 )
then loser as status AND count how many of the total count does this apply to
results would look similar to:
Count | status |total_of each status |
--------|-------------|---------------------|
2 | almostaloser| 2 |
--------|-------------|---------------------|
0 | loser | 1 |
--------|-------------|---------------------|
3 | almostaloser| 2 |
--------|-------------|----------------------
I've been told this could be achieved using a derived table, but i've not yet been able to get them both, only one or the other.
This can be achieved with this query (you must place your original query as subquery in two places):
SELECT t1.*, t2.total_of_each_status
FROM (
-- put here your query --
) t1
INNER JOIN (
SELECT status, count(*) AS total_of_each_status
FROM (
-- put here your query --
) t2
GROUP BY status
) t2 ON t2.status = t1.status