SQL COUNT EXCLUDE - sql

Hello here's my question
Retrieve the total number of bookings for each type of the services that
have at least three bookings (excluding those cancelled).
i.e. where status = 'open' AND 'done'
I'm not to sure on how to exclude and how to count values in a column?
SELECT Service.type, Service.description,
COUNT (DISTINCT status)
FROM Booking
LEFT JOIN Service
ON Booking.service = Service.type
WHERE status >= 3
EXCLUDE 'cancelled'
GROUP BY status DESC;
CREATE TABLE Booking(
car CHAR(8) ,
on_date DATE NOT NULL,
at_time TIME NOT NULL,
technician CHAR(6) NOT NULL,
service VARCHAR(15) NOT NULL,
status VARCHAR(9)CHECK(status IN ('open','done', 'cancelled')) DEFAULT 'open' NOT NULL,
note VARCHAR(200) ,
rating INTEGER CHECK(rating IN('0','1','2','3','4','5')) DEFAULT '0' NOT NULL,
feedback VARCHAR(2048) ,
PRIMARY KEY (car, on_date, at_time),
FOREIGN KEY (car) REFERENCES Car (cid)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (technician) REFERENCES Technician (tech_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (service) REFERENCES Service (type)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE Service(
type VARCHAR(15) PRIMARY KEY,
description VARCHAR(2048)
);

It will be faster to aggregate first and join later. Fewer join operations. Hence the subquery:
SELECT s.type, s.description, b.ct
FROM (
SELECT service, count(*) AS ct
FROM booking
WHERE status <> 'cancelled'
GROUP BY 1
HAVING count(*) > 2
) b
JOIN service s ON s.type = b.service;
Since you enforce referential integrity with a foreign key constraint and service is defined NOT NULL, you can as well use [INNER] JOIN instead of a LEFT [OUTER] JOIN in this query.
It would be cleaner and more efficient to use an enum data type instead of VARCHAR(9) for the status column. Then you wouldn't need the CHECK constraint either.
For best performance of this particular query, you could have a partial covering index (which would also profit from the enum data type):
CREATE INDEX foo ON booking (service)
WHERE status <> 'cancelled';
Every index carries a maintenance cost, so only keep this tailored index if it actually makes your query faster (test with EXPLAIN ANALYZE) and it is run often and / or important.

select s.type, s.description, count(*)
from
booking b
inner join
service s on b.service = s.type
where status != 'cancelled'
group by 1, 2
having count(*) >= 3
order by 3 desc;

How about:
SELECT Service.type, Service.description, COUNT (status)
FROM Booking
LEFT JOIN Service ON Booking.service = Service.type
WHERE status != 'cancelled'
GROUP BY Service.type, Service.description
HAVING COUNT(status) >= 2;
The service attributes have to be grouped as well.
Filtering by aggregate, here COUNT(status), is what the HAVING clause does.

Related

Sum two SQLite columns, when they're subqueries

I have a table of receipts. Each one is associated with a service, and each person is obligated to pay equally for it, except when they are assigned an extra fee that can be activated/deactivaded (0/1). So I used a subquery to get the extra amount they have to pay only if that fee is active; the table 'fees' contains the user_id, the service_id, the extra amount and the active flag. And then, I should get the total per person, adding the extra fee (if any) to the subtotal (receipt total amount minus any active extra fee, and then divided by the number of persons who are obligated to contribute).
SELECT
P.nombre AS person,
S.nombre AS service,
(
SELECT TOTAL(C.value)
FROM fees C
WHERE C.user_id = P.id AND C.service_id = O.service_id AND C.active = 0
) AS fee,
IFNULL(NULL, 23333) AS subtotal,
(fee + subtotal) as total
FROM receipts R
LEFT JOIN obligations O ON O.service_id = R.service_id
LEFT JOIN persons P ON O.user_id = P.id
LEFT JOIN services S ON O.service_id = S.id
WHERE R.id = 3 AND O.active = 0;
Note: 23333 (the subtotal) will be replaced with a '?' and then I'll pass as argument to execute the query with Golang that result that I've already got from another function
Problem occurs at this line
(fee + subtotal) as total
Output: no such column: fee
If I run the query without that line, it will actually return a table with the active extra fees and subtotal, but I'm stuck when trying to create a final column to add those two values.
Thanks!
Edit
Following Stefan's advice, here are the statements I used to create the tables:
CREATE TABLE IF NOT EXISTS persons (id INTEGER PRIMARY KEY, name TEXT NOT NULL, active INTEGER DEFAULT 0); CREATE UNIQUE INDEX per_nom_uindex on persons (name)
CREATE TABLE IF NOT EXISTS services (id INTEGER PRIMARY KEY, name TEXT NOT NULL, active INTEGER DEFAULT 0); CREATE UNIQUE INDEX ser_nom_uindex on services (name)
CREATE TABLE IF NOT EXISTS receipts (id INTEGER PRIMARY KEY, y INTEGER NOT NULL, m INTEGER NOT NULL, service_id INTEGER NOT NULL, amount INTEGER NOT NULL, FOREIGN KEY (service_id) REFERENCES services (id))
CREATE TABLE IF NOT EXISTS fees (id INTEGER PRIMARY KEY, person_id INTEGER NOT NULL, service_id INTEGER NOT NULL, amount INTEGER NOT NULL, active INTEGER DEFAULT 0, FOREIGN KEY(person_id) REFERENCES persons(id), FOREIGN KEY(service_id) REFERENCES services(id))
CREATE TABLE IF NOT EXISTS obligations (id INTEGER PRIMARY KEY, person_id INTEGER NOT NULL, service_id INTEGER NOT NULL, active INTEGER DEFAULT 0, FOREIGN KEY(person_id) REFERENCES persons(id), FOREIGN KEY(service_id) REFERENCES services(id))
Consider moving the subquery from SELECT to JOIN clause (often called derived table) and adjust it with GROUP BY aggregation on user_id and service_id. Doing so, this allows you to reference the column as needed and even avoid rowwise aggregation (unless the SQLite engine runs it as a single aggregation under the hood).
SELECT
P.nombre AS person,
S.nombre AS service,
C.fee, -- REFERENCE SUBQUERY COLUMN
IFNULL(?, 23333) AS subtotal,
C.fee + IFNULL(?, 23333) as total -- REPEAT NEEDED EXPRESSION
FROM receipts R
LEFT JOIN obligations O
ON O.service_id = R.service_id
LEFT JOIN persons P
ON O.user_id = P.id
AND O.active = 0 -- MOVED FROM WHERE CLAUSE
LEFT JOIN services S
ON O.service_id = S.id
LEFT JOIN (
SELECT user_id,
service_id,
TOTAL(value) AS fee
FROM fees
WHERE active = 0
GROUP BY user_id,
service_id
) C ON C.user_id = P.id
AND C.service_id = O.service_id
WHERE R.id = 3

SELECT values where their foreign keys are duplicate

Let's say that we have clients and providers. A client can have multiple providers (like the internet, phone, TV etc) and I would like to find clients' names who have multiple providers.
create table clients
(
client_id char(8) not null,
client_name varchar(80) not null,
contract char(1) not null,
primary key (client_id)
)
create table client_provider
(
provider_id char(11) not null,
client_id char(8) not null,
primary key (provider_id, client_id),
foreign key (provder_id) references providers ON DELETE CASCADE,
foreign key (client_id) references clients ON DELETE CASCADE
);
Therefore, even without knowing anything about providers, we can know clients with multiple providers by the following relational algebra (just started learning, please correct me if I am wrong):
π client_name (
[
σ client_provider2.provider_id ≠ client_provider.provider_id ∧ client_provider2.client_id = client_provider.client_id (ρ client_provider2 (client_provider) ⨯ client_provider))
⨝ clients]
what I have tried so far (returning "not a GROUP BY expression" in line 1):
SQL> select c.client_name
2 from clients c
3 inner join client_provider cp on c.client_id = cp.client_id
4 group by cp.client_id
5 having count(*) > 1;
When using GROUP BY all columns used should either be in GROUP BY or in an aggregate function. To resolve the issue do the following:
Add cp.client_id in SELECT clause
Add c.client_name in GROUP BY clause
SELECT
cp.client_id,
c.client_name
FROM clients c
INNER JOIN client_provider cp
ON c.client_id = cp.client_id
GROUP BY
cp.client_id,
c.client_name
HAVING
COUNT(1) > 1
All non-aggregated columns must be in group by clause, now you know that.
As you commented that you want to display only client_name but not client_id (while it has to be in the group by clause), use current query as source for the final result:
select client_name
from (-- current query begins here
select cp.client_id,
c.client_name
from clients c join client_provider cp on c.client_id = cp.client_id
group by cp.client_id,
c.client_name
having count(*) > 1
-- current query ends here
);
Alternatively, you could do it by using (slightly modified) current query as a subquery:
select cl.client_name
from client cl
where cl.client_id in (select cp.client_id
from client_provider cp
group by cp.client_id
having count(*) > 1
);

Select a product that is on all interventions

Hello my question is simple for some of yours ^^
I've a table product, reference, and intervention. When there is an intervention the table reference make the link between products that we need for the interventions and the intervention.
I would like to know how to do to search products that have made part of all interventions.
This are my tables :
--TABLE products
create table products (
reference char(5) not null check ( reference like 'DT___'),
designation char(50) not null,
price numeric (9,2) not null,
primary key(reference) );
-- TABLE interventions
create table interventions (
nointerv integer not null ,
dateinterv date not null,
nameresponsable char(30) not null,
nameinterv char(30) not null,
time float not null check ( temps !=0 AND temps between 0 and 8),
nocustomers integer not null ,
nofact integer not null ,
primary key( nointerv),
foreign key( noclient) references customers,
foreign key (nofacture) references facts
);
-- TABLE replacements
create table replacements (
reference char(5) not null check ( reference like 'DT%'),
nointerv integer not null,
qtereplaced smallint,
primary key ( reference, nointerv ),
foreign key (reference) references products,
foreign key(nointerv) references interventions(nointerv)
);
--EDIT :
This is a select from my replacement table
We can see in this picture that the product DT802 is used in every interventions
Thanks ;)
This will show 1 line intervention - products. Is this you are expecting for?
select interventions.nointerv, products.reference
from interventions
inner join replacements on interventions.nointerv = replacements.nointerv
inner join products on replacements.reference = products.reference;
This one?
select products.reference, products.designation
from interventions
inner join replacements on interventions.nointerv = replacements.nointerv
inner join products on replacements.reference = products.reference
group by products.reference, products.designation
having count(*) = (select count(*) from interventions);
Your question is hard to follow. If I interpret it as all nointerv in replacements whose reference contains all products, then:
select nointerv
from replacements r
group by nointerv
having count(distinct reference) = (select count(*) from products);

How to relate tables SQL

I have three tables and i want to relate them, but i don't know what im doing wrong. If the way that im thinking is bad, can you correct me also?
I have clients table with Primary key as ID_c column,
create table clients
(
id_c INTEGER not null,
name VARCHAR2(20),
age INTEGER,
address VARCHAR2(20),
Primary key (id_c)
);
also i have products with primary key as ID_p column.
create table PRODUCTS
(
id_p NUMBER not null,
name_product VARCHAR2(30),
price NUMBER,
duration NUMBER,
primary key (id_p)
);
and now i create third
create table TRANSACTIONS
(
id_t NUMBER not null,
id_c NUMBER not null,
id_p NUMBER not null
primary key (ID_t),
foreign key (ID_c) references CLIENTS (ID_c),
foreign key (ID_p) references PRODUCTS (ID_p)
);
and now i want to see all records that are connected, so im trying to use that:
select * from transactions join clients using (id_c) and join products using (id_p);
but only what works is
select * from transactions join clients using (id_c);
is it relational database or im making something too easy, and too primitive? How can i do that to connect everything?
try this
select *
from transactions
inner join clients on transactions.id_c = clients.id_c
inner join products on transactions.id_p = products.id_p;
Are you just trying to join?
select * from transactions a
join clients b on a.id_c = b.id_c
join products c on a.id_p = c.id_p
If you want to join 3 tables, just write:
SELECT * FROM TRANSACTIONS t JOIN client c on t.id_c = c.id_c JOIN PRODUCTS p on t.id_p = p.id_p

Can I SQL join a table twice?

I have two entities: Proposal and Vote.
Proposal: A user can make a proposition.
Vote: A user can vote for a proposition.
CREATE TABLE `proposal` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
);
CREATE TABLE `vote` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`idea_id` int(11) NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`),
);
Now I want to fetch rising Propsals, which means:
Proposal title
Total number of all time votes
has received votes within the last 3 days
I am trying to fetch without a subSELECT because I am using doctrine which doesn't allow subSELECTs. So my approach is to fetch by joining the votes table twice (first for fetching the total amount of votes, second to be able to create a WHERE clause to filter last 3 days) and do a INNER JOIN:
SELECT
p.title,
COUNT(v.p_id) AS votes,
DATEDIFF(NOW(), DATE(x.updated))
FROM proposal p
JOIN vote v ON p.id = v.p_id
INNER JOIN vote x ON p.id = x.p_id
WHERE DATEDIFF(NOW(), DATE(x.updated)) < 3
GROUP BY p.id
ORDER BY votes DESC;
It's clear that this will return a wrong votes amount as it triples the votes' COUNT(). It's actually , because it creates a cartesian product just as a CROSS JOIN does.
Is there any way I can get the proper amount without using a subSELECT?
Instead, you can create a kind of COUNTIF function using this pattern:
- COUNT(CASE WHEN <condition> THEN <field> ELSE NULL END)
For example...
SELECT
p.title,
COUNT(v.p_id) AS votes,
COUNT(CASE WHEN v.updated >= DATEADD(DAY, -3, CURRENT_DATE()) THEN v.p_id ELSE NULL END) AS new_votes
FROM
proposal p
JOIN
vote v
ON p.id = v.p_id
GROUP BY
p.title
ORDER BY
COUNT(v.p_id) DESC
;