SQL to count staff working at time with join - mysql

I am new to SQL.
I want to count staff working at a particular time.
The data schema has a Person table and a Shifts table. They are joined by a StaffShifts table which has both a user_id field and a shift_id field.
Each staff member can have many shifts, and each shift can have many staff.
create table Person
(
user_id INT,
rank_id INT,
groupschedule_id INT,
personnum VARCHAR(6),
PRIMARY KEY (user_id)
);
INSERT INTO Person (user_id, rank_id, groupschedule_id, personnum)
VALUES
(1, 1, 1, 'ABC123'),
(2, 1, 2, 'DEF456'),
(3, 2, 3, 'GHI789'),
(4, 1, 1, 'JKL123'),
(5, 3, 2, 'NOP123'),
(6, 1, 3, 'RST789'),
(7, 2, 1, 'WXY789'),
(8, 1, 2, 'ABC432'),
(9, 1, 3, 'DEF789')
;
CREATE TABLE Groupschedule
(
groupschedule_id INT,
shortnm char(20)
);
INSERT INTO Groupschedule
VALUES
(1,'TEAM 1'),
(2,'TEAM 2'),
(3,'TEAM 3')
;
CREATE TABLE Shifts
(
shift_id INT PRIMARY KEY,
shift_start datetime,
shift_end datetime
);
INSERT INTO Shifts
VALUES
(1, '2021-03-08 06:45:00', '2021-03-08 15:00:00'),
(2, '2021-03-08 14:00:00', '2021-03-08 23:00:00'),
(3, '2021-03-08 23:00:00', '2021-04-09 07:00:00')
;
CREATE TABLE Osl
(
shift_id INT,
osl INT,
area char(10),
FOREIGN KEY (shift_id) REFERENCES Shifts(shift_id)
);
INSERT INTO Osl
VALUES
(1,3, 'EAST'),
(2,2, 'EAST'),
(3,2, 'EAST')
;
CREATE TABLE StaffShifts
( shift_id INT,
user_id INT,
FOREIGN KEY (shift_id) REFERENCES Shifts(shift_id),
FOREIGN KEY (user_id) REFERENCES Person(user_id)
);
WHATS BEEN TRIED
I tried to start with retrieving all the staff working at the time with:
SELECT shift_id FROM Shifts WHERE shift_start < '2021-03-08 11:00:00' AND shift_end > '2021-03-08 11:00:00'
INNER JOIN StaffShifts ON Person.user_id=StaffShifts.user_id
On a fiddle this results in an error that references the INNER JOIN but does not elaborate.
I have created a fiddle here https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=1572ed28005766d30a18521557aadb90
UPDATE
I have tried SQL statement:
SELECT shift_id FROM Shifts INNER JOIN StaffShifts ON Person.user_id=StaffShifts.user_id
WHERE shift_start < '2021-03-08 11:00:00' AND shift_end > '2021-03-08 11:00:00'
However this produces and error shift_id is ambiguous.
UPDATE
Since i really want to COUNT the users working a given shift I am trying to return a list of users - using JOINs for the two one to many relationships:
SELECT Person.user_id
FROM Person
INNER JOIN StaffShifts ON Person.user_id=StaffShifts.user_id
INNER JOIN StaffShifts ON Shifts.shift_id=StaffShifts.shift_id
WHERE Shifts.shift_start < '2021-03-08 11:00:00'
AND Shifts.shift_end > '2021-03-08 11:00:00'
But this results in 'Not unique table/alias: 'StaffShifts''
Note I have not tried to use COUNT until I return a list of Persons.

You need to include Person - which you want to count, StaffShifts - where a person is assigned to a shift so you could also join Shifts based on that, and Shifts - where you check the hour range you wish.
SELECT
COUNT(*)
FROM `Person`
INNER JOIN `StaffShifts` ON `Person`.`user_id` = `StaffShifts`.`user_id`
INNER JOIN `Shifts` ON `StaffShifts`.`shift_id` = `Shifts`.`shift_id`
WHERE
Shifts.shift_start < '2021-03-08 11:00:00'
AND Shifts.shift_end > '2021-03-08 14:00:00'
;

Related

SQL including count and multiple where clauses

