Summary
I am looking for a semi-join(ish) query that selects a number of customers and joins their most recent data from other tables.
At a later time, I wish to directly append conditions to the end of the query: WHERE c.id IN (1,2,3)
Problem
As far as I am aware, my requirement rules out GROUP BY:
SELECT * FROM customer c
LEFT JOIN customer_address ca ON ca.customer_id = c.id
GROUP BY c.id
# PROBLEM: Cannot append conditions *after* GROUP BY!
With most subquery-based attempts, my problem is the same.
As an additional challenge, I cannot strictly use a semi-join, because I allow at least two types of phone numbers (mobile and landline), which come from the same table. As such, from the phone table I may be joining multiple records per customer, i.e. this is no longer a semi-join. My current solution below illustrates this.
Questions
The EXPLAIN result at the bottom looks performant to me. Am I correct? Are each of the subqueries executed only once? Update: It appears that DEPENDENT SUBQUERY is executed once for each row in the outer query. It would be great if we could avoid this.
Is there a better solution to what I am doing?
DDLs
DROP TABLE IF EXISTS customer;
CREATE TABLE `customer` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
DROP TABLE IF EXISTS customer_address;
CREATE TABLE `customer_address` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` bigint(20) unsigned NOT NULL,
`street` varchar(85) DEFAULT NULL,
`house_number` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
);
DROP TABLE IF EXISTS customer_phone;
CREATE TABLE `customer_phone` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` bigint(20) unsigned NOT NULL,
`phone` varchar(32) DEFAULT NULL,
`type` tinyint(3) unsigned NOT NULL COMMENT '1=mobile,2=landline',
PRIMARY KEY (`id`)
);
insert ignore customer values (1);
insert ignore customer_address values (1, 1, "OldStreet", 1),(2, 1, "NewStreet", 1);
insert ignore customer_phone values (1, 1, "12345-M", 1),(2, 1, "12345-L-Old", 2),(3, 1, "12345-L-New", 2);
SELECT * FROM customer;
+----+
| id |
+----+
| 1 |
+----+
SELECT * FROM customer_address;
+----+-------------+-----------+--------------+
| id | customer_id | street | house_number |
+----+-------------+-----------+--------------+
| 1 | 1 | OldStreet | 1 |
| 2 | 1 | NewStreet | 1 |
+----+-------------+-----------+--------------+
SELECT * FROM customer_phone;
+----+-------------+-------------+------+
| id | customer_id | phone | type |
+----+-------------+-------------+------+
| 1 | 1 | 12345-M | 1 |
| 2 | 1 | 12345-L-Old | 2 |
| 3 | 1 | 12345-L-New | 2 |
+----+-------------+-------------+------+
Solution so far
SELECT *
FROM customer c
# Join the most recent address
LEFT JOIN customer_address ca ON ca.id = (SELECT MAX(ca.id) FROM customer_address ca WHERE ca.customer_id = c.id)
# Join the most recent mobile phone number
LEFT JOIN customer_phone cphm ON cphm.id = (SELECT MAX(cphm.id) FROM customer_phone cphm WHERE cphm.customer_id = c.id AND cphm.`type` = 1)
# Join the most recent landline phone number
LEFT JOIN customer_phone cphl ON cphl.id = (SELECT MAX(cphl.id) FROM customer_phone cphl WHERE cphl.customer_id = c.id AND cphl.`type` = 2)
# Yay conditions appended at the end
WHERE c.id IN (1,2,3)
Fiddle
This fiddle gives the appropriate result set using the given solution. See my questions above.
http://sqlfiddle.com/#!9/98c57/3
I would avoid those dependent subqueries, instead try this:
SELECT
*
FROM customer c
LEFT JOIN (
SELECT
customer_id
, MAX(id) AS currid
FROM customer_phone
WHERE type = 1
GROUP BY
customer_id
) gm ON c.id = gm.customer_id
LEFT JOIN customer_phone mobis ON gm.currid = mobis.id
LEFT JOIN (
SELECT
customer_id
, MAX(id) AS currid
FROM customer_phone
WHERE type = 2
GROUP BY
customer_id
) gl ON c.id = gl.customer_id
LEFT JOIN customer_phone lands ON gl.currid = lands.id
WHERE c.id IN (1, 2, 3)
;
or, perhaps:
SELECT
*
FROM customer c
LEFT JOIN (
SELECT
customer_id
, MAX(case when type = 1 then id end) AS mobid
, MAX(case when type = 2 then id end) AS lndid
FROM customer_phone
GROUP BY
customer_id
) gp ON c.id = gp.customer_id
LEFT JOIN customer_phone mobis ON gp.mobid = mobis.id
LEFT JOIN customer_phone lands ON gp.lndid = lands.id
WHERE c.id IN (1, 2, 3)
;
see: http://sqlfiddle.com/#!9/ef983/1/
Related
SlNo
UserID
points
1
001
Three
2
002
Three
3
001
Three
I have the following table named 'userdata' and I would like to get the points of just the unique usernames.
SELECT points from userdata where USERID==distinct
Is there a functionality in mysql that works similar to == ?
Use Distinct
Query
Select distinct UserId, points
From userdata;
There are some possibilities
IN clause or a INNER JOIN
Latter will be faster on big tables
CREATE TABLE userdata (
`SlNo` INTEGER,
`UserID` INTEGER,
`points` VARCHAR(5)
);
INSERT INTO userdata
(`SlNo`, `UserID`, `points`)
VALUES
('1', '001', 'Three'),
('2', '002', 'Three'),
('3', '001', 'Thr');
SELECT `points` FROM userdata WHERE `UserID` IN (SELECT `UserID` FROM userdata GROUP BY `UserID` HAVING COUNT(*) = 1)
| points |
| :----- |
| Three |
SELECT `points`
FROM userdata u1 INNER JOIN (SELECT `UserID` FROM userdata GROUP BY `UserID` HAVING COUNT(*) = 1) u2 ON u1.`UserID` = u2.`UserID`
| points |
| :----- |
| Three |
db<>fiddle here
I have a schema that has following two tables
1. People -
+-------+----------------+------------+-----------------+-----------------+
| ID | NAME | DOJ | PREV_COMPANY_ID | CURR_COMPANY_ID |
+-------+----------------+------------+-----------------+-----------------+
| 1795f | ALEX BROWN | 1973-03-02 | 4e5b | 123a |
| 8772 | Chris Mitchell | 2016-06-15 | 4746 | 4e5b |
| 5e03 | Patt Hobbs | 1976-09-14 | 4e5b | 123b |
+-------+----------------+------------+-----------------+-----------------+
2. Company -
+-------+---------------+
| ID | NAME |
+-------+---------------+
| 4746 | Mora-Sullivan |
| 49de6 | Harmon-Miller |
| 4e5b | Fakesmith |
+-------+---------------+
I want to write the query to find the following -
First Determine the Company/Companies that has highest no.of employees & then display it's employees with previous company.
I have found out the company which has the highest employees using sql query. i.e Fakesmith here.
SELECT PREV_COMPANY_ID, COUNT(*) as count
FROM PEOPLE
GROUP BY PREV_COMPANY_ID
ORDER BY count DESC;
But I am unable to buildup any logic to find, the current employers of the highest number of employees with their previous company names. How can I solve this query?
Are you trying something like this:
create table people(
id varchar(9) not null ,
name varchar(50) not null ,
doj date ,
PREV_COMPANY_ID varchar(50),
CURR_COMPANY_ID varchar(50),
Primary key id(`id`)
);
insert into people values ('1795f','ALEX BROWN','1973-03-02','4e5b','123a'),('8772','Chris Mitchell','2016-06-15','4746','4e5b'),('5e03','Patt Hobbs','1976-09-14','4e5b','123b');
create table company(
id varchar(9) not null ,
name varchar(50) not null ,
Primary key id(`id`)
);
insert into company values ('4746','Mora-Sullivan'),('49de6','Harmon-Miller'),('4e5b','Fakesmith');
select p.name,p.CURR_COMPANY_ID,company.name as company_name
from people p join (select PREV_COMPANY_ID ,count(*) as comp_high_nr from people group by PREV_COMPANY_ID order by comp_high_nr desc limit 1) as t on p.CURR_COMPANY_ID =t.PREV_COMPANY_ID join company on p.PREV_COMPANY_ID=company.id ;
Try this one will help you
CREATE TABLE tblcompany (
id varchar(9) NOT NULL,
name varchar(50) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO tblcompany (id,name) VALUES
('1','Mora-Sullivan'),
('2','Harmon-Miller'),
('3','Fakesmith');
CREATE TABLE tblpeople (
id varchar(9) NOT NULL,
name varchar(50) NOT NULL,
doj date DEFAULT NULL,
PREV_COMPANY_ID varchar(50) DEFAULT NULL,
CURR_COMPANY_ID varchar(50) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO tblpeople (id,name,doj,PREV_COMPANY_ID,CURR_COMPANY_ID) VALUES
('1','ALEX BROWN','1973-03-02','1','2'),
('2','Chris Mitchell','2016-06-15','1','3'),
('3','Patt Hobbs','1976-09-14','2','3');
First, we need to get companies with the highest employee's
SELECT t1.CURR_COMPANY_ID, COUNT(t1.id) as `NoOfEmployee` FROM tblpeople as t1 GROUP BY t1.CURR_COMPANY_ID ORDER BY NoOfEmployee DESC LIMIT 1;
Then we join with people table to get all employees for that company
SELECT t1.*, t2.name as `CURR_COMPANY`, t3.name as `PREV_COMPANY` FROM tblpeople as t1 LEFT JOIN tblcompany as t2 ON t1.CURR_COMPANY_ID=t2.id LEFT JOIN tblcompany as t3 ON t1.PREV_COMPANY_ID=t3.id INNER JOIN (SELECT CURR_COMPANY_ID, COUNT(id) as EmployeeCount FROM tblpeople GROUP BY CURR_COMPANY_ID ORDER BY EmployeeCount DESC LIMIT 1) as t4 ON t1.CURR_COMPANY_ID=t4.CURR_COMPANY_ID;
Please check if following query serve your purpose
SELECT *
FROM PEOPLE
WHERE CURR_COMPANY_ID IN ( SELECT PREV_COMPANY_ID
FROM ( SELECT PREV_COMPANY_ID, COUNT (1) AS C
FROM PEOPLE
GROUP BY CURR_COMPANY_ID
ORDER BY C DESC)
ORDER BY c DESC)
I want count column per specific user, using data from 3 tables.
TABLE 1 (users) :
CREATE TABLE `datastore`.`users` ( `uid` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(30) NOT NULL DEFAULT 'john' , `class` VARCHAR(20) NOT NULL DEFAULT 'NEW' , PRIMARY KEY (`uid`)) ENGINE = InnoDB;
INSERT INTO `users` (`uid`, `name`, `class`) VALUES (NULL, 'john', 'NEW'), (NULL, 'mark', 'OLD');
SAMPLE :
uid name class
1 john NEW
2 mark OLD
TABLE 2 (data) :
CREATE TABLE `datastore`.`data` ( `id` INT NOT NULL AUTO_INCREMENT , `source` VARCHAR(30) NULL DEFAULT NULL , `destination` VARCHAR(30) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;
INSERT INTO `data` (`id`, `source`, `destination`) VALUES (NULL, 'NETWORK', 'SERVER_1'), (NULL, 'STATION', 'SERVER_2'), (NULL, 'DATASTORE', 'SERVER_1');
SAMPLE :
id source destination
1 NETWORK SERVER_1
2 STATION SERVER_2
3 DATASTORE SERVER_1
TABLE 3 (access):
CREATE TABLE `datastore`.`access` ( `id` INT(11) NOT NULL AUTO_INCREMENT , `uid` INT(11) NULL DEFAULT NULL , `source` VARCHAR(30) NULL DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;
INSERT INTO `access` (`id`, `uid`, `source`) VALUES (NULL, '1', 'NETWORK'), (NULL, '2', 'STATION'), (NULL, '1', 'STATION'), (NULL, '1', 'STATION');
SAMPLE :
id uid source
1 1 NETWORK
2 2 STATION
3 1 STATION
4 1 STATION
What i tried so far :
SELECT access.uid, data.destination, COUNT(*) as count FROM data, access WHERE access.source = data.source GROUP BY destination, uid
Result :
uid destination count
1 SERVER_1 1
1 SERVER_2 2
2 SERVER_2 1
I what to link it with user name alse,
Desired Result :
uid name destination count
1 john SERVER_1 1
1 john SERVER_2 2
2 mark SERVER_2 1
Seems you need also a join for users
SELECT access.uid
, users.name
, data.destination
, COUNT(*) as count
FROM data
INNER JOIN access ON access.source = data.source
INNER JOIN users ON users.uid = access.uid
GROUP BY destination, uid, users.name
and as suggestion, you should not use the (old) implicit join syntax based on where .. but the explicit join syntax.
Use aggregation:
select
a.uid,
u.name,
d.destination,
count(*)
from
access a
inner join users u on u.uid = a.uid
inner join data on d.source = a.source
group by
a.uid,
u.name,
d.destination
All you need to get the user's name is to join your query to the table users:
SELECT u.uid, u.name, t.destination, t.count
FROM users u INNER JOIN (
SELECT a.uid, d.destination, COUNT(*) AS count
FROM data d
INNER JOIN access a ON a.source = d.source
GROUP BY d.destination, a.uid
) t ON u.uid = t.uid
ORDER BY u.uid, t.destination
See the demo.
Results:
| uid | name | destination | count |
| --- | ---- | ----------- | ----- |
| 1 | john | SERVER_1 | 1 |
| 1 | john | SERVER_2 | 2 |
| 2 | mark | SERVER_2 | 1 |
I want to create a query in MySQL, on an order table and verify if it has a booking id, if it does not have a booking_id it should available on all relations in the invoice table.
I want the value returned to be a boolean in a single field.
Taken the example given, in
Case of id #1 I expect an immediate true, because it's available
Case of id #2 I expect an "delayed" false from the invoice table as not all related invoices have an booking_id, it should only return true if invoice id #3 actually has an booking id, meaning all invoices have an booking_id when the order does not.
I've tried several ways but still failed and don't even know what the best way to tackle this is.
Thanks for your input in advance!
Table order:
|----+------------+
| id | booking_id |
|----+------------+
| 1 | 123 |
| 2 | NULL |
|----+------------+
Table invoice:
+----+----------+------------+
| id | order_id | booking_id |
+----+----------+------------+
| 1 | 1 | 123 |
| 2 | 2 | 124 |
| 3 | 2 | NULL |
+----+----------+------------+
Schema
CREATE TABLE IF NOT EXISTS `invoice` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`booking_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
)
CREATE TABLE IF NOT EXISTS `order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`booking_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
If I understand you correctly, this is the base query for your request:
SELECT
O.id
, SUM(CASE WHEN I.booking_id IS NOT NULL THEN 1 ELSE 0 END) AS booked_count
, COUNT(1) AS total_count
, CASE WHEN SUM(CASE WHEN I.booking_id IS NOT NULL THEN 1 ELSE 0 END) = COUNT(1) THEN 1 ELSE 0 END AS has_all_bookings
FROM
`order` O
LEFT JOIN invoice I
ON O.id = I.order_id
GROUP BY
O.id
If you want to check if there is no record in the invoice table add the COUNT(1) to the last CASE statement as an additional condition (COUNT(1) = 0)
Fiddle Demo
I have not understood how the logic works out when the order is booked but some of the invoices are not. I'll presume either is good for a true value (OR logic). I'd avoid COUNT and GROUP BY and go for a SUBSELECT, which works fine in MySQL (I'm using and old 5.1.73-1 version).
This query gives you both values in distinct columns:
SELECT o.*
, (booking_id IS NOT NULL) AS order_booked
, (NOT EXISTS (SELECT id FROM `invoice` WHERE order_id=o.id AND booking_id IS NULL)) AS invoices_all_booked
FROM `order` o
Of course you can combine the values:
SELECT o.*
, (booking_id IS NOT NULL OR NOT EXISTS (SELECT id FROM `invoice` WHERE order_id=o.id AND booking_id IS NULL)) AS booked
FROM `order` o
Here you go, create a view that does it
create view booked_view as
select `order`.id as order_id
,
case when booking_id > 0 then true
when exists (SELECT id FROM invoice WHERE order_id=`order`.id AND invoice.booking_id IS NULL) then true
else false
end as booked
from `order` ;
Then just join your view to the order table and you will have your boolean column 'booked'
select o.id, booked from `order` o
join booked_view on (o.id = booked_view.order_id)
I have two tables with data
CREATE TABLE `MASTER` (
`NAME` VARCHAR(10) NOT NULL,
`QTY` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`NAME`)
);
NAME | QTY
----------
'ABC' | 0
'XYZ' | 0
CREATE TABLE `DETAIL` (
`NAME` VARCHAR(10) NOT NULL,
`QTY` INT(10) UNSIGNED NOT NULL,
`FLAG` TINYINT(1) UNSIGNED NOT NULL
);
NAME | QTY| FLAG
--------------------
'ABC' | 10 | 0
'ABC' | 20 | 0
'PQR' | 15 | 0
'PQR' | 25 | 0
i want to update sum(detail.qty) to master and set its flag to 1
so i have written query
UPDATE MASTER M, DETAIL D
SET M.QTY = M.QTY + D.QTY,
D.FLAG =1
WHERE M.NAME = D.NAME;
i have guesed MASTER.QTY should be 30 (10 + 20) from detail table.
but it only updates the first value
actual value is MASTER.QTY =10 (only updtaed first value from table)
How can i get MASTER.QTY =30?
Try this query:
update `MASTER` m,`DETAIL` d,
(
SELECT `NAME`, SUM( `QTY` ) as `QTY`
FROM `DETAIL`
GROUP BY `NAME`
) s
SET m.QTY = s.QTY,
d.FLAG = 1
WHERE
m.NAME = s.NAME
AND m.NAME = d.NAME
;
SQLFiddle demo --> http://www.sqlfiddle.com/#!2/ab355/1
IMO, your Master table is unnecessary. You don't need it if the amount of rows ain't in a > 5-digit range.
This equals the MASTER table:
SELECT NAME, SUM(QTY), FLAG FROM DETAIL GROUP BY NAME;
You can create a view from that easily.
Your answer anyways:
UPDATE MASTER m
JOIN DETAIL d ON m.NAME = d.NAME
SET
d.FLAG = 1,
m.QTY = (SELECT SUM(QTY) FROM DETAIL WHERE NAME = d.NAME GROUP BY NAME)
WHERE m.NAME = d.NAME
Also, always follow normalization rules: https://en.wikipedia.org/wiki/Database_normalization