Select multiple rows from one table with a count of another - mysql

I am having trouble with a select statement. What I have so far is this -
SELECT COUNT(booked.desk_id),
name,
desk.desk_id,
phone,
fax,
dock,
pc
FROM desk, booked
WHERE desk.desk_id = booked.desk_id
AND booking_id >=1
AND location = "Cheltenham"
Which outputs
"12" "Desk 1" "1" "1" "0" "0" "1"
Which is close to what I want, but there is another desk in the desk table called Desk 2, which is it completely ignoring. And indeed, if there are bookings for Desk 2 it includes their count in what it is showing as a count for Desk 1...
Entire table strucutres is as follows:
table "booked"
INSERT INTO `booked` (`id`, `booking_id`, `desk_id`, `member_id`, `date_booked`) VALUES
(246, 1358121601, 1, 1, 'Monday 14th January at 4:40pm'),
(247, 1358121602, 1, 1, 'Monday 14th January at 4:40pm'),
(248, 1358121604, 1, 1, 'Monday 14th January at 4:40pm'),
(249, 1358121603, 1, 1, 'Monday 14th January at 4:40pm'),
(250, 1358121606, 1, 1, 'Monday 14th January at 4:40pm'),
(251, 1358121605, 1, 1, 'Monday 14th January at 4:40pm'),
(252, 1358121607, 2, 1, 'Monday 14th January at 4:40pm'),
(253, 1358121609, 2, 1, 'Monday 14th January at 4:40pm'),
(254, 1358121608, 2, 1, 'Monday 14th January at 4:40pm'),
(255, 1358121610, 2, 1, 'Monday 14th January at 4:40pm'),
(256, 1358121612, 2, 1, 'Monday 14th January at 4:40pm'),
(257, 1358121611, 2, 1, 'Monday 14th January at 4:40pm');
table "desk"
INSERT INTO `desk` (`location`, `desk_id`, `name`, `phone`, `fax`, `dock`, `pc`) VALUES
('Cheltenham', 1, 'Desk 1', 1, 0, 0, 1),
('Cheltenham', 2, 'Desk 2', 1, 1, 0, 1);
What I need help with is how to correctly structure the statement so it will output individual rows for each desk with it's relevant information.

You are missing a GROUP BY to go along with your aggregate function:
SELECT COUNT(booked.desk_id),
name,
desk.desk_id,
phone,
fax,
dock,
pc
FROM desk
INNER JOIN booked
ON desk.desk_id = booked.desk_id
WHERE booking_id >=1
AND location = "Cheltenham"
GROUP BY name;
In MySQL you do not have to GROUP BY all fields in the select list, but in other RDBMS you would have to use:
SELECT COUNT(booked.desk_id),
name,
desk.desk_id,
phone,
fax,
dock,
pc
FROM desk
INNER JOIN booked
ON desk.desk_id = booked.desk_id
WHERE booking_id >=1
AND location = "Cheltenham"
GROUP BY name, desk.desk_id, phone, fax, dock, pc
Based on your sample data and comment, you can use:
SELECT coalesce(CountDesk, 0) Total,
name,
d.desk_id,
phone,
fax,
dock,
pc
FROM desk d
LEFT JOIN
(
select COUNT(booked.desk_id) CountDesk,
desk_id
from booked
WHERE booking_id >=1
GROUP BY desk_id
) b
ON d.desk_id = b.desk_id
WHERE location = "Cheltenham"
See SQL Fiddle with Demo
If you want to do this without the subquery:
SELECT
Coalesce(count(b.desk_id), 0) Total,
name,
d.desk_id,
phone,
fax,
dock,
pc
FROM desk d
LEFT JOIN booked b
ON d.desk_id = b.desk_id
WHERE booking_id >=1
AND location = "Cheltenham"
GROUP BY name, d.desk_id, phone, fax, dock, pc ;
See SQL Fiddle with Demo

Related

How to calculate active users percentage in SQL