I have 2 tables.
Table text_to_annotate:
CREATE TABLE text_to_annotate (
ID varchar(3),
text varchar(100));
INSERT INTO text_to_annotate (ID, text)
VALUES
(1, test1),
(2, test2),
(3, test3);
Table annotation_data:
CREATE TABLE annotation_data (
text_ID varchar(3),
annotation_ID varchar(3)
IP varchar(15));
INSERT INTO annotation_data (text_ID, annotation_ID, IP)
VALUES
(1, 0, IP_1),
(2, 1, IP_1),
(3, 2, IP_1),
(1, 1, IP_2),
(2, 2, IP_2),
(3, 3, IP_2),
(3, 0, IP_3),
(3, 0, IP_4),
(3, 2, IP_5);
I want to display an unseen text to an annotator which hasn't been annotated more than 5 times. For example, a new annotator with IP = IP_6 cannot annotate text_ID = 3, only text_ID = 1 and text_ID = 2. An annotator can only annotate unique text_IDs once.
Here's my code, but something isn't quite correct:
SELECT text_to_annotate.ID, text_to_annotate.text
FROM text_to_annotate
WHERE text_to_annotate.ID NOT IN (
SELECT text_ID, count(*)
FROM annotation_data
WHERE IP = '{$ip}'
AND GROUP BY text_ID
HAVING count(*) > 1;
)
ORDER BY RAND()
Here's my answer:
SELECT text_to_annotate.ID, text_to_annotate.text
FROM text_to_annotate
WHERE text_to_annotate.ID NOT IN (
SELECT text_ID, count(*)
FROM annotation_data
WHERE IP = '{$ip}'
)
AND text_to_annotate.ID IN (
SELECT text_ID FROM impact_annotation
GROUP BY text_ID
HAVING COUNT(*) < 5
)
ORDER BY RAND()

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;

Ponderate average MYSQL

We have a little simulator of a tour-operator DB (MYSQL) and we are asked to get a Query that gives us the weighted avg of duration of the tours that we have.
https://en.wikipedia.org/wiki/Weighted_arithmetic_mean
Using subquery I got to this point where I have the days that each tour lasts and the weight of each tour from the total of tours, but I am stuck and don't know how to get the weighted avg from here. I know I have to use another select from the result I already got but I would appreciate some help.
SQLfiddle down here:
http://sqlfiddle.com/#!9/53d80/2
Tables and data
CREATE TABLE STAGE
(
ID INT AUTO_INCREMENT NOT NULL,
TOUR INT NOT NULL,
TYPE INT NOT NULL,
CITY INT NOT NULL,
DAYS INT NOT NULL,
PRIMARY KEY (ID)
);
CREATE TABLE TOUR
(
ID INT AUTO_INCREMENT NOT NULL,
DESCRIPTION VARCHAR(255) CHARACTER SET UTF8 COLLATE UTF8_UNICODE_CI
NOT NULL,
STARTED_ON DATE NOT NULL,
TYPE INT NOT NULL,
PRIMARY KEY (ID)
);
INSERT INTO TOUR (DESCRIPTION, STARTED_ON, TYPE) VALUES
('Mediterranian Cruise','2018-01-01',3),
('Trip to Nepal','2017-12-01',1),
('Tour in Nova York','2015-04-24',5),
('A week at the Amazones','2014-09-11',2),
('Visiting the Machu Picchu','2013-02-19',4);
INSERT INTO STAGE (TOUR, TYPE, CITY, DAYS) VALUES
(1, 1, 38254, 1),
(1, 2, 22460, 3),
(1, 2, 47940, 3),
(1, 2, 42600, 4),
(1, 3, 38254, 1),
(2, 1, 13097, 1),
(2, 2, 29785, 5),
(2, 3, 13097, 1),
(3, 1, 788, 2); ,
(3, 2, 48019, 6),
(3, 3, 788, 1),
(4, 1, 38254, 2),
(4, 2, 8703, 3);,
(4, 3, 38254, 4),
(5, 1, 10453, 1),
(5, 2, 32045, 5),
(5, 3, 10453, 2);
Query:
SELECT
AVG(TD.TOUR_DAYS) AS AVERAGE_DAYS,
COUNT(TD.TOUR_ID) AS WEIGHT
FROM
(
SELECT
TOUR.ID AS TOUR_ID,
SUM(DAYS) AS TOUR_DAYS,
COUNT(STAGE.ID) AS STAGE_DAYS
FROM
TOUR
INNER JOIN
STAGE
ON
TOUR.ID = STAGE.TOUR
GROUP BY
TOUR.ID
) AS TD
GROUP BY
TD.TOUR_DAYS
weigthed avg would be:
(1×7+1×8+2×9+1×12) / (1+1+2+1) = 9
Wheighted AVG can be calculated with SUM(value * wheight) / SUM(wheight). In your case:
SELECT SUM(AVERAGE_DAYS * WEIGHT) / SUM(WEIGHT)
FROM (
SELECT
AVG(TD.TOUR_DAYS) AS AVERAGE_DAYS,
COUNT(TD.TOUR_ID) AS WEIGHT
FROM
(
SELECT
TOUR.ID AS TOUR_ID,
SUM(DAYS) AS TOUR_DAYS,
COUNT(STAGE.ID) AS STAGE_DAYS
FROM
TOUR
INNER JOIN
STAGE
ON
TOUR.ID = STAGE.TOUR
GROUP BY
TOUR.ID
) AS TD
GROUP BY
TD.TOUR_DAYS
) sub
http://sqlfiddle.com/#!9/53d80/4
I'm not 100% sure, but it looks like the following query is doing exactly the same:
SELECT AVG(TOUR_DAYS)
FROM (
SELECT TOUR, SUM(DAYS) AS TOUR_DAYS
FROM STAGE
GROUP BY TOUR
) sub;
Or even without any subqueries:
SELECT SUM(DAYS) / COUNT(DISTINCT TOUR)
FROM STAGE;
That would mean, the requirement should be simplified to "Get average number of days per tour".

