MySQL Query Rollup - mysql

I have a problem. My MySQL query doesn't work. How could I fix this ?
my query
select state, city, sum((sales.retail_price - products.wholesale_price) * sales.quantity) as profit
from products, sales
where sales.product_id = products.product_id
group by rollup (state, city)
order by state, city;
my error
11:39:12 select state, city, sum((sales.retail_price - products.wholesale_price) * sales.quantity) as profit from products, sales where sales.product_id = products.product_id group by rollup (state, city) order by state, city Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(state, city) order by state, city' at line 4 0.000 sec
my schema
-- Create some tables and insert some rows.
create table products (product_id integer, wholesale_price real);
insert into products (product_id, wholesale_price) values
(1, 1.00),
(2, 2.00);
create table sales (product_id integer, retail_price real,
quantity integer, city varchar, state varchar);
insert into sales (product_id, retail_price, quantity, city, state) values
(1, 2.00, 1, 'SF', 'CA'),
(1, 2.00, 2, 'SJ', 'CA'),
(2, 5.00, 4, 'SF', 'CA'),
(2, 5.00, 8, 'SJ', 'CA'),
(2, 5.00, 16, 'Miami', 'FL'),
(2, 5.00, 32, 'Orlando', 'FL'),
(2, 5.00, 64, 'SJ', 'PR');

Try this:
select state, city, sum((sales.retail_price - products.wholesale_price) *
sales.quantity) as profit
from products, sales
where sales.product_id = products.product_id
group by state, city WITH ROLLUP
order by state, city;
You can see here the ROLL UP behaviour

Related

Write MySQL query with out rollup

I have a problem. How can I write this sql statment without roll up?
I want to get the same result, but without rollup. The result should look like the image below.
my query
select state, city, sum((sales.retail_price - products.wholesale_price) *
sales.quantity) as profit
from products, sales
where sales.product_id = products.product_id
group by state, city WITH ROLLUP
order by state is null, city is null, state, city ;
my schema
-- Create some tables and insert some rows.
create table products (product_id integer, wholesale_price real);
insert into products (product_id, wholesale_price) values
(1, 1.00),
(2, 2.00);
create table sales (product_id integer, retail_price real,
quantity integer, city varchar, state varchar);
insert into sales (product_id, retail_price, quantity, city, state) values
(1, 2.00, 1, 'SF', 'CA'),
(1, 2.00, 2, 'SJ', 'CA'),
(2, 5.00, 4, 'SF', 'CA'),
(2, 5.00, 8, 'SJ', 'CA'),
(2, 5.00, 16, 'Miami', 'FL'),
(2, 5.00, 32, 'Orlando', 'FL'),
(2, 5.00, 64, 'SJ', 'PR');
You must use an UNION operation, as follow:
The first query returns the SUM grouped by state and city, the second only for state, and the third (without group by) for all rows
select state, city, sum((sales.retail_price - products.wholesale_price) *
sales.quantity) as profit
from products, sales
where sales.product_id = products.product_id
group by state, city
UNION ALL
select state, NULL, sum((sales.retail_price - products.wholesale_price) *
sales.quantity) as profit
from products, sales
where sales.product_id = products.product_id
group by state
UNION ALL
select NULL, NULL, sum((sales.retail_price - products.wholesale_price) *
sales.quantity) as profit
from products, sales
where sales.product_id = products.product_id;

I want to to find a way to get my appropriate result in 1 mysql query

