mysql update multiple tables with single sql to get sum(qty) - mysql

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

Related

Limit query result

Given the schema and data below,
CREATE TABLE `Payroll` (
`payId` int,
`payName` varchar(20),
`groupId` int,
`startDate` date ,
`endDate` date,
`paymentDate` date
);
insert into Payroll values
(20,'June A',2,'2022-06-01','2022-06-30','2022-06-30'),
(21,'July A',2,'2022-07-01','2022-07-31','2022-07-31'),
(17,'April A',1,'2022-04-01','2022-04-30','2022-04-30'),
(18,'May A',1,'2022-05-01','2022-05-31','2022-05-31'),
(19,'July B',1,'2022-07-01','2022-07-31','2022-07-31')
;
CREATE TABLE `PayrollItems` (
`payId` int NOT NULL,
`employeeId` int ,
`payCategory` varchar(45) ,
`value` decimal(15,4) NOT NULL
);
insert into PayrollItems values
(20,12,'salary',200),
(20,12,'housing',500),
(20,13,'salary',400),
(20,14,'salary',1300),
(21,12,'salary',200),
(21,12,'housing',500),
(21,13,'salary',400),
(21,14,'salary',1300),
(18,13,'salary',400),
(18,13,'housing',1300),
(19,14,'salary',500),
(19,14,'housing',1200),
(17,14,'salary',700),
(17,14,'housing',1000)
;
How should i enhance the below query so that i get the previous payid row only, previous pay id would be a payid that has the same group id but a payment date dated prior, having said that i would want the one payid dated prior.
SELECT distinct
a.payId,a.payName, b.*
FROM
(SELECT
Payroll.payId,
employeeId,
payName,
groupId,
paymentDate,
SUM(value * (payCategory = 'housing')) housing,
SUM(value * (payCategory = 'salary')) salary
FROM
PayrollItems
JOIN Payroll ON (Payroll.payid = PayrollItems.payId)
GROUP BY Payroll.payId , employeeId , groupId , paymentDate,payName) a
JOIN
(SELECT
Payroll.payId,
employeeId,
payName,
groupId,
max(paymentDate) paymentDate,
SUM(value * (payCategory = 'housing')) housing,
SUM(value * (payCategory = 'salary')) salary
FROM
PayrollItems
JOIN Payroll ON (Payroll.payid = PayrollItems.payId)
GROUP BY Payroll.payId , employeeId , groupId , paymentDate,payName) b ON b.groupId = a.groupId
AND b.paymentDate < a.paymentDate
order by a.payId,b.payId,b.employeeId
;
In the result shown below i do not need the row marked in red to appear
dbfiddle
I think you need to learn how to use MySQL 8.0 window functions.
mysql> select payId, lag(payId) over (partition by groupId order by payId) as prevPayId
from Payroll;
+-------+-----------+
| payId | prevPayId |
+-------+-----------+
| 17 | NULL |
| 18 | 17 |
| 19 | 18 |
| 20 | NULL |
| 21 | 20 |
+-------+-----------+

MYSQL : Group count specific column per user?

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 |

MySQL complex semi-join without group by

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/

SQL RIGHT JOIN misunderstanding