Returning records which only have one specific many to many relation

Given this structure
CREATE TABLE locations
(`id` int, `Name` varchar(128))
;
INSERT INTO locations
(`id`, `Name`)
VALUES
(1, 'Location 1'),
(2, 'Location 2'),
(3, 'Location 3')
;
CREATE TABLE locations_publications
(`id` int, `publication_id` int, `location_id` int)
;
INSERT INTO locations_publications
(`id`, `publication_id`, `location_id`)
VALUES
(1, 1, 1),
(2, 2, 1),
(3, 2, 2),
(4, 1, 3)
;
I would like to find only Location 2 based on the fact that it has only one relation with a publication_id = 2.
It should not return location one due to the fact that it has two relation rows.
This is sort of what I'm looking for but of course dosnt work because it limits the relationship to where publication_id = 2.
select * from locations
join locations_publications on locations_publications.location_id = locations.id
where locations_publications.publication_id = 2
group by (locations.location_id)
having count(*) = 1
You can do this with aggregation:
select location_id
from locations_publications
group by location_id
having count(*) = 1
If a location might have multiple records with the same publication, change the having criteria to count(distinct publication_id) = 1
Given your edits, you can use conditional aggregation for that:
select location_id
from locations_publications
group by location_id
having count(*) = sum(case when publication_id = 2 then 1 else 0 end)

Table Architecture Difficulty with Query

I'm working on a practice problem with DDL as follows:
CREATE TABLE people (
id SMALLINT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(50),
last_name VARCHAR(50),
PRIMARY KEY (id)
)
;
CREATE TABLE cd (
id SMALLINT NOT NULL AUTO_INCREMENT,
artist VARCHAR(50),
title VARCHAR(50),
PRIMARY KEY(id),
owner SMALLINT,
FOREIGN KEY (owner) REFERENCES people(id)
)
;
CREATE TABLE lend (
id SMALLINT NOT NULL AUTO_INCREMENT,
cd_id SMALLINT,
lend_to SMALLINT,
FOREIGN KEY (lend_to) REFERENCES people(id),
FOREIGN KEY (cd_id) REFERENCES cd(id),
lend_date DATE DEFAULT '0000-00-00',
PRIMARY KEY(id)
)
;
INSERT INTO people (id, first_name, last_name) VALUES
(1, 'Brett', 'CEO'),
(2, 'Jeff', 'President'),
(3, 'Beta', 'Media'),
(4, 'Casey', 'Content')
;
INSERT INTO cd (id, artist, title, owner) VALUES
(1, 'The xx', 'Coexist', 2),
(2, 'ACDC', 'High Voltage', 1),
(3, 'Bjork', 'Cocoon', 3),
(4, 'Ella Fitzgerald', 'Ella Sings Gershwin', 4),
(5, 'Fever Ray', 'Live in Lulea', 2),
(6, 'Tom Waits', 'Rain Dogs', 4),
(7, 'Howlin Wolf', 'Smokestack Lightning', 1),
(8, 'Tupac', 'Poetic Justice', 4)
;
INSERT INTO lend (id, cd_id, lend_to, lend_date) VALUES
(1, 2, 3, '2014/01/03'),
(2, 3, 1, '2014/04/02'),
(3, 7, 4, '2013/12/22'),
(4, 4, 2, '2014/01/03')
;
I want my query to show who the CD is lent to. I can get the ID from the lend table, but want to display the full name of the individual lending it from the people table. Do I need to rework the design of how the lend table connects to the people table, or just use some sort of case function in the query? Below is my query so-far where I'm getting the l.lent_to and want to be showing the CONCAT(p.first_name, ' ', p.last_name) who the CD is lent to.
SELECT /*cd.id,*/
CONCAT(p.first_name, ' ', p.last_name) 'CD OWNER',
cd.title,
l.lend_to,
p.id ,
(
CASE
WHEN l.lend_to IS NULL
THEN 'Not Lent'
ELSE DATE_FORMAT(l.lend_date, '%m-%d-%Y')
END
) 'LEND DATE',
(
CASE
WHEN l.lend_to IS NULL
THEN 'Not Lent'
ELSE TIMESTAMPDIFF(day, l.lend_date, NOW())
END
) 'DAYS LENT'
FROM
people p
LEFT JOIN cd cd
ON p.id = cd.owner
LEFT JOIN lend l
ON cd.id = l.cd_id
LEFT JOIN lend l1
on p.id = l1.lend_to
;
See if this query gives you the basic information you are looking for
select c.title as 'Title', c.artist as 'Artist', o.first_name as 'Owner',
l.lend_date as 'Lend Date', p.first_name as 'Lender'
from cd c
left outer join people o on c.owner = o.id
left outer join lend l on c.id = l.cd_id
left outer join people p on l.lend_to = p.id
You can add additional switch logic to refine the result, if this is what you are looking for.
I've resolved the issue with a data architecture redesign. Take a look if interested.
http://sqlfiddle.com/#!2/b6158/3