I have two tables;
db_user (user_id,create_date,country_code)
db_payment (user_id,pay_amount,pay_date)
I am trying to find what percentage of users are active in 2021 Feb, among all the users joined in 2021 Jan.
I can separately find the total users created in Jan 2021 and who is active in Feb 2021 (see my below query). Then simply divide the numbers. However, I am trying to find all in one query.
SELECT
count(distinct u.user_id) as active_users_jan_to_feb
from db_user as u
left join db_payment as p
ON u.user_id = p.user_id
where YEAR(STR_TO_DATE(u.create_date, "%Y-%m-%d"))=2021
and MONTH (STR_TO_DATE(u.create_date, "%Y-%m-%d"))=01
and YEAR(STR_TO_DATE(p.pay_date, "%Y-%m-%d"))=2021
and MONTH(STR_TO_DATE(p.pay_date, "%Y-%m-%d"))=02
SELECT
count(distinct u.user_id) as total_users_jan_2021
from db_user as u
where YEAR(STR_TO_DATE(u.create_date, "%Y-%m-%d"))=2021
and MONTH (STR_TO_DATE(u.create_date, "%Y-%m-%d"))=01
I joined two tables to create a master view of what users created in what year/month and their payment year/month. However, I am not sure how to go from this master view to find the percentage of users are created in 2021 Jan and they are active in 2021 Feb. Can you please help me understand how I should approach it?
SELECT
u.user_id
,YEAR(STR_TO_DATE(u.create_date, "%Y-%m-%d")) as create_year
,MONTH (STR_TO_DATE(u.create_date, "%Y-%m-%d")) as create_month
,YEAR(STR_TO_DATE(p.pay_date, "%Y-%m-%d")) as pay_year
,MONTH(STR_TO_DATE(p.pay_date, "%Y-%m-%d")) as pay_month
,p.payment_amount
from db_user as u
left join db_payment as p
ON u.user_id = p.user_id
Here is the table creation and sample data import;
CREATE TABLE db_user
(
user_id int PRIMARY KEY,
create_date TEXT,
country_code TEXT
);
INSERT INTO db_user (user_id, create_date, country_code)
VALUES
(1, '2019-01-01', 'US'),
(2, '2020-02-01', 'US'),
(3, '2021-01-01', 'US'),
(4, '2021-02-01', 'TR'),
(5, '2021-03-01', 'FR'),
(6, '2021-06-01', 'FR'),
(7, '2021-02-11', 'US'),
(8, '2021-02-19', 'TR'),
(9, '2021-01-10', 'US');
CREATE TABLE db_payment
(
user_id int,
payment_amount double,
pay_date TEXT
);
INSERT INTO db_payment (user_id, payment_amount, pay_date)
VALUES
(1, 10, '2019-01-01'),
(1, 10, '2019-02-01'),
(1, 10, '2019-03-01'),
(3, 10, '2021-01-01'),
(3, 10, '2021-02-01'),
(4, 10, '2021-02-01'),
(4, 10, '2021-03-01');
SELECT 100 * COUNT(DISTINCT db_payment.user_id) / COUNT(DISTINCT db_user.user_id) AS percent_active_in_Feb_from_joined_in_Jan
FROM db_user
LEFT JOIN db_payment ON db_payment.user_id = db_user.user_id
AND db_payment.pay_date BETWEEN '2021-02-01' AND '2021-02-28'
WHERE db_user.create_date BETWEEN '2021-01-01' AND '2021-01-31';
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=bb1ac14b5410f65b2922a9f771a8c6db

Identifying users with a downward trend SQL