I have a table name order_history where I store both old_status and new_status of company orders.
the schema of table :
CREATE TABLE order_history (
id int(11) NOT NULL AUTO_INCREMENT,
old_status longtext COLLATE utf8_unicode_ci,
new_status longtext COLLATE utf8_unicode_ci,
created_at datetime NOT NULL,
order_id int(11) DEFAULT NULL,
PRIMARY KEY (id)
}
The insert to populate is :
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (1, '56', '714', '2020-12-20 21:37:54', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (2, '714', '61', '2020-12-20 21:37:56', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (3, '61', '713', '2020-12-20 21:38:17', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (4, '713', '42', '2020-12-20 21:38:26', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (5, '42', '51', '2020-12-20 21:59:17', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (6, '56', '714', '2020-12-20 22:21:27', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (7, '714', '61', '2020-12-20 22:21:29', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (8, '61', '713', '2020-12-20 22:24:28', 94471496);
INSERT INTO order_history (id, old_status, new_status, created_at, order_id) VALUES (9, '713', '42', '2020-12-20 22:24:43', 94471496);
And Now the question I want to find the TIMEDIFF of created_ats between rows that new_status=61 and rows that new_status=42 and old_status=713.
So in the example the affected rows should be (2,4,7,9) , and the right answer will be the TIMEDIFF between rows with ids (2,4) and rows with ids (7,9). But my query returns 3 results instead of 2 and it also calculate the TIMEDIFF between rows (2,9).
How can I exclude this result?
Here is my query:
select *
from (select oschStart.order_id as order_id, TIMEDIFF(oschEnd.created_at, oschStart.created_at) as confirm_time
from (select osch1.order_id, osch1.created_at
from order_history osch1
where osch1.old_status = 713
and osch1.new_status = 42
) oschEnd
join (select osch1.order_id, osch1.created_at
from order_history osch1
where osch1.new_status = 61
) oschStart
on oschStart.order_id = oschEnd.order_id and oschEnd.created_at > oschStart.created_at) order_time;
A simpler approach is to use a correlated sub query
select *,
timediff(
(select created_at from order_history oh1
where oh1.order_id = oh.order_id and
oh1.id > oh.id and
oh1.old_status = '713' and oh1.new_status = '42'
order by oh1.id asc limit 1),oh.created_at) diff
from order_history oh
where new_status = 61;
Why you have the unwanted results?
oschStart will result rows[2,7] and oschEnd will result rows [4,9]. Joining these subqueries will result in 4 rows [(2,4),(2,9),(7,4),(7,9)]. Your condition (on oschStart.order_id = oschEnd.order_id and oschEnd.created_at > oschStart.created_at) will result in these three rows: [(2,4),(2,9),(7,9)]. It wont prune (2,9) because also 9[created_date] > 2[created_date]. So your query will match a oschStart with all oschEnds that occurs after it. But You need it to be matched with the first occurring oschEnd
Solution
Use group by. If you group by your query results on a field and put other fields on your select part, Mysql will fill those fields with first row of that "group". So assuming that order_history is sorted on created_date you may use this query:
select order_time.id , order_time.*
from (
select oschStart.id as id, oschStart.order_id as order_id,
TIMEDIFF(oschEnd.created_at, oschStart.created_at) as confirm_time
from (select osch1.order_id, osch1.created_at
from order_history osch1
where osch1.old_status = 713
and osch1.new_status = 42
) oschEnd
join (select osch1.id as id, osch1.order_id, osch1.created_at
from order_history osch1
where osch1.new_status = 61
) oschStart
on oschStart.order_id = oschEnd.order_id
and oschEnd.created_at > oschStart.created_at)
order_time
group by order_time.id;

How to get the list of products and prices meeting different criteria in a table

I have a pricing table as follows,
Pricing Table
id productId ContractId ageGroup ageFrom ageTo sellingPrice specialPrice
1 1 1 1 0 2 0 0
2 1 1 1 3 13 20 0
3 1 1 2 18 55 80 0
4 1 1 3 56 119 60 0
5 1 1 1 0 2 0 0
6 1 2 2 18 55 85 0
7 2 2 3 55 119 90 0
8 2 2 2 18 55 90 0
I need to find the list of Contract Ids and Ids for given age Group (1-adult or 2-child or 3-senior). For the children the age range (from - to) need to be considered as well.
The following query (1 adult, 2 children with the ages 2 & 4 and 1 senior) seems to be working but returns only the ids matching the age group 1.
SELECT contractId,id
FROM tbl_contract_price cp1
WHERE contractId IN
(SELECT contractId FROM tbl_contract_price cp2
WHERE contractId IN
(SELECT contractId FROM tbl_contract_price cp3
WHERE cp1.ageGroup = 1 AND (cp2.ageGroup = 2 AND cp2.ageFrom <= 2 AND 2 <= cp2.ageTo OR cp2.ageGroup = 2 AND cp2.ageFrom <= 4 AND 4 <= cp2.ageTo ) AND cp3.ageGroup = 3))
Is there anything I am missing?
Based on some assumptions, I have created the following to help you get started. Please note that you will need to enforce your data integrity (i.e., ensuring that for each product, all possible ages are covered by a price, etc.)
I suggest that you use a temporary quote table so that you can have more flexibility on the number of inputs. You can see the data example below. Or, better yet, handle that logic within your Business Logic Layer.
You will need to apply any tie-breaker logic if two contracts yield the same price, etc.
CREATE TABLE Pricing (
ID int not null,
productId int not null,
ContractId int not null,
ageGroup int not null,
ageFrom int not null,
ageTo int not null,
sellingPrice int not null,
PRIMARY KEY (ID)
);
INSERT INTO Pricing (ID, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (1, 1, 1, 1, 0, 2, 0);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (2, 1, 1, 1, 3, 13, 20);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (3, 1, 1, 2, 18, 55, 80);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (4, 1, 1, 3, 56, 119, 60);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (5, 1, 2, 1, 3, 13, 0);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (6, 1, 2, 2, 18, 55, 85);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (7, 2, 2, 3, 55, 119, 90);
INSERT INTO Pricing (id, productId, ContractId, ageGroup, ageFrom, ageTo, sellingPrice) Values (8, 2, 2, 2, 18, 55, 90);
CREATE TABLE ValidDates (
ID int not null,
priceId int not null,
fromDate date not null,
toDate date not null,
PRIMARY KEY (ID)
);
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (1, 1, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (2, 2, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (3, 2, '2018-07-01', '2018-07-31');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (4, 3, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (5, 3, '2018-07-01', '2018-07-31');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (6, 4, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (7, 5, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (8, 5, '2018-07-01', '2018-07-31');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (9, 6, '2018-06-01', '2018-06-30');
INSERT INTO ValidDates (id, priceId, fromDate, toDate) VALUES (10, 6, '2018-07-01', '2018-07-31');
CREATE TABLE Products (
ID int not null,
PRIMARY KEY (ID)
);
CREATE TABLE Quotes (
ID int not null,
age int
);
INSERT INTO Quotes (Id, age) VALUES (1, 70);
INSERT INTO Quotes (Id, age) VALUES (1, 25);
INSERT INTO Quotes (Id, age) VALUES (1, 1);
INSERT INTO Quotes (Id, age) VALUES (1, 4);
Then, you can use the following query to calculate your total price based on the product id, selected date, and your quote id (which has all the ages for the particular quote)
Scenario: tour date = Jun 22, 2018; product = 1, quote = 1 with age = 1, 4, 25, 70
SELECT #tourdate := '2018-06-22', #productid := 1, #quoteid := 1;
First query to show how the relevant information is retrieved
SELECT productid, contractId, ageGroup, ageFrom, ageTo,
SUM(CASE WHEN age BETWEEN ageFrom AND ageTo THEN 1 ELSE 0 END) AS PAXCount, sellingPrice
FROM ValidDates
LEFT JOIN Pricing
ON priceId = Pricing.ID
LEFT JOIN Products
ON productId = Products.ID
LEFT JOIN Quotes
ON Quotes.ID = #quoteid
WHERE (#tourdate BETWEEN fromDate AND toDate) AND productid = #productid
GROUP BY productid, contractid, ageGroup, ageFrom, ageTo, sellingPrice;
second query is built upon the first query, aggregating the total so that you have the total cost for ranking
SELECT contractId, SUM(sellingPrice * PAXCount) FROM (
SELECT productid, contractId, ageGroup,
SUM(CASE WHEN age BETWEEN ageFrom AND ageTo THEN 1 ELSE 0 END) AS PAXCount, sellingPrice
FROM ValidDates
LEFT JOIN Pricing
ON priceId = Pricing.ID
LEFT JOIN Products
ON productId = Products.ID
LEFT JOIN Quotes
ON Quotes.ID = #quoteid
WHERE (#tourdate BETWEEN fromDate AND toDate) AND productid = #productid
GROUP BY productid, contractid, ageGroup, sellingPrice) P
GROUP BY contractid
ORDER BY SUM(sellingPrice * PAXCount)
#LIMIT 1;
You can uncomment the #Limit 1 to get only the cheapest package, but you need to be aware of the limitation
You will need to ensure that your data integrity is enforced, i.e., for each product and date range, all possible age needs to be covered by
Note that because the child aged 0 and the senior aged 70 were not covered by contract id 2, the $85 total is misleading. You can add logic to check if a contract can fulfil all ages (if input count is 4, check if the contract does indeed include four people, etc.)
You might need to clean up the quotes tables as required. It is not the most efficient approach for sure (but it should work according to your requirements).
For example, change the query to something like this:
SELECT #PAXCount := COUNT(*) FROM Quotes WHERE id = #quoteid;
Or you can probably pass that in from your application fairly easily.
Then, check to make sure that the count matches.
SELECT contractId, SUM(sellingPrice * PAXCount) AS TotalPrice, SUM(PAXCount) AS TotalPAXCOUNT
FROM (
SELECT productid, contractId, ageGroup,
SUM(CASE WHEN age BETWEEN ageFrom AND ageTo THEN 1 ELSE 0 END) AS PAXCount, sellingPrice
FROM ValidDates
LEFT JOIN Pricing
ON priceId = Pricing.ID
LEFT JOIN Products
ON productId = Products.ID
LEFT JOIN Quotes
ON Quotes.ID = #quoteid
WHERE (#tourdate BETWEEN fromDate AND toDate) AND productid = #productid
GROUP BY productid, contractid, ageGroup, sellingPrice) P
GROUP BY contractid
HAVING #PAXCount = SUM(PAXCount)
ORDER BY SUM(sellingPrice * PAXCount)
#LIMIT 1;
This way, only contract id covering all passengers will be shown.
Try it in the DB Fiddler

Calculating DAU average for each country daily using subqueries and group by's

I'm trying to calculate the DAU average for each country for a time period of 1 month. The job of the query is to:
identify unique users
find all users who logged in during last
month
group them into individual days
segment them into their
respective countries
count the average for each country.
So far I've managed steps 1, 2, 3 and 4, but the last one is proving to be tricky.
The query is supposed to first calculate the subquery where it calculates how many active users opened the app in the last month and then group them into days and countries.
After this, it should calculate the average DAU for each country using all 30 days data it has calculated in the subquery.
The result would then be a list of countries and their average DAU.
query so far looks like this:
SELECT Country, AVG(User_ID)
FROM usersession
WHERE User_ID IN
(SELECT count(distinct us.User_ID)
FROM usersession us
WHERE Opened > current_timestamp - interval 1 month
GROUP BY DAY(Opened), Country)
GROUP BY Country ORDER BY Country;
The subquery does steps 1,2,3,4 but the secondary query outside the subquery isn't just working as intended.
Table is as follows (just a short example of the relevant information):
ID | UserID | Opened | Country
-----------------------------------------------
233231 1 2017-11-20 08:00:00 NA
223214 2 2017-11-20 08:53:00 DK
Expected result (around 230 countries total):
Country | Average
------------------
NA 150354
DK 60345
FI 50242
Actual result:
+---------+--------------+
| Country | AVG(User_ID) |
+---------+--------------+
| NULL | 804397.7297 |
| | 746046.7500 |
| BR | 893252.0000 |
| GB | 935599.0000 |
| RU | 993311.0000 |
| US | 735568.0000 |
+---------+--------------+
I think this is what you want:
select
country,
sum(number_of_users) / count(distinct day_of_month) as daily_average_users
from
(
select
country,
day(opened) as day_of_month,
count(distinct user_id) as number_of_users
from
user_session
where
opened > current_timestamp - interval 1 month
group by
country,
day_of_month
) x
group by
country
order by
country;
I tested this on MySQL 5.7:
create table user_session
(
id int,
user_id int,
opened timestamp,
country varchar(2)
);
insert into user_session (id, user_id, opened, country) values ( 1, 100, '2017-12-20 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 2, 100, '2017-12-20 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 3, 100, '2017-12-20 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 4, 100, '2017-12-21 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 5, 100, '2017-12-22 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 6, 200, '2017-12-20 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 7, 300, '2017-12-21 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 8, 400, '2017-12-20 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values ( 9, 500, '2017-12-20 08:00:00', 'NA');
insert into user_session (id, user_id, opened, country) values (10, 600, '2017-12-20 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (11, 600, '2017-12-21 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (12, 700, '2017-12-20 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (13, 800, '2017-12-20 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (14, 800, '2017-12-21 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (15, 800, '2017-12-21 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (16, 900, '2017-12-20 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (17, 900, '2017-12-20 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (18, 900, '2017-12-22 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (19, 900, '2017-12-22 08:00:00', 'DK');
insert into user_session (id, user_id, opened, country) values (19, 1000, '2017-12-22 08:00:00', 'DK');
Results:
+---------+---------------------+
| country | daily_average_users |
+---------+---------------------+
| DK | 2.6667 |
| NA | 2.3333 |
+---------+---------------------+
2 rows in set (0.00 sec)
For this to be a proper daily average you would need every day of the month to be represented in the data (otherwise the average is over the number of days represented). If that isn't the case then we need to calculate the number of days in the period being considered.

using count to count values and nulls

I have the query below
List the number of students graduated in 2009 by department. The results should show 0 for the departments that do not have any student graduated in 2009.
im having trouble with the 2nd part of the question. as of right now my query only shows the department that have students that graduated. i have no idea how to make the table show the departments that dd not have any students graduate.
my query looks like this
select d.name, count(s.major_id) as students from departments d
right join students s on s.major_id = d.id where extract( year from s.graduation_date ) = 2009
group by d.name
and my table looks like this
name students
Math 2
Drama 1
how can i get it to show the other departments with no students graduating?
database is
create table departments (
id integer primary key,
name varchar(255)
);
insert into departments (id, name) values (10, 'Computer Science');
insert into departments (id, name) values (20, 'Math');
insert into departments (id, name) values (30, 'Drama');
create table faculty (
id integer primary key,
name varchar(255),
department_id integer references departments(id)
);
insert into faculty (id, name, department_id) values (1, 'Turing', 10);
insert into faculty (id, name, department_id) values (2, 'Newton', 20);
insert into faculty (id, name, department_id) values (3, 'Einstein', 20);
insert into faculty (id, name, department_id) values (4, 'Brando', 30);
insert into faculty (id, name, department_id) values (5, 'Joe', 30);
insert into faculty (id, name, department_id) values (6, 'Gray', 10);
create table students (
id integer primary key,
name varchar(255),
graduation_date date,
major_id integer references departments(id)
);
insert into students (id, name, graduation_date, major_id) values
(1, 'Joe', null, 10);
insert into students (id, name, graduation_date, major_id) values
(2, 'Amy', '2009-04-22', 20);
insert into students (id, name, graduation_date, major_id) values
(3, 'Max', null, 10);
insert into students (id, name, graduation_date, major_id) values
(4, 'Sue', '2009-01-10', 20);
insert into students (id, name, graduation_date, major_id) values
(5, 'Bob', '2009-03-05', 30);
insert into students (id, name, graduation_date, major_id) values
(6, 'Kim', null, 20);
insert into students (id, name, graduation_date, major_id) values
(7, 'Art', null, 30);
insert into students (id, name, graduation_date, major_id) values
(8, 'Pat', '2005-07-11', 20);
insert into students (id, name, graduation_date, major_id) values
(9, 'Lee', null, 10);
Try with a LEFT JOIN instead of a RIGHT JOIN:
select d.name, count(s.major_id) as students
from departments d
left join students s on s.major_id = d.id and
extract( year from s.graduation_date ) = 2009
group by d.name
Note that extract( year from s.graduation_date ) = 2009 predicate should be placed in the ON clause, otherwise LEFT JOIN becomes an INNER JOIN.
Output:
name | students
=================+============
Computer Science | 0
Drama | 1
Math | 2