I have these 3 tables :
1) Sailors (sid:INT, sname:VARCHAR(30), rating:INT, age:INT)
2) Boats (bid:INT, bname:VARCHAR(30), color:VARCHAR(10))
3) Reserves (bid:INT, sid:INT, day:DATE)
I want to write a query which displays the number(bid) of every boat that has been reserved by all the sailors at least once/
So far I wrote this :
SELECT b.bid, b.bname
FROM boats as b , sailors as s , reserves as r
WHERE r.bid = b.bid and r.sid = s.sid
GROUP BY r.bid
HAVING count(r.sid) = count(s.sid)
It seems the right answer to me but after trying it on MYSQL id didn't work.
Have you tried comparing the count of boat reserves to count of sailors like this?
HAVING count(r.sid) = (Select count(sid) from sailors)
you also need to make sure that count(r.sid) is a unique count of bid and sid incase the same sailor reserved the same boat more than once.
SELECT g.bid,
g.name
FROM
(SELECT b.bid, b.name, s.sid
FROM boats AS b, reserves AS r, sailors AS s
WHERE b.bid = r.bid AND r.sid = s.sid
GROUP BY b.bid, b.name, s.sid
) AS g
GROUP BY g.bid, g.name
HAVING COUNT(g.bid) = (SELECT COUNT(s.SID) FROM sailors AS s)
There are several query patterns that will return the specified result.
Assuming that the sid column in the Reserves table is a FOREIGN KEY referencing Sailors(sid), we can use a distinct count of sid from the Reserves table for a given boat, and compare that to a distinct count of sid from Sailors.
For example:
SELECT b.bid
, b.bname
FROM Boats b
JOIN ( SELECT rr.bid
, COUNT(DISTINCT rr.sid) AS cnt
FROM Reserves rr
GROUP BY rr.bid
) r
ON r.bid = b.bid
JOIN ( SELECT COUNT(DISTINCT ss.sid) AS cnt
FROM Sailors ss
) s
ON s.cnt = r.cnt
The inline view aliased as r gets us a count of distinct sid values for each boat, from the Reserves table.
The inline view aliased as s gets us a count of distinct sid values from Sailors. If sid is the PRIMARY KEY or UNIQUE KEY of Sailors (which is what we expect, it's the normative pattern) we can omit the DISTINCT keyword. The question did not indicate if this was the case, so rather than making a wrong assumption, we write a query that will work in the more general case.)
This query did assume that there was a foreign key constraint... that rows in Reserves would not have a non-NULL value that didn't reference a row in Sailors. If that's not the case, we can modify the inline view query r, so we get a distinct count of sid values that appear in the Sailors table.
This can replace the inline view query r above:
( SELECT rr.bid
, COUNT(DISTINCT rr.sid) AS cnt
FROM Reserves rr
JOIN Sailors rs
ON rs.sid = rr.sid
GROUP BY rr.bid
) r
This query pattern can easily be adapted to check conditions other than ALL sailors.
And there are other query patterns that can achieve an equivalent result.
You Can Check This out :)
===============================================
CREATE TABLE Sailors (sid INT, sname VARCHAR(30), rating INT, age INT)
INSERT INTO Sailors values (1,'S1',100,20);
INSERT INTO Sailors values (2,'S2',100,20);
INSERT INTO Sailors values (3,'S3',100,20);
INSERT INTO Sailors values (4,'S4',100,20);
INSERT INTO Sailors values (5,'S5',100,20);
CREATE TABLE Boats (bid INT, bname VARCHAR(30), color VARCHAR(10))
INSERT INTO Boats values (1,'B1','Blue');
INSERT INTO Boats values (2,'B2','Red');
INSERT INTO Boats values (3,'B3','Green');
INSERT INTO Boats values (4,'B4','Blue');
INSERT INTO Boats values (5,'B5','Green');
CREATE TABLE Reserves (bid INT, sid INT, day DATE)
INSERT INTO Reserves values (1,1,'2015-01-01');
INSERT INTO Reserves values (1,2,'2015-01-01');
INSERT INTO Reserves values (1,3,'2015-01-01');
INSERT INTO Reserves values (1,4,'2015-01-01');
INSERT INTO Reserves values (1,5,'2015-01-01');
INSERT INTO Reserves values (2,1,'2015-01-01');
INSERT INTO Reserves values (2,2,'2015-01-01');
INSERT INTO Reserves values (2,3,'2015-01-01');
INSERT INTO Reserves values (2,4,'2015-01-01');
INSERT INTO Reserves values (2,5,'2015-01-01');
INSERT INTO Reserves values (2,3,'2015-01-02');
INSERT INTO Reserves values (2,4,'2015-01-03');
INSERT INTO Reserves values (2,5,'2015-01-04');
INSERT INTO Reserves values (3,1,'2015-01-02');
INSERT INTO Reserves values (4,1,'2015-01-03');
INSERT INTO Reserves values (5,1,'2015-01-04');
===============================================
SELECT Reserves.bid , Boats.bname FROM Reserves , Boats
WHERE Reserves.bid = Boats.bid
GROUP BY Reserves.bid , Boats.bname
HAVING COUNT (DISTINCT Reserves.sid) = (SELECT COUNT(sid) FROM Sailors)
Result ===>
BID BNAME
---------------
1 B1
2 B2
Hope it will help :)
Related
Question: Write a SQL query to find and display a customer who made 2 consecutive orders in the same category?
I am struggling with the answer. Any help would be appreciated.
Queries:
CREATE TABLE customers (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name TEXT,
email TEXT);
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
customer_id INTEGER,
item TEXT,
price REAL,
ORDER_DATE DATETIME,
category TEXT);
INSERT INTO customers (name, email) VALUES ("Doctor Who", "doctorwho#timelords.com");
INSERT INTO customers (name, email) VALUES ("Harry Potter", "harry#potter.com");
INSERT INTO customers (name, email) VALUES ("Captain Awesome", "captain#awesome.com");
INSERT INTO orders (customer_id, item, price,ORDER_DATE,category)
VALUES (1, "Sonic Screwdriver", 1000.00,'21-04-15 09.00.00','tools');
INSERT INTO orders (customer_id, item, price,ORDER_DATE,category)
VALUES (1, "Light", 1000.00,'21-10-15 09.00.00','tools');
INSERT INTO orders (customer_id, item, price,ORDER_DATE,category)
VALUES (2, "High Quality Broomstick", 40.00,'20-12-20 09.00.00','cleaner');
INSERT INTO orders (customer_id, item, price,ORDER_DATE,category)
VALUES (3, "TARDIS", 1000000.00,'21-01-20 09.00.00','other');
Step: 1
First of all, you add a foreign key in the column containing the customer id of the order table, then after that add the customers and orders tables together.
Step: 2
After adding both tables together run this query and you will get your result.
SELECT DISTINCT orders.category , customers.id,customers.name,customers.email FROM customers JOIN orders ON customers.id= orders.customer_id WHERE orders.category in ( select category from orders group by category having count(*) >= 2 )
You can also solve it by using LEAD. Get lead_category and lead_customers_id and filter with category, customers_id
select * from
(SELECT orders.category , orders.item, customers.id,customers.name,customers.email,
LEAD(category) OVER (ORDER BY customers.id ASC) AS lead_category,
LEAD(customers.id) OVER (ORDER BY customers.id ASC) AS lead_customers_id
FROM orders
JOIN
customers ON
orders.customer_id = customers.id) AS T
where category = lead_category and id = lead_customers_id
SELECT DISTINCT t1.customer_id
FROM orders t1
JOIN orders t2 USING (customer_id, category)
WHERE t1.ORDER_DATE < t2.ORDER_DATE
AND NOT EXISTS ( SELECT NULL
FROM orders t3
WHERE t1.customer_id = t3.customer_id
AND t1.category != t3.category
AND t1.ORDER_DATE < t3.ORDER_DATE
AND t3.ORDER_DATE < t2.ORDER_DATE )
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=a26be6164e027b1a4b0aa9a736764da3
I.e. we simply search for a pair of orders for the same customer and category where an order with the same customer but another category not exists between these orders.
Join customers table if needed.
i have MySQL data which is imported from csv file and have multiple duplicate files on it,
I picked all non duplicates using Distinct feature.
Now i need to delete all duplicates using SQL command.
Note i don't need any duplicates i just need to fetch only noon duplicates
thanks.
for example if number 0123332546666 is repeated 11 time i want to delete 12 of them.
Mysql table format
ID, PhoneNumber
Just COUNT the number of duplicates (with GROUP BY) and filter by HAVING. Then supply the query result to DELETE statement:
DELETE FROM Table1 WHERE PhoneNumber IN (SELECT a.PhoneNumber FROM (
SELECT COUNT(*) AS cnt, PhoneNumber FROM Table1 GROUP BY PhoneNumber HAVING cnt>1
) AS a);
http://sqlfiddle.com/#!9/a012d21/1
complete fiddle:
schema:
CREATE TABLE Table1
(`ID` int, `PhoneNumber` int)
;
INSERT INTO Table1
(`ID`, `PhoneNumber`)
VALUES
(1, 888),
(2, 888),
(3, 888),
(4, 889),
(5, 889),
(6, 111),
(7, 222),
(8, 333),
(9, 444)
;
delete query:
DELETE FROM Table1 WHERE PhoneNumber IN (SELECT a.PhoneNumber FROM (
SELECT COUNT(*) AS cnt, PhoneNumber FROM Table1 GROUP BY PhoneNumber HAVING cnt>1
) AS a);
you could try using a left join with the subquery for min id related to each phonenumber ad delete where not match
delete m
from m_table m
left join (
select min(id), PhoneNumber
from m_table
group by PhoneNumber
) t on t.id = m.id
where t.PhoneNumber is null
otherwise if you want delete all the duplicates without mantain at least a single row you could use
delete m
from m_table m
INNER join (
select PhoneNumber
from m_table
group by PhoneNumber
having count(*) > 1
) t on t.PhoneNumber= m.PhoneNumber
Instead of deleting from the table, I would suggest creating a new one:
create table table2 as
select min(id) as id, phonenumber
from table1
group by phonenumber
having count(*) = 1;
Why? Deleting rows has a lot of overhead. If you are bringing the data in from an external source, then treat the first landing table as a staging table and the second as the final table.
I am trying to limit returned results of users to results that are "recent" but where users have a parent, I also need to return the parent.
CREATE TABLE `users` (
`id` int(0) NOT NULL,
`parent_id` int(0) NULL,
`name` varchar(255) NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `times` (
`id` int(11) NOT NULL,
`time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `users`(`id`, `parent_id`, `name`) VALUES (1, NULL, 'Alan');
INSERT INTO `users`(`id`, `parent_id`, `name`) VALUES (2, 1, 'John');
INSERT INTO `users`(`id`, `parent_id`, `name`) VALUES (3, NULL, 'Jerry');
INSERT INTO `users`(`id`, `parent_id`, `name`) VALUES (4, NULL, 'Bill');
INSERT INTO `users`(`id`, `parent_id`, `name`) VALUES (5, 1, 'Carl');
INSERT INTO `times`(`id`, `time`) VALUES (2, '2019-01-01 14:40:38');
INSERT INTO `times`(`id`, `time`) VALUES (4, '2019-01-01 14:40:38');
http://sqlfiddle.com/#!9/91db19
In this case I would want to return Alan, John and Bill, but not Jerry because Jerry doesn't have a record in the times table, nor is he a parent of someone with a record. I am on the fence about what to do with Carl, I don't mind getting the results for him, but I don't need them.
I am filtering tens of thousands of users with hundreds of thousands of times records, so performance is important. In general I have about 3000 unique id's coming from times that could be either an id, or a parent_id.
The above is a stripped down example of what I am trying to do, the full one includes more joins and case statements, but in general the above example should be what we work with, but here is a sample of the query I am using (full query is nearly 100 lines):
SELECT id AS reference_id,
CASE WHEN (id != parent_id)
THEN
parent_id
ELSE null END AS parent_id,
parent_id AS family_id,
Rtrim(last_name) AS last_name,
Rtrim(first_name) AS first_name,
Rtrim(email) AS email,
missedappt AS appointment_missed,
appttotal AS appointment_total,
To_char(birth_date, 'YYYY-MM-DD 00:00:00') AS birthday,
To_char(first_visit_date, 'YYYY-MM-DD 00:00:00') AS first_visit,
billing_0_30
FROM users AS p
RIGHT JOIN(
SELECT p.id,
s.parentid,
Count(p.id) AS appttotal,
missedappt,
billing0to30 AS billing_0_30
FROM times AS p
JOIN (SELECT missedappt, parent_id, id
FROM users) AS s
ON p.id = s.id
LEFT JOIN (SELECT parent_id, billing0to30
FROM aging) AS aging
ON aging.parent_id = p.id
WHERE p.apptdate > To_char(Timestampadd(sql_tsi_year, -1, Now()), 'YYYY-MM-DD')
GROUP BY p.id,
s.parent_id,
missedappt,
billing0to30
) AS recent ON recent.patid = p.patient_id
This example is for a Faircom C-Tree database, but I also need to implement a similar solution in Sybase, MySql, and Pervasive, so just trying to understand what I should do for best performance.
Essentially what I need to do is somehow get the RIGHT JOIN to also include the users parent.
NOTES:
based on your fiddle config I'm assuming you're using MySQL 5.6 and thus don't have support for Common Table Expressions (CTE)
I'm assuming each name (child or parent) is to be presented as separate records in the final result set
We want to limit the number of times we have to join the times and users tables (a CTE would make this a bit easier to code/read).
The main query (times -> users(u1) -> users(u2)) will give us child and parent names in separate columns so we'll use a 2-row dynamic table plus a case statement to to pivot the columns into their own rows (NOTE: I don't work with MySQL and didn't have time to research if there's a pivot capability in MySQL 5.6)
-- we'll let 'distinct' filter out any duplicates (eg, 2 'children' have same 'parent')
select distinct
final.name
from
-- cartesian product of 'allnames' and 'pass' will give us
-- duplicate lines of id/parent_id/child_name/parent_name so
-- we'll use a 'case' statement to determine which name to display
(select case when pass.pass_no = 1
then allnames.child_name
else allnames.parent_name
end as name
from
-- times join users left join users; gives us pairs of
-- child_name/parent_name or child_name/NULL
(select u1.id,u1.parent_id,u1.name as child_name,u2.name as parent_name
from times t
join users u1
on u1.id = t.id
left
join users u2
on u2.id = u1.parent_id) allnames
join
-- poor man's pivot code:
-- 2-row dynamic table; no join clause w/ allnames will give us a
-- cartesian product; the 'case' statement will determine which
-- name (child vs parent) to display
(select 1 as pass_no
union
select 2) pass
) final
-- eliminate 'NULL' as a name in our final result set
where final.name is not NULL
order by 1
Result set:
name
==============
Alan
Bill
John
MySQL fiddle
My SQL query is like this
SELECT `product_code`, `test`
FROM `table_products`
WHERE `product_code` IN ('38986', '222098', '1113426', '3645651', ...)
I would like the results to be ordered as in product_code order shown in query and also when there is no matching for that product_code in table, I would like to add an empty line in SQL result.
There (probably) is no other way except that you express the values as rows:
SELECT codelist.code, table_products.whatever
FROM (
SELECT 38986 AS code, 1 AS sort UNION ALL
SELECT 222098, 2 UNION ALL
SELECT 1113426, 3 UNION ALL
SELECT 3645651, 4
) AS codelist
LEFT JOIN table_products ON codelist.code = table_products.product_code
ORDER BY codelist.sort
The LEFT JOIN will give you the code number and empty right hand side row if there is no match. ORDER BY sort will sort the products in the desired order.
You can have the reference product code values in another table and use right outer join
eg)
create table Test(id integer, title varchar(100));
create table ref(idtosee integer);//reference table
insert into ref(idtosee) values(1);
insert into ref(idtosee) values(4);
insert into Test(id, title) values(1, "Hello");
insert into Test(id, title) values(2, "sHello");
select id,title,idtosee from Test right outer join ref on id=idtosee;
I have four tables
store[store_id(pk),name]
itemsA(item_id(pk),store_id,name)
itemB(item_id(pk),store_id,name)
itemC(item_id(pk),store_id,name)
I want a query to retrieve a store and the number of items that he have. something like :
select s.store_id ,s.name,count() as numberOfItems from store limit 100
what is the optimal query to achieve that with the following restraints :
cannot create a function in the db
cannot create view
I can only run queries on the db
Thanks
I would recommend doing this with correlated subqueries:
select s.store_id, s.name,
((select count(*) from itemsA a where a.store_id = s.store_id) +
(select count(*) from itemsB b where b.store_id = s.store_id) +
(select count(*) from itemsC c where c.store_id = s.store_id)
) as numberOfItems
from store s
limit 100;
You then want an index in each of the item tables: itemsA(stored_id), itemsB(store_id), and itemsC(store_id).
The reason this is optimized is because it only has to calculate the values for the arbitrary 100 stores chosen by the limit. And, the calculation can be done directly from the index. Other approaches will require doing the calculation for all the stores.
Note: usually when using limit you want an order by clause.
Stores with no items will not show up with this query. If this is a requirement it will have to be tweaked somewhat.
SELECT s.store_id, COUNT(*)
FROM Store s
JOIN ItemA a ON a.store_id = s.store_id
JOIN ItemB b ON b.store_id = s.store_id
JOIN ItemC c ON c.store_id = s.store_id
GROUP BY s.store_id
A simple modification to also include stores with 0 items:
SELECT s.store_id, COUNT(a.store_id) + COUNT(b.store_id) + COUNT(c.store_id)
FROM Store s
LEFT JOIN ItemA a ON a.store_id = s.store_id
LEFT JOIN ItemB b ON b.store_id = s.store_id
LEFT JOIN ItemC c ON c.store_id = s.store_id
GROUP BY s.store_id
If i understood you correctly
DECLARE #store TABLE (store_id INT, name NVARCHAR(100))
DECLARE #itemsA TABLE (item_id INT,store_id INT, name NVARCHAR(100))
DECLARE #itemsB TABLE (item_id INT,store_id INT, name NVARCHAR(100))
DECLARE #itemsC TABLE (item_id INT,store_id INT, name NVARCHAR(100))
INSERT INTO #store VALUES (1,'Store1')
INSERT INTO #store VALUES (2,'Store2')
INSERT INTO #itemsA VALUES (1,1,'itemsA_item1')
INSERT INTO #itemsA VALUES (2,1,'itemsA_item2')
INSERT INTO #itemsA VALUES (3,1,'itemsA_item3')
INSERT INTO #itemsB VALUES (1,2,'itemsB_item1')
INSERT INTO #itemsB VALUES (2,2,'itemsB_item2')
INSERT INTO #itemsB VALUES (3,2,'itemsB_item3')
INSERT INTO #itemsB VALUES (4,1,'itemsB_item4')
INSERT INTO #itemsC VALUES (1,3,'itemsC_item1')
INSERT INTO #itemsC VALUES (2,3,'itemsC_item2')
INSERT INTO #itemsC VALUES (3,2,'itemsC_item3')
SELECT TOP 100 store_id, SUM(HasItems) AS TotalItems FROM
(
SELECT store_id, COUNT(name) AS HasItems FROM #itemsA GROUP BY store_id
UNION
SELECT store_id, COUNT(name) AS HasItems FROM #itemsB GROUP BY store_id
UNION
SELECT store_id, COUNT(name) AS HasItems FROM #itemsC GROUP BY store_id
) AS StoreItems
GROUP BY store_id