SQL/MySQL - Update target table with most recent entry - mysql

I'm looking for some help with a SQL/MySQL problem.
I have three source tables:
CREATE TABLE `customers` (
`cid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`customer_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
CREATE TABLE `standards` (
`sid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`standard_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`sid`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
CREATE TABLE `partial_standard_compliance` (
`customer` bigint(20) unsigned NOT NULL,
`standard` bigint(20) unsigned NOT NULL,
`standard_compliance` bigint(20) unsigned DEFAULT NULL,
`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8
The idea is a customer gives themselves a rating using the standard_compliance column in the partial_standard_compliance table.
Customers can rate the same standard multiple times.
Result example:
+----------+----------+---------------------+---------------------+
| customer | standard | standard_compliance | created_time |
+----------+----------+---------------------+---------------------+
| 1 | 1 | 50 | 2023-01-28 16:19:34 |
| 1 | 1 | 60 | 2023-01-28 16:19:40 |
| 1 | 1 | 70 | 2023-01-28 16:19:48 |
| 2 | 10 | 30 | 2023-01-28 16:58:21 |
| 2 | 8 | 60 | 2023-01-28 16:58:32 |
| 2 | 9 | 60 | 2023-01-28 16:58:39 |
| 2 | 9 | 80 | 2023-01-28 16:58:43 |
+----------+----------+---------------------+---------------------+
I need to create a 4th table that has customer name, standard name and the most recent rating they have given themselves.
I have been trying with JOINS and CREATE AS SELECT, but haven't been able to solve it.
Any point in the right direction would be great. Thanks.
I have been trying with JOINS and CREATE AS SELECT

I need to create a 4th table that has customer name, standard name and
the most recent rating they have given themselves
Would be better if you create a view instead.
create view fourth_table as
select customer_name ,
standard_name ,
standard_compliance,
created_time
from (select c.customer_name,
s.standard_name,
psc.standard_compliance,
psc.created_time,
row_number() over(partition by c.customer_name order by psc.created_time desc ) as rn
from customers c
inner join partial_standard_compliance psc on psc.customer=c.cid
inner join standards s on s.sid=psc.standard
) x
where rn=1;
https://dbfiddle.uk/ZiK-k8jN
MySQL View

Related

LEFT JOIN / IS NULL not working as expected -to select only row which is not in another table

I am trying to select rows which is not in another table. Here I have used LEFT JOIN with IS NULL, but not getting expected result. It works only one for table, but not in another table.
Mainly I Can't manage the relation between four tables. fee - that includes fee categories) and fee_tm - includes lists of month to pay fee), cls_fee - fee criteria of each classes) and invoice - contains information about paid fee).
And in result I am trying to show student lists who haven't paid, or those students records which is not in invoice table.
Mysql
SELECT
fee_tm.id AS ftm_d,
fee.id AS f_id,
fee_tm.en_ttl AS f_tm,
fee.en_ttl AS fee,
cls_fee.fee AS f_mnt
FROM
fee
LEFT JOIN
fee_tm ON fee_tm.year = fee.year
LEFT JOIN
cls_fee ON cls_fee.fee_id = fee.id
LEFT JOIN
student ON student.cls = cls_fee.c_id AND student.sec = cls_fee.s_id
LEFT JOIN
invoice ON invoice.stu_id = student.id AND invoice.fee_id = fee.id AND invoice.ftm_id = fee_tm.id
WHERE
student.id =1 AND invoice.ftm_id is NULL AND invoice.fee_id is NULL
Current Result
ftm_d | f_id | f_tm | fee | f_mnt
=====================================================
2 | 1 | Feb | Annual | 1000
2 | 2 | Feb | Monthly | 560
Expected Result
ftm_d | f_id | f_tm | fee | f_mnt
=====================================================
2 | 2 | Feb | Monthly | 560
My result is checking only f_tm column, so in my current result month Jan is filtered, but it has to check the fee column also and the Annual row has to be filtered.
So, we can know the students who haven't paid their fee. If the record is found in invoice table, then this fee category should be filtered in result.
Database Structure
Student
id | en_ttl | cls | sec | year
========================================
1 | John | 1 | 1 | 1
cls
id | en_ttl | year
========================
1 | One | 1
sec
id | en_ttl | year
========================
1 | A | 1
fee
id | en_ttl | year
========================
1 | Annual | 1
2 | Monthly | 1
3 | Library | 1
fee_tm
id | en_ttl | year
========================
1 | Jan | 1
2 | Feb | 1
cls_fee
id | c_id | s_id | fee_id | fee
===============================================
1 | 1 | 1 | 1 | 1000
2 | 1 | 1 | 2 | 560
invoice
id | stu_id | fee_id | ftm_id
======================================
1 | 1 | 1 | 1
DDL Statements of tables
CREATE TABLE `student` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`en_ttl` varchar(100) NOT NULL,
`cls` int(2) NOT NULL,
`sec` int(2) NOT NULL,
`year` int(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `cls` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`en_ttl` varchar(50) NOT NULL,
`year` int(2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `sec` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`en_ttl` varchar(50) NOT NULL,
`year` int(2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `fee` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`en_ttl` varchar(50) NOT NULL,
`year` int(2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `fee_tm` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`en_ttl` varchar(50) NOT NULL,
`year` int(2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `cls_fee` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`c_id` int(2) NOT NULL,
`s_id` int(2) NOT NULL,
`fee_id` int(2) NOT NULL,
`fee` int(6) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `fee` (`c_id`,`s_id`,`fee_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
CREATE TABLE `invoice` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`stu_id` int(4) NOT NULL,
`fee_id` int(11) NOT NULL,
`ftm_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
use studentid=1 in on cluase
SELECT
fee_tm.id AS ftm_d,
fee.id AS f_id,
fee_tm.en_ttl AS f_tm,
fee.en_ttl AS fee,
cls_fee.fee AS f_mnt
FROM
fee
inner JOIN
fee_tm ON fee_tm.year = fee.year and fee.id=fee_tm.id inner join cls_fee on cls_fee.fee_id = fee.id
LEFT JOIN
student ON student.cls = cls_fee.c_id AND student.sec = cls_fee.s_id
left join invoice ON invoice.stu_id = student.id AND invoice.fee_id = fee.id AND invoice.ftm_id = fee_tm.id and student.id=1
where invoice.fee_id is null

Remove unforgeted records

I have two tables
CREATE TABLE `server` (
`server_id` int(3) NOT NULL AUTO_INCREMENT,
`server_name` varchar(15),
`server_alias` varchar(50),
`server_status` tinyint(1) DEFAULT '0',
`server_join` tinyint(1) DEFAULT '1',
`server_number_member` int(5),
PRIMARY KEY (`server_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `member` (
`member_id` int(11) NOT NULL AUTO_INCREMENT,
`member_server` int(3) DEFAULT NULL COMMENT 'Id server',
`member_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Tên của member',
PRIMARY KEY (`member_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
An I create table VIEW to get list server
CREATE VIEW `server_client` AS
SELECT
`s`.`server_id` AS `server_id`,
`s`.`server_name` AS `server_name`,
`s`.`server_alias` AS `server_alias`,
IF (`s`.`server_join` = 1, (COUNT(`m`.`member_id`) / `s`.`server_number_member` * 100) DIV 1, 100) AS `server_full`
FROM (`server` `s`
LEFT JOIN `member` `m`
ON ((`m`.`member_server` = `s`.`server_id`)))
WHERE `s`.`server_status` = 1
Now, server table have 1 record:
-------------------------------------------------------------------------------------------
|server_id|server_name|server_alias |server_status|server_join|server_number_member|
|-----------------------------------------------------------------------------------------|
| 1 | SV 01 | http://example.com/ | 0 | 0 | 10 |
-------------------------------------------------------------------------------------------
In membertable
------------------------------------------
| member_id | member_server | member_name|
|----------------------------------------|
| 1 | 1 | aaa |
|----------------------------------------|
| 2 | 1 | bbb |
|----------------------------------------|
| 3 | 1 | ccc |
------------------------------------------
Result in server_client table
--------------------------------------------------------
| server_id | server_name | server_alias | server_full |
|------------------------------------------------------|
| NULL | NULL | NULL | 100 |
--------------------------------------------------------
server_full is used to calculate the percentage of the number of members already in a server
I want to remove record NULL in server_client table
How to do it
Thank
Because you are using COUNT() you should also be aggregating over the servers with GROUP BY. The following query should be along the lines of what you want:
CREATE VIEW server_client AS
SELECT
s.server_id AS server_id,
s.server_name AS server_name,
s.server_alias AS server_alias,
IF (s.server_join = 1,
(COUNT(m.member_id) / s.server_number_member * 100) DIV 1,
100) AS server_full
FROM server s
LEFT JOIN member m
ON m.member_server = s.server_id
WHERE s.server_status = 1
GROUP BY
s.server_id,
s.server_name,
s.server_alias
The only issue you may have is with the sum conditional aggregation I have in my query. In any case, I expect that the results from the above will at least start looking correct.
By the way, I removed all the backticks because you don't them and they are ugly.

Is there a way to combine two aggregate functions in MySQL to get distinct values?

I have the following schema in MySQL:
CREATE TABLE `ORDER_CONTENTS` (
`Order_ID` int(10) NOT NULL,
`Pizza_Name` varchar(20) NOT NULL DEFAULT '',
`Quantity` int(2) NOT NULL,
PRIMARY KEY (`Order_ID`,`Pizza_Name`),
KEY `ordercontentsfk2_idx` (`Pizza_Name`),
CONSTRAINT `order_contentsfk1` FOREIGN KEY (`Order_ID`) REFERENCES `ORDERS` (`Order_ID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `CUSTOMERS` (
`Mobile_Number` varchar(10) NOT NULL,
`Name` varchar(45) NOT NULL,
`Age` int(3) DEFAULT NULL,
`Gender` enum('M','F') DEFAULT NULL,
`Email` varchar(100) DEFAULT NULL,
PRIMARY KEY (`Mobile_Number`),
UNIQUE KEY `Mobile_Number_UNIQUE` (`Mobile_Number`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `ORDERS` (
`Order_ID` int(10) NOT NULL AUTO_INCREMENT,
`Mobile_Number` varchar(10) NOT NULL,
`Postcode` int(4) NOT NULL,
`Timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`Order_ID`),
KEY `ordersfk1_idx` (`Mobile_Number`),
KEY `ordersfk2_idx` (`Postcode`),
CONSTRAINT `ordersfk1` FOREIGN KEY (`Mobile_Number`) REFERENCES `CUSTOMERS` (`Mobile_Number`) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT `ordersfk2` FOREIGN KEY (`Postcode`) REFERENCES `STORES` (`Postcode`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
CREATE TABLE `STORES` (
`Postcode` int(4) NOT NULL DEFAULT '0',
`Address` varchar(100) DEFAULT NULL,
`Phone_Number` varchar(10) DEFAULT NULL,
PRIMARY KEY (`Postcode`),
UNIQUE KEY `Postcode_UNIQUE` (`Postcode`),
UNIQUE KEY `Address_UNIQUE` (`Address`),
UNIQUE KEY `Phone_Number_UNIQUE` (`Phone_Number`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
I need to find the following:
Problem Statement
For each customer, list the store details of their favorite pizza
store, where a store is the favorite if it is the one where the
customer purchased the most pizzas).
I have managed to figure it out upto the following query:
select `Name`,SUM(quantity) as hqty,COUNT(*),Postcode from CUSTOMERS natural join orders natural join order_contents group by Mobile_Number,postcode;
This gives me a result as the following:
+---------------+------+----------+----------+
| Name | hqty | COUNT(*) | Postcode |
+---------------+------+----------+----------+
| Homer Simpson | 19 | 3 | 4000 |
| Homer Simpson | 1 | 1 | 4502 |
| Ned Flanders | 2 | 1 | 4000 |
+---------------+------+----------+----------+
But in this case there are two instances of the same customer ( i.e. Homer Simpson). Why is this so? I figured that I would need to use a combination of aggregate function.
Any help/explanation would be great.
Cheers!
[UPDATE 1]
Just for reference:
select * from CUSTOMERS natural join orders natural join
order_contents;
The above query produces this:
+----------+---------------+---------------+------+--------+-----------------+----------+---------------------+--------------+----------+
| Order_ID | Mobile_Number | Name | Age | Gender | Email | Postcode | Timestamp | Pizza_Name | Quantity |
+----------+---------------+---------------+------+--------+-----------------+----------+---------------------+--------------+----------+
| 1 | 0412345678 | Homer Simpson | 38 | M | homer#doh.com | 4000 | 2014-08-21 19:38:01 | Garlic Bread | 9 |
| 1 | 0412345678 | Homer Simpson | 38 | M | homer#doh.com | 4000 | 2014-08-21 19:38:01 | Hawaiian | 9 |
| 2 | 0412345678 | Homer Simpson | 38 | M | homer#doh.com | 4000 | 2014-08-21 19:38:01 | Vegan Lovers | 1 |
| 3 | 0412345678 | Homer Simpson | 38 | M | homer#doh.com | 4502 | 2014-08-21 19:38:12 | Meat Lovers | 1 |
| 4 | 0412345679 | Ned Flanders | 60 | M | ned#vatican.net | 4000 | 2014-08-21 19:39:09 | Meat Lovers | 2 |
+----------+---------------+---------------+------+--------+-----------------+----------+---------------------+--------------+----------+
Also please note the problem statement
SELECT *
FROM customers c
JOIN stores s
ON s.postcode =
(
SELECT postcode
FROM orders o
JOIN order_contents oc
USING (order_id)
WHERE o.mobile_number = c.mobile_number
GROUP BY
postcode
ORDER BY
SUM(quantity) DESC
LIMIT 1
)
This won't show customers who have made no orders at all. If you need those, change the JOIN to stores to a LEFT JOIN
Group by your customers primary key (maybe an ID).
The reason you are getting duplicate customers is because you are grouping the query by mobile_number and postcode, which isn't making a unique index.
Your query should become something like this:
select Name ,SUM(quantity) as hqty,COUNT(*),Postcode from CUSTOMERS natural join orders natural join order_contents group by CUSTOMERS.id
Replace ID with whatever the customers table PK is and it should group by the customer uniquely.

MySQL CONCAT multiple unique rows

So, here's basically the problem:
For starter, I am not asking anyone to do my homework, but to just give me a nudge in the right direction.
I have 2 tables containing names and contact data for practicing
Let's call these tables people and contact.
Create Table for people:
CREATE TABLE `people` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fname` tinytext,
`mname` tinytext,
`lname` tinytext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Create Table for contact:
CREATE TABLE `contact` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`person_id` int(10) unsigned NOT NULL DEFAULT '0',
`tel_home` tinytext,
`tel_work` tinytext,
`tel_mob` tinytext,
`email` text,
PRIMARY KEY (`id`,`person_id`),
KEY `fk_contact` (`person_id`),
CONSTRAINT `fk_contact` FOREIGN KEY (`person_id`) REFERENCES `people` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
When getting the contact information for each person, the query I use is as follows:
SELECT p.id, CONCAT_WS(' ',p.fname,p.mname,p.lname) name, c.tel_home, c.tel_work, c.tel_mob, c.email;
This solely creates a response like:
+----+----------+---------------------+----------+---------+---------------------+
| id | name | tel_home | tel_work | tel_mob | email |
+----+----------+---------------------+----------+---------+---------------------+
| 1 | Jane Doe | 1500 (xxx-xxx 1500) | NULL | NULL | janedoe#example.com |
| 2 | John Doe | 1502 (xxx-xxx 1502) | NULL | NULL | NULL |
| 2 | John Doe | NULL | NULL | NULL | johndoe#example.com |
+----+----------+---------------------+----------+---------+---------------------+
The problem with this view is that row 1 and 2 (counting from 0) could've been grouped to a single row.
Even though this "non-pretty" result is due to corrupt data, it is likely that this will occur in a multi-node database environment.
The targeted result would be something like
+----+----------+---------------------+----------+---------+---------------------+
| id | name | tel_home | tel_work | tel_mob | email |
+----+----------+---------------------+----------+---------+---------------------+
| 1 | Jane Doe | 1500 (xxx-xxx 1500) | NULL | NULL | janedoe#example.com |
| 2 | John Doe | 1502 (xxx-xxx 1502) | NULL | NULL | johndoe#example.com |
+----+----------+---------------------+----------+---------+---------------------+
Where the rows with the same id and name are grouped when still showing the effective data.
Side notes:
innodb_version: 5.5.32
version: 5.5.32-0ubuntu-.12.04.1-log
version_compile_os: debian_linux-gnu
You could use GROUP_CONCAT(), which "returns a string result with the concatenated non-NULL values from a group":
SELECT p.id,
GROUP_CONCAT(CONCAT_WS(' ',p.fname,p.mname,p.lname)) name,
GROUP_CONCAT(c.tel_home) tel_home,
GROUP_CONCAT(c.tel_work) tel_work,
GROUP_CONCAT(c.tel_mob ) tel_mob,
GROUP_CONCAT(c.email ) email
FROM my_table
GROUP BY p.id

MySQL, get data from two related tables if second table not always have matching rows

Example table content
'main'
| id | total |
| 1 | 10 |
| 2 | 20 |
'timed'
| id | id_main | date_from | date_to | total |
| 1 | 2 | 2012-03-29 | 2012-04-29 | 50 |
Desired result
| id | total |
| 1 | 10 |
| 2 | 50 |
Not exactly working query
SELECT main.id AS id, COALESCE(timed.total, main.total) AS total
FROM main
LEFT JOIN timed
ON main.id = timed.id_main
WHERE SYSDATE() BETWEEN timed.date_from AND timed.date_to
Result
| id | total |
| 2 | 50 |
In tables 'main' and 'timed' 'total' field will never be NULL.
In some 'timed' records there will be no relative 'id_main', or there will be few, but they will differ, 'date_from' 'date_to' never intersect.
Table 'main' is large, but in 'timed' will always be two or three relative records.
CREATE TABLE `main` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`total` decimal(10,2) unsigned NOT NULL DEFAULT '0.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO `main` VALUES (1,10);
INSERT INTO `main` VALUES (2,20);
CREATE TABLE `timed` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`id_main` int(11) unsigned NOT NULL DEFAULT '0',
`date_from` date DEFAULT NULL,
`date_to` date DEFAULT NULL,
`total` decimal(10,2) unsigned NOT NULL DEFAULT '0.00',
PRIMARY KEY (`id`),
KEY `link` (`id_main`)
) ENGINE=InnoDB;
INSERT INTO `timed` VALUES (1,2,'2012-03-29','2012-03-30',50);
ALTER TABLE `timed`
ADD CONSTRAINT `link` FOREIGN KEY (`id_main`)
REFERENCES `main` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
Sorry for my english.
You should move the date condition in the join condition:
SELECT main.id AS id, COALESCE(timed.total, main.total) AS total
FROM main
LEFT JOIN timed
ON main.id = timed.id_main and SYSDATE() BETWEEN timed.date_from AND timed.date_to
In your query, those rows not matched are filtered out by the WHERE condition because timed.date_form and timed.date_to are null, so sysdate can't be between them :)