Trying to identify a list of customers who's quantity decreases from their previous purchase.
In this example we see that with each new purchase Mary's quantity decreases over time. However, while Bob shows a decline, he would not yield in the results because on 9/19 he purchased 8 quantities which is greater than his previous purchase of 5.
I'm trying to figure out a query for this for the life of me I can't seem to get it together
Customer PurchaseDate Quantity
Bob 9/1/2021 10
Bob 9/10/2021 6
Bob 9/18/2021 5
Bob 9/19/2021 8
Mary 9/1/2021 10
Mary 9/10/2021 6
Mary 9/18/2021 5
Mary 9/19/2021 3
Frank 9/1/2021 5
Lucus 9/1/2021 5
Lucus 9/10/2021 6
Lucus 9/18/2021 10
End results should be
Customer
Mary
This is a bit tricky, and to find results that are steadily increasing or decreasing you would probably want to use the MATCH_RECOGNIZE clause, which MySQL doesn't (yet) support. This way you can define a pattern whereby each qty is less than than the previous value. Additionally, you could probably do this with a recursive cte, but that would be outside of my abilities.
Here is what I came up with, with the caveat that it only compares the first and last values:
WITH
tbl (customer, purchasedate, quantity) AS (
SELECT * FROM VALUES
('Bob', '9/1/2021', 10),
('Bob', '9/10/2021', 6),
('Bob', '9/18/2021', 5),
('Bob', '9/19/2021', 8),
('Mary', '9/1/2021', 10),
('Mary', '9/10/2021', 6),
('Mary', '9/18/2021', 5),
('Mary', '9/19/2021', 3),
('Frank', '9/1/2021', 5),
('Lucus', '9/1/2021', 5),
('Lucus', '9/10/2021', 6),
('Lucus', '9/18/2021', 10)
)
SELECT
DISTINCT customer
FROM
tbl
QUALIFY
FIRST_VALUE(quantity) OVER (partition BY customer ORDER BY purchasedate)
> LAST_VALUE(quantity) OVER (PARTITION BY customer ORDER BY purchasedate)
Which gives:
CUSTOMER
Bob
Mary
Or, to get strictly decreasing with a known max, you can chain them all together which gets pretty ugly:
WITH
tbl (customer, purchasedate, quantity) AS (
SELECT * FROM VALUES
('Bob', '9/1/2021', 10),
('Bob', '9/10/2021', 6),
('Bob', '9/18/2021', 5),
('Bob', '9/19/2021', 8),
('Mary', '9/1/2021', 10),
('Mary', '9/10/2021', 6),
('Mary', '9/18/2021', 5),
('Mary', '9/19/2021', 3),
('Frank', '9/1/2021', 5),
('Lucus', '9/1/2021', 5),
('Lucus', '9/10/2021', 6),
('Lucus', '9/18/2021', 10)
)
SELECT
DISTINCT customer
FROM
tbl
qualify
(NTH_VALUE(quantity, 1) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 2) OVER (partition BY customer ORDER BY purchasedate))
and ((NTH_VALUE(quantity, 2) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 3) OVER (partition BY customer ORDER BY purchasedate)) or (NTH_VALUE(quantity, 3) OVER (partition BY customer ORDER BY purchasedate) is null))
and ((NTH_VALUE(quantity,3) OVER (partition BY customer ORDER BY purchasedate) >= NTH_VALUE(quantity, 4) OVER (partition BY customer ORDER BY purchasedate)) or (NTH_VALUE(quantity, 4) OVER (partition BY customer ORDER BY purchasedate) is null))
Which gives:
CUSTOMER
Mary
Though for an unknown amount I would think match_recognize would be the best solution (or you could add in some recursion or a custom function).
SELECT Customer
FROM ( SELECT CASE WHEN Customer = #customer AND Quantity > #quantity
THEN 1
ELSE 0
END AS increase_detected,
#customer := Customer Customer,
PurchaseDate,
#quantity := Quantity Quantity
FROM test
CROSS JOIN ( SELECT #customer := NULL, #quantity := NULL ) init_variables
ORDER BY Customer, PurchaseDate
) subquery
GROUP BY Customer
HAVING NOT SUM(increase_detected);
https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=68b75b0df7fe4b383896e78db0caa569

How do you make a nested select with a restriction (WHERE)

I have a database for a small calendar app in which people are stored in the clients table, dates are stored in the table calendarDate and since the relations are many to many there is a connecting table called client_date which holds both of their ids.
I want to make a nested select to get all the dates for a particular person lets say with id = 2.
I came up with this, but it prints all of the dates and asigns them to the person with that id, instead of just printing the only ones he is asigned to:
SELECT c.username
, c.country
, d.day
, d.month
, d.year
, d.dayOfWeek
, d.weekOfYear
, d.emotionId
, d.id
from clients as c
join calendarDate as d
on d.id in (SELECT dateId
from client_date
WHERE clientId in (SELECT id
from clients )
)
where c.id = 2;
Is there something I am doing wrong or is there another way to make a nested select statement ?
My database and data:
DROP DATABASE IF EXISTS calendar;
CREATE DATABASE calendar;
USE calendar;
CREATE TABLE clients(
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
joinedOnDate DATE NOT NULL,
country VARCHAR(100) NOT NULL
);
CREATE TABLE emotions(
id INT NOT NULL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
value DOUBLE
);
CREATE TABLE calendarDate(
id INT AUTO_INCREMENT PRIMARY KEY,
day INT NOT NULL,
month INT NOT NULL,
year INT NOT NULL,
dayOfWeek VARCHAR(20) NOT NULL,
weekOfYear int NOT NULL,
emotionId INT NOT NULL,
CONSTRAINT FOREIGN KEY (emotionId)
REFERENCES emotions( id )
);
CREATE TABLE client_date(
dateId INT NOT NULL,
clientId INT NOT NULL,
CONSTRAINT FOREIGN KEY ( dateId )
REFERENCES calendarDate( id ) ,
CONSTRAINT FOREIGN KEY ( clientId )
REFERENCES clients( id ) ,
UNIQUE KEY( dateId, clientId )
);
USE calendar;
INSERT INTO emotions (id, name, value) VALUES
(0, 'None', 1),
(1, 'Excited', 2.0),
(2, 'Happy', 2.0),
(3, 'Positive', 1.5),
(4, 'Average', 1.0),
(5, 'Mixed', 1),
(6, 'Negative', 0.5),
(7, 'Sad', 0);
INSERT INTO clients (username, joinedOnDate, country) VALUES
('Malazzar', DATE(NOW()), 'Bulgaria'),
('Preslava981', DATE(NOW()), 'Bulgaria'),
('Thusnake', DATE(NOW()), 'United Kingdom');
INSERT INTO calendarDate (day, month, year, dayOfWeek, weekOfYear, emotionId) VALUES
(1, 1, 2019, 'Tuesday', 1, 0),
(2, 1, 2019, 'Wednesday', 1, 0),
(3, 1, 2019, 'Thursday', 1, 0),
(4, 1, 2019, 'Friday', 1, 0),
(5, 1, 2019, 'Saturday', 1, 0),
(6, 1, 2019, 'Sunday', 1, 0),
(7, 1, 2019, 'Monday', 2, 0),
(8, 1, 2019, 'Tuesday', 2, 0),
(9, 1, 2019, 'Wednesday', 2, 0),
(10, 1, 2019, 'Thursday', 2, 0),
(11, 1, 2019, 'Friday', 2, 0),
(12, 1, 2019, 'Saturday', 2, 0),
(13, 1, 2019, 'Sunday', 2, 0),
(14, 1, 2019, 'Monday', 3, 0);
INSERT INTO client_date (clientId, dateId) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(2, 8),
(2, 9),
(2, 10),
(2, 11),
(2, 12),
(2, 13),
(2, 14);
The output I get for the particular client:
Preslava981 Bulgaria 1 1 2019 Tuesday 1 0 1
Preslava981 Bulgaria 2 1 2019 Wednesday 1 0 2
Preslava981 Bulgaria 3 1 2019 Thursday 1 0 3
Preslava981 Bulgaria 4 1 2019 Friday 1 0 4
Preslava981 Bulgaria 5 1 2019 Saturday 1 0 5
Preslava981 Bulgaria 6 1 2019 Sunday 1 0 6
Preslava981 Bulgaria 7 1 2019 Monday 2 0 7
Preslava981 Bulgaria 8 1 2019 Tuesday 2 0 8
Preslava981 Bulgaria 9 1 2019 Wednesday 2 0 9
Preslava981 Bulgaria 10 1 2019 Thursday 2 0 10
Preslava981 Bulgaria 11 1 2019 Friday 2 0 11
Preslava981 Bulgaria 12 1 2019 Saturday 2 0 12
Preslava981 Bulgaria 13 1 2019 Sunday 2 0 13
Preslava981 Bulgaria 14 1 2019 Monday 3 0 14
Your mistake is here:
WHERE clientId in (SELECT id from clients)
Every client ID exists in the clients table. You want this instead:
WHERE clientId = c.id
You could also use a non-corelated subquery instead, which is easier to read:
on (c.id, d.id) in (select clientid, dateid from client_date)
But as has been mentioned by others, a mere join to client_date would do the same job.
Why not just use joins?
select c.username, c.country, d.*
from clients c join
client_date cd
on cd.clientId = c.id join
calendarDate as d
on d.id cd.dateId
where c.id = 2;
I want to make a nested select to get all the dates for a particular
person lets say with id = 2
This can be done with:
select *
from calendarDate
where day in (
select dateId
from client_date
where clientId = 2
)
An equivalent JOIN query would be:
select d.*
from calendarDate d
join client_date cd
on cd.dateId = d.day
where cd.clientId = 2
View on DB Fiddle
Note: The two queries are only equivalent, if there are no duplicates in the client_date table.
If you want to select data from more than one table, then you actualy need to use a JOIN. In that case asking for how to do it without joins, wouldn't make sense.

Calculating product purchases in a Financial Year | SQL Server

I would like to find out product purchases for 2 financial years (FY16-17 & FY17-18).
To go about it:
OwnerID: 101, the first purchase is in 2014 with 3 purchases in FY17-18.
OwnerID: 102, the first purchase is in 2011 with 1 purchase in FY16-17, 1 purchase in FY17-18.
OwnerID: 103, the first purchase is in 2017 however should not be considered as he's a new customer with only 1 purchase in FY17-18. (i.e. first purchase not considered if new customer)
OwnerID: 104, the first purchase is in 2016 but made 3 more purchases in FY16-17.
Code:
CREATE TABLE Test
(
OwnerID INT,
ProductID VARCHAR(255),
PurchaseDate DATE
);
INSERT INTO Test (OwnerID, ProductID, PurchaseDate)
VALUES (101, 'P2', '2014-04-03'), (101, 'P9', '2017-08-09'),
(101, 'P11', '2017-10-05'), (101, 'P12', '2018-01-15'),
(102, 'P1', '2011-06-02'), (102, 'P3', '2016-06-03'),
(102, 'P10', '2017-09-01'),
(103, 'P8', '2017-06-23'),
(104, 'P4', '2016-12-17'), (104, 'P5', '2016-12-18'),
(104, 'P6', '2016-12-19'), (104, 'P7', '2016-12-20');
Desired output:
FY16-17 FY17-18
-----------------
5 4
I tried the below query to fetch records that aren't first occurrence and there by fetching the count within financial years:
SELECT *
FROM
(SELECT
ROW_NUMBER() OVER(PARTITION BY OwnerID ORDER BY PurchaseDate) AS OCCURANCE
FROM Test
GROUP BY OwnerID, PurchaseDate)
WHERE
OCCURANCE <> 1
However it throws an error:
Msg 102, Level 15, State 1, Line 5
Incorrect syntax near ')'.
The subquery needs to have an alias - try this:
SELECT *
FROM
(SELECT
ROW_NUMBER() OVER(PARTITION BY OwnerID ORDER BY PurchaseDate) AS OCCURRENCE
FROM Test
GROUP BY OwnerID, PurchaseDate) subQry
WHERE
subQry.OCCURRENCE <> 1
I am using IIF to separate the two fiscal years and subquery to filter out those with only one purchase
SELECT SUM(IIF(PurchaseDate >= '2016-04-01' AND PurchaseDate < '2017-04-01',1,0)) AS 'FY16-17',
SUM(IIF(PurchaseDate >= '2017-04-01' AND PurchaseDate < '2018-04-01',1,0)) AS 'FY17-18'
FROM test t1
JOIN (SELECT ownerID, COUNT(*) count
FROM test
GROUP BY ownerID) t2 on t1.ownerID = t2.ownerID
WHERE t2.count > 1

MySQL: Aggregating counts

I'm trying to find how many companies had sales in a specific segment. I've managed to get a count of the sales entries (5), but I can't seem to aggregate by the product segment as well. Please see this simplification:
http://sqlfiddle.com/#!9/685cb/1
CREATE TABLE Table1
(`company` text, `sales` int, `segment` text)
;
INSERT INTO Table1
(`company`, `segment`, `sales`)
VALUES
('ACME',10,100),
('ACME',11,100),
('HAL',10,25),
('HAL',13,25),
('GEN',11,50)
;
SELECT COUNT(company) AS companies,
CASE
WHEN segment IN (10, 11, 12, 13, 14, 15, 16)
THEN 'Product segment A'
WHEN segment IN (20, 21, 22)
THEN 'Product segment B'
WHEN segment IN (30)
THEN 'Product segment C'
END AS grp, SUM(sales) AS sum_sales
FROM Table1
WHERE
(company LIKE '%ACME%'
OR company LIKE '%HAL%'
OR company LIKE '%GEN%'
)
AND
segment IN (10, 11, 12, 13, 14, 15 ,16, 20, 21, 22, 30)
GROUP BY grp
ORDER BY grp
;
The goal is to get "companies" to show 3, as there are three companies that had sales in segment A.
You could use the distinct modifier in the count function to get the number of different entries:
SELECT COUNT(DISTINCT company) AS companies,
-- Here -----^
CASE
WHEN segment IN (10, 11, 12, 13, 14, 15, 16)
THEN 'Product segment A'
WHEN segment IN (20, 21, 22)
THEN 'Product segment B'
WHEN segment IN (30)
THEN 'Product segment C'
END AS grp, SUM(sales) AS sum_sales
FROM Table1
WHERE
(company LIKE '%ACME%'
OR company LIKE '%HAL%'
OR company LIKE '%GEN%'
)
AND
segment IN (10, 11, 12, 13, 14, 15 ,16, 20, 21, 22, 30)
GROUP BY grp
ORDER BY grp
;
SQLFiddle