I'm working on ASP.NET application whose SQL backend (MySQL 5.6) has 4 tables:
The first table is defined in this way:
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`descr` varchar(45) NOT NULL,
`modus` varchar(8) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
These are the items managed in the application.
the second table:
CREATE TABLE `files` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`file_path` varchar(255) NOT NULL,
`id_item` int(11) NOT NULL,
`id_type` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
these are files that are required for items management. Each 'item' can have 0 or multiple files ('id_item' field is filled with a valid 'id' of 'items' table).
the third table:
CREATE TABLE `file_types` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`file_type` varchar(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
this table describe the type of the file.
the fourth table:
CREATE TABLE `checklist` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_type` int(11) NOT NULL,
`modus` varchar(8) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
);
this table, as suggested by its name, is a checklist. It describe what types of files needs to be collected for a particular 'modus', 'modus' field holds the same values as for 'modus' in 'items' table, 'id_type' holds valid 'id' values from 'file_types' table.
Let's suppose that the first table holds those items:
id descr modus
--------------------
1 First M
2 Second P
3 Third M
4 Fourth M
--------------------
The second:
id file_path id_item id_type
--------------------------------------
1 file1.jpg 1 1
2 file2.jpg 1 2
3 file3.jpg 2 1
4 file4.jpg 1 4
5 file5.jpg 1 1
--------------------------------------
The third:
id file_type
--------------
1 red
2 blue
3 green
4 default
--------------
The fourth table:
id id_type modus
--------------------
1 1 M
2 2 M
3 3 M
4 4 M
5 1 P
6 4 P
--------------------
What I need to obtain is a table with such items (referred to id_item = 1):
id_item file_path id_type file_type
--------------------------------------------
1 file1.jpg 1 red
1 file5.jpg 1 red
1 file2.jpg 2 blue
1 file4.jpg 4 default
<null> <null> 3 green
--------------------------------------------
While the result table for id_item = 2 should be the following:
id_item file_path id_type file_type
--------------------------------------------
2 file3.jpg 1 red
<null> <null> 4 default
--------------------------------------------
where of course 'id_item' is the 'id' of 'items' table, 'id_type' is the 'id' of the 'types' table etc.
In short I need to have a table that depicts the checklist status for a particularm 'item' id i.e. which files have been collected but also which of them are missing.
I tried to use RIGHT JOIN clause without success:
SELECT
items.id AS id_item,
files.file_path AS file_path,
file_types.id AS id_type,
file_types.file_type AS file_type
FROM
files
RIGHT JOIN
checklist ON (files.id_type = checklist.id_type )
INNER JOIN
items ON (files.id_item = items.id)
AND (items.modus = checklist.modus)
INNER JOIN
file_types ON (checklist.id_type = file_types.id)
WHERE (items.id = 1);
the result of this query is:
id_item file_path id_type file_type
------------------------------------------
1 file1.jpg 1 red
1 file5.jpg 1 red
1 file2.jpg 2 blue
1 file4.jpg 4 default
it lacks of the last row (the missing file from the checklist).
Following query gives you status of each item as following (kind of checklist). I had to change some of the column names which were reserved words in my test environment.
select item_id,
fp filepath,
m_type,
item_desc,
modee,
(select t.type from typess t where t.id = m_type)
from (select null item_id,
i.descr item_desc,
c.modee modee,
c.id_type m_type,
null fp
from items i, checklist c
where c.modee = i.modee
and i.id = 0
and c.id_type not in
(select f.id_type from files f where f.id_item = i.id)
union all
select i.id item_id,
i.descr item_desc,
c.modee modee,
c.id_type m_type,
f.file_path fp
from items i, checklist c, files f
where c.modee = i.modee
and i.id = 0
and f.id_item = i.id
and f.id_type = c.id_type)
order by item_id asc, m_type asc
Try this:
SELECT
files.file_path,
types.type
FROM files
LEFT JOIN checklist ON (files.id_type = checklist.id_type )
LEFT JOIN items ON (files.id_item = items.id)
AND (items.mode = checklist.mode)
LEFT JOIN types ON (checklist.id_type = types .id)
WHERE (items.id = 0);
I have created and populated your tables, but I a discrepancy between what you request (for each item) and your example output (for each item type). However, I have created a query based on the output:
;with cte as (
SELECT i.id, f.file_path, f.id_type
from checklist ck
JOIN files f on f.id_type = ck.id_type
JOIN items i on i.id = f.id_item AND i.mode = ck.mode AND i.id = 0
)
SELECT cte.id, cte.file_path, T.id, T.[type]
FROM types T
LEFT JOIN cte on cte.id_type = T.id
[edit]
My result is the following (SQL):
id file_path id type
---------------------------------
0 file1.jpg 0 red
0 file5.jpg 0 red
0 file2.jpg 1 blue
NULL NULL 2 green
0 file4.jpg 3 default
No CTE version:
SELECT cte.id, cte.file_path, T.id, T.[type]
FROM types T
LEFT JOIN (
SELECT i.id, f.file_path, f.id_type
from checklist ck
JOIN files f on f.id_type = ck.id_type
JOIN items i on i.id = f.id_item AND i.mode = ck.mode AND i.id = 0
) cte on cte.id_type = T.id

Validating presence of value(s) in a (sub)table and return a "boolean" result

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)