SQL group by two fields counts for both fields - mysql

I have some MySQL code like this:
select
Country,
if(upper(District) like 'A%', 'A',
if(upper(District) like '%B', 'B','C')) as Field1Type,
count(*) as Salescount
FROM tablename
group by Country, District;
which returns data like this:
Country District Salescount
---------------------------
France A 10
France B 20
France C 45
Germany A 30
Germany B 5
Germany C 50
How can I also get the totals per country, like this? (I know it's not very efficient but it's only a small table.
Country District Salescount CountryTotal
----------------------------------------
France A 10 75
France B 20 75
France C 45 75
Germany A 30 85
Germany B 5 85
Germany C 50 85

you could use a inner join with the total
select
a.Country,
if(upper(a.District) like 'A%', 'A',
if(upper(a.District) like '%B', 'B','C')) as Field1Type,
count(a.*) as Salescount,
t.CountryTotal
FROM tablename as a
INNER JOIN (
Select
Country,
count(*) CountryTotal
FROM tablename
group by Country
) on a.Country = t.country
group by a.Country, a.District;

I would suggest writing the query like this:
select t.Country,
(case when upper(t.District) like 'A%' then 'A',
when upper(t.District) like '%B' then 'B',
else 'C'
end) as Field1Type,
count(*) as Salescount,
t2.CountryCount
FROM tablename t join
(select t2.country, count(*) as CountryCount
from tablename t2
group by t2.country
) t2
on t.country = t2.country
group by t.Country, t.District;
The use of case is better than nested ifs for two reasons. First case is standard SQL. Second, there is no reason to nest the functionality.
By the way, most other databases support window functions which makes this much easier to express.

Related

Aggregate information from one table to another with a different “layout” (mysql)

this is my starting table which provides sales information by Id.
Id
Store_Name
Market
Sales
Main_Product
1
StoreA
Rome
10
a
2
StoreB
Rome
15
b
3
StoreC
Rome
9
c
4
Mag1
Paris
10
a
5
Mag2
Paris
23
b
6
Mag3
Paris
12
c
7
Shop1
London
11
a
8
Shop2
London
31
b
9
Shop3
London
45
c
10
Shop4
London
63
d
In order to build a report and create some dynamic sentences, I will need the dataset to be "paginated" as per below table:
Id
Dimension
Dimension_Name
Sales
Main_Product
1
ShoppingCentre
StoreA
10
a
1
Market
Rome
34
a
2
ShoppingCentre
StoreB
15
b
2
Maket
Rome
34
b
3
ShoppingCentre
StoreC
9
c
3
Market
Rome
34
c
Do you have any tip about how to build the last table starting from the first one?
To sum-up:
The new table will be always by Id
Aggregation of market sales happens at row level where every single shopping centre is located
This is the query that I have built so far but wondering if there is a better and more efficient way to accomplish the same:
with store_temp_table as (
select
id
,Store_Name
,Market
, Main_Product
, sum(Sales) as Sales
from Production_Table
where 1=1
group by
1,2,3,4
)
, market_temp_table as (
select
market
, sum(Sales) as Sales
from Production_Table
where 1=1
group by
1
)
, store_temp_table_refined as(
Select
a.id
,a.Main_Product
, 'ShoppingCentre' as Dimension_Name
,SUM(a.Sales) as Sales
FROM store_temp_table a INNER JOIN
market_temp_table b on a.market = b.market
group by
1,2,3
)
, market_temp_table_refined as (
Select
a.id
,a.Main_Product
, 'Market' as DimensionName
,SUM(b.Sales) as Sales
FROM store_temp_table a INNER JOIN
market_temp_table b on a.market = b.market
group by
1,2,3
)
select * from store_temp_table_refined
union all
select * from market_temp_table_refined
Thank you
Use a CTE that returns the dimensions that you want and cross join it to a query that returns the columns of the table and an additional column with the total sales of each market:
WITH Dimensions(id, Dimension) AS (VALUES
ROW(1, 'ShoppingCentre'),
ROW(2, 'Market')
)
SELECT p.Id,
d.Dimension,
CASE d.id WHEN 1 THEN p.Store_Name ELSE p.Market END Dimension_Name,
CASE d.id WHEN 1 THEN p.Sales ELSE p.MarketSales END Sales,
p.Main_Product
FROM Dimensions d
CROSS JOIN (SELECT *, SUM(Sales) OVER (PARTITION BY Market) AS MarketSales FROM Production_Table) p
ORDER BY p.id, d.id;
Or, with UNION ALL:
SELECT Id,
'ShoppingCentre' Dimension,
Store_Name Dimension_Name,
Sales,
Main_Product
FROM Production_Table
UNION ALL
SELECT Id,
'Market',
Market,
SUM(Sales) OVER (PARTITION BY Market),
Main_Product
FROM Production_Table
ORDER BY Id,
CASE Dimension WHEN 'ShoppingCentre' THEN 1 WHEN 'Market' THEN 2 END;
See the demo.

Displaying the value with the most occurrences

I have two related tables:
(1) people contains names and image files.
(2) cities contains cities they have visited.
people
id name image
1 John NULL
2 Carrie 001.jpg
3 Desmond 002.jpg
4 Harry 003.jpg
5 Paul NULL
cities
id city people_id year_visited
1 Chicago 1 2000
2 Chicago 4 2000
3 Chicago 5 2001
4 Paris 1 2000
5 Paris 2 2002
6 Chicago 4 2002
7 Chicago 1 2001
8 London 1 2004
9 Sydney 5 2001
10 Sydney 1 2002
11 Rio 5 2002
12 London 5 2004
13 Sydney 5 2003
14 Sydney 5 2005
I would like to identify all people without an image, and the city they have visited the most. So the results I am looking for is:
name most_visited_city number_of_visits
John Chicago 2
Paul Sydney 3
I can group_concat the cities they have visited, but not drill down to the single city they visited the most.
All help gratefully appreciated.
The following gets people, cities, and the count:
select p.id, c.city, count(*) as cnt
from people p join
cities c
on p.id = c.people_id
where p.image is null
group by p.id, c.city;
Getting information about the most visited is tricky in MySQL. Here is one method that works if the data is not too large:
select id,
substring_index(group_concat(city order by cnt desc separator '|'), '|', 1) as most_visited_city,
max(cnt) as number_of_times_visited
from (select p.id, c.city, count(*) as cnt
from people p join
cities c
on p.id = c.people_id
where p.image is null
group by p.id, c.city
) pc
group by id;
This query should return the most visited city for each people_id in cities.
SELECT t1.people_id, t2.city, t2.visits
FROM (
SELECT people_id, MAX(visits) AS max_visits
FROM (
SELECT people_id, city, COUNT(*) AS visits
FROM cities
GROUP BY people_id, city) x
GROUP BY people_id) AS t1
JOIN (
SELECT people_id, city, COUNT(*) AS visits
FROM cities
GROUP BY people_id, city) AS t2
ON t1.people_id = t2.people_id AND t1.max_visits = t2.visits
The general structure is based on an answer in SQL Select only rows with Max Value on a Column, but instead of getting the max value of a column in the table, it's using the max value in the subquery that counts visits per city. Unfortunately, it results in an ugly query because you have to repeat that subquery, since MySQL doesn't have CTEs.
Then you can join it with people to get the person's name and filter out the ones with an image.
SELECT p.name, t2.city, t2.visits
FROM (
SELECT people_id, MAX(visits) AS max_visits
FROM (
SELECT people_id, city, COUNT(*) AS visits
GROUP BY people_id, city) x
GROUP BY people_id) AS t1
JOIN (
SELECT people_id, city, COUNT(*) AS visits
GROUP BY people_id, city) AS t2
ON t1.people_id = t2.people_id AND t1.max_visits = t2.max_visits
JOIN people AS p ON p.id = t1.people_id
WHERE p.image IS NULL
DEMO

How to select one column with all distinct values based on some clause

I essentially like to have one query which I'll execute one time and like to have the result (no multiple query execution) and definitely, the query should use simple MySQL structure (no complex/advanced structure to be used like BEGIN, loop, cursor).
Say I've two tables.
1st Table = Country (id(PK), name);
2nd Table = Businessman (id(PK), name, city, country_id(FK))
Like to SELECT all countries, whose businessmen are from distinct cities. No two businessmen exist in one country, who are from the same city. If so, that country will not be selected by the SELECT clause.
Country
id name
1 India
2 China
3 Bahrain
4 Finland
5 Germany
6 France
Businessman
id name city country_id
1 BM1 Kolkata 1
2 BM2 Delhi 1
3 BM3 Mumbai 1
4 BM4 Beijing 2
5 BM5 Paris 6
6 BM6 Beijing 2
7 BM7 Forssa 4
8 BM8 Anqing 2
9 BM9 Berlin 5
10 BM10 Riffa 3
11 BM11 Nice 6
12 BM12 Helsinki 4
13 BM13 Bremen 5
14 BM14 Wiesbaden 5
15 BM15 Angers 6
16 BM16 Sitra  3
17 BM17 Adliya 3
18 BM18 Caen 6
19 BM19 Jinjiang 2
20 BM20 Tubli 3
21 BM21 Duisburg 5
22 BM22 Helsinki 4
23 BM23 Kaarina 4
24 BM24 Bonn 5
25 BM25 Kemi 4
In this respect, China and Finland shouldn't be listed.
I've attempted using count and group by, but no luck.
Can you please help me to build up this query.
Here it is, all you need is to join Businessman table and count cities and distinct cities and if they equal that means all businessmen are from different cities:
SELECT
c.`id`,
c.`name`,
COUNT(b.`id`) AS BusinessmanCount,
COUNT(b.`city`) AS CityCount,
COUNT(DISTINCT b.`city`) AS DistinctCityCount
FROM `countries` c
INNER JOIN Businessman b ON c.`id` = b.`country_id`
GROUP BY c.`id`
HAVING CityCount = DistinctCityCount
For minified version what you exactly need:
SELECT
c.`id`,
c.`name`
FROM `countries` c
INNER JOIN Businessman b ON c.`id` = b.`country_id`
GROUP BY c.`id`
HAVING COUNT(b.`city`) = COUNT(DISTINCT b.`city`)
Well, I think we should have waited for you to show your own query, because one learns best from mistakes and their explanations. However, now that you've got answers already:
Yes, you need group by and count. I'd group by cities to see if I got duplicates. Then select countries and exclude those that have duplicate cities.
select *
from country
where id not in
(
select country_id
from businessmen
group by city, country_id
having count(*) > 1
);
You need either nested aggregations:
select *
from Country
where id in
(
select country_id
from
(
select city, country_id,
count(*) as cnt -- get the number of rows per country/city
from Businessman
group by city, country_id
) as dt
group by country_id
having max(cnt) = 1 -- return only those countries where all counts are unique
)
Or compare two counts:
select *
from Country
where id in
(
select country_id
from Businessman
group by country_id
having count(*) = count(distinct city) -- number of cities is equal to umber of rows
)

selecting top row of each repeating tuple according to some criteria (MYSQL)

I have an example table like this:
Month City Person
8 LHR ABC
10 BEIJING BCS
11 NY JJJ
11 VENICE hghg
11 VENICE KKK
12 NY aa
12 ORL abc
12 ORL bbc
So what I want to achieve is see the city in a specific month with the most number of people visiting
Like the output should be:
12 ORL
11 VENINCE
10 BEIJING
8 LHR
I have tried grouping it like
SELECT month, city , count(*) AS 'no of people visiting'
FROM table
GROUP BY month, city
This table does tell me which city and month location had how many people
visiting but I cannot extract the the top most month and city combination
with respect to a certain month.
Updated Query (with error)
SELECT *
FROM
( SELECT monthname(reservation.PickupDate), location.LocationName, COUNT(*) AS count
FROM reservation NATURAL JOIN location
WHERE reservation.pickupdate >= DATE_ADD(NOW(), INTERVAL - 3 MONTH)
GROUP BY month(reservation.PickupDate), location.LocationName) AS t1
WHERE NOT EXISTS (SELECT 1
FROM reservation R1 NATURAL JOIN location L1
WHERE monthname(R1.PickupDate) = monthname(t1.PickupDate)
GROUP BY month(R1.PickupDate), L1.LocationName)
Starting from your query, you just need to eliminate those rows having another city with more visitors on that month:
SELECT *
FROM
(SELECT `month`, city, count(*) AS cnt
FROM `table`
GROUP BY `month`, city) t1
WHERE NOT EXISTS(SELECT 1
FROM `table` t2
WHERE t2.`month` = t1.`month`
GROUP BY `month`, city
HAVING count(*) > t1.cnt)

JOINING two tables to fetch COUNT from one and SUM from the other

I am working on a project where users work on reports and enter details of their work in database. My database structure has two tables:
tbl_reports - this table contains all details of work performed
report_id user_id date country status
-----------------------------------------------------------------------
0001 abc 2014-05-04 USA checked
0002 abc 2014-05-04 USA checked
0003 abc 2014-05-05 India checked
0004 lmn 2014-05-04 USA checked
0005 lmn 2014-05-04 India checked
0006 xyz 2014-05-06 Taiwan checked
tbl_time - this table contains all details on time repoted by the users, date and country wise
id user_id date country time (hrs)
----------------------------------------------------
01 abc 2014-05-04 USA 4
02 abc 2014-05-05 India 2
03 lmn 2014-05-04 USA 3
04 lmn 2014-05-04 India 2
05 opq 2014-05-05 Belgium 4
As you can see users "abc and "lmn" have tracked all their tasks appropriately while user "xyz" has not tracked his time yet and user "opq" has tracked his time but has no records of reports he has worked on.
Now out of this I want to extract details of this team GROUPING BY "date" and "country" as below:
date country total_report_count total_time_count
-----------------------------------------------------------------------
2014-05-04 India 1 2
2014-05-04 USA 3 7
2014-05-05 Belgium 0 4
2014-05-05 India 1 2
2014-05-06 Taiwan 1 0
Which means irrespective of which user has tracked his reports or time, I need to generate team report for worked done in which country on which date , its counts and total time tracked.
Now I was able to find total_time_count report using below code:
CREATE VIEW vw_teamreport AS
SELECT
tb1.date , tb1.country,
SUM(tb1.time) AS total_time_count
FROM tbl_time tb1
LEFT JOIN tbl_reports tb2
ON tb2.report_id IS NULL
GROUP BY tb1.date, tb1.country
ORDER BY tb1.date, tb1.country;
Need help to complete the problem, and I am using MYSQL (In case if FULL JOIN is required, FULL JOIN keyword is not supported)
Because there's no FULL JOIN you'll need a query to pull out all the distinct date/country combinations from the UNION of these two tables. Or, you'll need some other query to generate the full list of dates and countries. Call this query A.
You need to write two separate aggregating queries. One will aggregate the hours by date and country and the other will aggregate the reports by date and country. Call these queries B and C.
Then you need to do
SELECT whatever, whatever
FROM (
/*query A*/
) AS a
LEFT JOIN (
/*query B*/
) AS b ON a.date=b.date AND a.country=b.country
LEFT JOIN (
/*query C*/
) AS c ON a.date=c.date AND a.country=c.country
This will produce a correctly summarized report with all the rows you need, and NULLs where there is missing summary data.
Edit
Sorry, forgot about the nested query view restriction. You'll need to create four views, one for each subquery and one for the join query. So it will be:
CREATE VIEW dates_countries AS
SELECT DISTINCT `date`, country FROM tbl_time
UNION
SELECT DISTINCT `date`, country FROM tbl_reports;
CREATE VIEW time_totals AS
SELECT `date`, country, SUM(time) AS tot
FROM tbl_time
GROUP BY `date`, country
CREATE VIEW report_totals AS
SELECT `date`, country, COUNT(*) AS tot
FROM tbl_reports
GROUP BY `date`, country
And finally this view.
CREATE VIEW team_report AS
SELECT a.`date`, a.country,
c.tot AS total_report_count,
b.tot AS total_time_count
FROM dates_countries AS a
LEFT JOIN time_totals AS b ON a.`date` = b.`date` AND a.country = b.country
LEFT JOIN repoorts_totals AS r ON a.`date` = r.`date` AND a.country = r.country;
You don't have much choice about this when you need a view.
you could do a divide it into two sub-queries with each section providing one of the reporting data like this,
CREATE VIEW vw_teamreport AS
SELECT tt.date, tt.country, t1.total_time_count, t2.total_report_count
FROM
(SELECT distinct tb1.date , tb1.country
FROM tbl_time tb1
UNION
SELECT tb1.date , tb1.country
FROM tbl_reports tb1
) tt
LEFT JOIN
(SELECT tb1.date , tb1.country, SUM(tb1.time) AS total_time_count
FROM tbl_time tb1
GROUP BY tb1.date, tb1.country) t1
ON tt.date = t1.date and tt.country = t1.country
LEFT JOIN
(SELECT tb1.date , tb1.country, COUNT(tb1.country) AS total_report_count
FROM tbl_reports tb1
GROUP BY tb1.date, tb1.country)t2
ON tt.date = t2.date and tt.country = t2.country
The first query provides the union for all time & country. The 2nd and the 3rd query provides the report data.