Write MySQL query with out rollup - mysql

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;

Related

MySQL Query Rollup

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

SQL SUM and divide linked tables

I have the following tables:
create table Cars
(
CarID int,
CarType varchar(50),
PlateNo varchar(20),
CostCenter varchar(50),
);
insert into Cars (CarID, CarType, PlateNo, CostCenter) values
(1,'Coupe','BC18341','CALIFORNIA'),
(2,'Hatchback','AU14974','DAKOTA'),
(3,'Hatchback','BC49207','NYC'),
(4,'SUV','AU10299','FLORIDA'),
(5,'Coupe','AU32703','NYC'),
(6,'Coupe','BC51719','CALIFORNIA'),
(7,'Hatchback','AU30325','IDAHO'),
(8,'SUV','BC52018','CALIFORNIA');
create table Invoices
(
InvoiceID int,
InvoiceDate date,
CostCenterAssigned bit,
InvoiceValue money
);
insert into Invoices (InvoiceID, InvoiceDate, CostCenterAssigned, InvoiceValue) values
(1, '2021-01-02', 0, 978.32),
(2, '2021-01-15', 1, 168.34),
(3, '2021-02-28', 0, 369.13),
(4, '2021-02-05', 0, 772.81),
(5, '2021-03-18', 1, 469.37),
(6, '2021-03-29', 0, 366.83),
(7, '2021-04-01', 0, 173.48),
(8, '2021-04-19', 1, 267.91);
create table InvoicesCostCenterAllocations
(
InvoiceID int,
CarLocation varchar(50)
);
insert into InvoicesCostCenterAllocations (InvoiceID, CarLocation) values
(2, 'CALIFORNIA'),
(2, 'NYC'),
(5, 'FLORIDA'),
(5, 'NYC'),
(8, 'DAKOTA'),
(8, 'CALIFORNIA'),
(8, 'IDAHO');
How can I calculate the total invoice values allocated to that car based on its cost center?
If the invoice is allocated to cars in specific cost centers, then the CostCenterAssigned column is set to true and the cost centers are listed in the InvoicesCostCenterAllocations table linked to the Invoices table by the InvoiceID column. If there is no cost center allocation (CostCenterAssigned column is false) then the invoice value is divided by the total number of cars and summed up.
The sample data in Fiddle: http://sqlfiddle.com/#!18/9bd18/3
The data structure here isn't perfect, hence we need some extra code to solve for this. I needed to gather the amount of cars in each location, as well as to allocate the amounts for each invoice, depending on whether or not it was assigned to a location. I broke out the totals for each invoice type so that you can see the components which are being put together, you won't need those in your final result.
;WITH CarsByLocation AS(
SELECT
CostCenter
,COUNT(*) AS Cars
FROM Cars
GROUP BY CostCenter
UNION ALL
SELECT
''
,COUNT(*) AS Cars
FROM Cars
),CostCenterAssignedInvoices AS (
SELECT
InvoicesCostCenterAllocations.CarLocation
,SUM(invoicevalue) / CarsByLocation.cars AS InvoiceTotal
FROM Invoices
INNER JOIN InvoicesCostCenterAllocations ON invoices.InvoiceID = InvoicesCostCenterAllocations.InvoiceID
INNER JOIN CarsByLocation on InvoicesCostCenterAllocations.CarLocation = CarsByLocation.CostCenter
WHERE CostCenterAssigned = 1 --Not needed, put here for clarification
GROUP BY InvoicesCostCenterAllocations.CarLocation,CarsByLocation.Cars
),UnassignedInvoices AS (
SELECT
'' AS Carlocation
,SUM(invoicevalue)/CarsByLocation.Cars InvoiceTotal
FROM Invoices
INNER JOIN CarsByLocation on CarsByLocation.CostCenter = ''
WHERE CostCenterAssigned = 0
group by CarsByLocation.Cars
)
SELECT
Cars.*
,cca.InvoiceTotal AS AssignedTotal
,ui.InvoiceTotal AS UnassignedTotal
,cca.InvoiceTotal + ui.InvoiceTotal AS Total
FROM Cars
LEFT OUTER JOIN CostCenterAssignedInvoices CCA ON Cars.CostCenter = CCA.CarLocation
LEFT OUTER JOIN UnassignedInvoices UI ON UI.Carlocation = ''
ORDER BY
Cars.CostCenter
,Cars.PlateNo;

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.

SQL Query To Show Total Each Time SalesMan Changes

This is my table structure with garbage data inserted. What I am wanting to do is for each sales man (Frank and Joe) have a total row underneath them. This is how I want the data to display when returned form my query
This is table structure filled with garbage data, can someone assist me on how to set this up?
Create Table TurribleSetUp
(
ID int,
si varchar(200),
salesmanname varchar(50),
itemsold varchar(100)
)
Insert Into TurribleSetUp Values
(1, 'Home Frank Bad', 'Frank', 'stove'),
(2, 'Internet Frank Left', 'Frank', 'table'),
(3, 'Store Frank Total Store Card', 'Frank', 'stereo')
,(4, 'Store Joe Bad', 'Joe', 'stove'),
(5, 'Store Joe Right', 'Joe', 'stove'),
(6, 'Joe, Person, High Five', 'Joe', 'car')
,(7, 'Frank, Person, High Five', 'Frank', 'car'),
(8, 'Left, Low Five, Joe', 'Joe', 'car')
Try;
select info, total_sale, itemsold piece_of_equipment_sold
from (
select si info, count(itemsold) total_sale, itemsold, salesmanname
from TurribleSetUp
group by si, itemsold, salesmanname
union all
select 'Total for ' + salesmanname, count(itemsold), '' , salesmanname
from TurribleSetUp
group by salesmanname
) x
order by salesmanname, total_sale
I'd do something like this:
select ts.salesmanname, si, count(itemsold) as Cnt, itemsold
from TurribleSetUp ts
group by si, itemsold, ts.salesmanname
union
select salesmanname, 'Totals For '+ Salesmanname as si, count(itemsold) as Cnt, NULL as itemsold
from TurribleSetUp
group by salesmanname
order by ts.salesmanname
I ordered it by Salesman Name. I'm assuming you have some type of number that you'd order it by instead.