Remove unforgeted records - mysql

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.

Related

SQL/MySQL - Update target table with most recent entry

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

MySQL takes 6 seconds for counting with conditions in 100k records

SELECT
`id`, `code`, `description`, `minamt`
FROM `coupons`
WHERE
`starts`<=DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
`ends`>=DATE_FORMAT(NOW(),"%Y-%m-%d")
and
active=1
and
is_public=1
This mysql took 6 to 7 second to execute , because there are 100k records in coupons table
Table structure
CREATE TABLE IF NOT EXISTS `coupons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bulk_coupon` int(11) DEFAULT '0',
`ctype` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type',
`code` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT 'n/a' COMMENT 'Code',
`discount` float(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Discount',
`description` text COLLATE utf8_bin,
`minamt` float(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Min. amount',
`custlogin` tinyint(1) NOT NULL DEFAULT '2' COMMENT 'Requires customer login',
`freeshipping` tinyint(1) NOT NULL DEFAULT '2' COMMENT 'Free shipping',
`customer` text COLLATE utf8_bin,
`products` text COLLATE utf8_bin COMMENT 'Specific products',
`categories` text COLLATE utf8_bin COMMENT 'Spedific categories',
`aod` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Apply on discounted products',
`starts` date NOT NULL COMMENT 'Start on',
`ends` date NOT NULL COMMENT 'Ends on',
`is_public` tinyint(1) DEFAULT '0',
`active` tinyint(1) DEFAULT '2' COMMENT 'Active',
`usage_type` tinyint(1) DEFAULT '0',
`is_used` tinyint(1) DEFAULT '0',
`cod_applicable` tinyint(1) DEFAULT '0',
`return_policy` tinyint(1) DEFAULT '1',
`added` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `startEndDate` (`starts`,`ends`,`is_public`,`active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1201682 ;
Simplification:
+------------+-------------------------------+
| CURDATE() | DATE_FORMAT(NOW(),"%Y-%m-%d") |
+------------+-------------------------------+
| 2019-02-19 | 2019-02-19 |
+------------+-------------------------------+
Indexes needed (Optimizer will pick one or the other):
INDEX(active, is_public, start)
INDEX(active, is_public, end)
Don't use FLOAT or DOUBLE for currency. Use DECIMAL.
A query rewrite might be.
Query
SELECT
coupons.id
, coupons.code
, coupons.description
, coupons.minamt
FROM (
SELECT
coupons.id
FROM coupons
WHERE
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
and
coupons.active=1
and
coupons.is_public=1
) AS coupons_start
INNER JOIN
coupons
ON
coupons_start.id = coupons.id
AND
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
coupons.ends >= DATE_FORMAT(NOW(),"%Y-%m-%d")
This one seams to have a "better" exection plan that your query.
Keep in mind executions plans on a empty table are not really sound.
So you need to do the EXPLAINS on your own MySQL also to verify
EXPLAIN
SELECT
`id`, `code`, `description`, `minamt`
FROM `coupons`
WHERE
`starts`<=DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
`ends`>=DATE_FORMAT(NOW(),"%Y-%m-%d")
and
active=1
and
is_public=1
;
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| --- | ----------- | ------- | ----- | ------------- | ------------ | ------- | --- | ---- | ----------- |
| 1 | SIMPLE | coupons | range | startEndDate | startEndDate | 3 | | 1 | Using where |
Notice the key_len which is only 3 which means the query can only use a very small part of the startEndDate key
EXPLAIN
SELECT
coupons.id
, coupons.code
, coupons.description
, coupons.minamt
FROM (
SELECT
coupons.id
FROM coupons
WHERE
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
) AS coupons_start
INNER JOIN
coupons
ON
coupons_start.id = coupons.id
AND
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
coupons.ends >= DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
coupons.active = 1
AND
coupons.is_public = 1
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| --- | ----------- | ------- | ----- | ------------- | ------------ | ------- | --- | ---- | --------------------------------------------------- |
| 1 | PRIMARY | | | | | | | | Impossible WHERE noticed after reading const tables |
| 2 | DERIVED | coupons | index | startEndDate | startEndDate | 10 | | 1 | Using where; Using index |
Like i really said it's not really sound to get explains on empty tables. Notice the Impossible WHERE noticed after reading const tables the optimizer know here the table is empty.
But also notice that the key_len is 10 and has the index type and using index which means the inner query can get the needed informations the id's to join from the index file alone..

Struggling writing a nested query

I've got the following tables:
Products:
+---------------------------+
|id|name |details |price|cn|
|__|_____|_________|_____|__|
| 1|pen |somethi |100 |10|
| 2|paper|something|30 |11|
+---------------------------+
Categories:
+----------------------------+
|id | name |parent_id |
|____________________________|
|1 | granny | 0 |
|2 | dad | 1 |
|3 | grandson | 2 |
|4 | grandson 2| 2 |
+----------------------------+
Products2categories:
+-------------------------------+
| id | product_id | category_id|
|_______________________________|
| 1 | 1 | 3 |
| 2 | 1 | 2 |
| 3 | 1 | 1 |
+-------------------------------+
I want to do a query that will return all of the categories that some product is related to.
for example:
When I supply a product ID of 1 I would like to get as a result the result
grandson,dad,granny (name of the categories that this product is related to)
This is my attempt:
SELECT `categories`.`name`
FROM `categories`
JOIN (
SELECT `products2categories`.`category_id`,`products2categories`.`product_id`
FROM `products2categories` a
JOIN `products`
ON `products`.`id` = `products2categories`.`product_id`
)
ON `categories`.`id` = `products2categories`.`category_id`
I've got the following error:
Every derived table must have its own alias
I would like to get some help here :)
Thanks in advance!
Unless I'm misunderstanding your statement, I believe you're overcomplicating things. Based on your verbal description of what you want (and ignoring your code), this will do what you're asking:
SELECT name FROM categories WHERE id IN (SELECT id FROM products2categories WHERE product_id = 1);
EDIT: HERE IS A COMPLETE TEST
CREATE TABLE `categories` (
`id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `categories` VALUES (1,'granny',0),(2,'dad',1),(3,'grandson',2),(4,'grandson 2',2);
CREATE TABLE `products` (
`id` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`details` varchar(255) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
`cn` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `products` VALUES (1,'pen','somethi',100,10),(2,'paper','something',30,11);
CREATE TABLE `products2categories` (
`id` int(11) DEFAULT NULL,
`product_id` int(11) DEFAULT NULL,
`category_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `products2categories` VALUES (1,1,3),(2,1,2),(3,1,1);
mysql> SELECT name FROM categories WHERE id IN (SELECT id FROM products2categories WHERE product_id = 1);
+----------+
| name |
+----------+
| granny |
| dad |
| grandson |
+----------+
3 rows in set (0.00 sec)
Well, I've just found out that I was doing it way too hard that it actually is.
For who-ever will come across it somewhen in the future, here is the solution:
SELECT `categories`.`name`
FROM `categories`
JOIN `products2categories`
ON `categories`.`id` = `products2categories`.`category_id`
WHERE `products2categories`.`product_id` = 1

Improve performance or redesign 'greatest-n-per-group' mysql query

I'm using MySQL5 and I currently have a query that gets me the info I need but I feel like it could be improved in terms of performance.
Here's the query I built (roughly following this guide) :
SELECT d.*, dc.date_change, dc.cwd, h.name as hub
FROM livedata_dom AS d
LEFT JOIN ( SELECT dc1.*
FROM livedata_domcabling as dc1
LEFT JOIN livedata_domcabling AS dc2
ON dc1.dom_id = dc2.dom_id AND dc1.date_change < dc2.date_change
WHERE dc2.dom_id IS NULL
ORDER BY dc1.date_change desc) AS dc ON (d.id = dc.dom_id)
LEFT JOIN livedata_hub AS h ON (d.id = dc.dom_id AND dc.hub_id = h.id)
WHERE d.cluster = 'localhost'
GROUP BY d.id;
EDIT: Using ORDER BY + GROUP BY to avoid getting multiple dom entries in case 'domcabling' has an entry with null date_change and another one with a date for the same 'dom'.
I feel like I'm killing a mouse with a bazooka. This query takes more than 3 seconds with only about 5k entries in 'livedata_dom' and 'livedata_domcabling'. Also, EXPLAIN tells me that 2 filesorts are used:
+----+-------------+------------+--------+-----------------------------+-----------------------------+---------+-----------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+-----------------------------+-----------------------------+---------+-----------------+------+----------------------------------------------+
| 1 | PRIMARY | d | ALL | NULL | NULL | NULL | NULL | 3 | Using where; Using temporary; Using filesort |
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 3 | |
| 1 | PRIMARY | h | eq_ref | PRIMARY | PRIMARY | 4 | dc.hub_id | 1 | |
| 2 | DERIVED | dc1 | ALL | NULL | NULL | NULL | NULL | 4 | Using filesort |
| 2 | DERIVED | dc2 | ref | livedata_domcabling_dc592d9 | livedata_domcabling_dc592d9 | 4 | live.dc1.dom_id | 2 | Using where; Not exists |
+----+-------------+------------+--------+-----------------------------+-----------------------------+---------+-----------------+------+----------------------------------------------+
How could I change this query to make it more efficient?
Using the dummy data (provided below), this is the expected result:
+-----+-------+---------+--------+----------+------------+-----------+---------------------+------+-----------+
| id | mb_id | prod_id | string | position | name | cluster | date_change | cwd | hub |
+-----+-------+---------+--------+----------+------------+-----------+---------------------+------+-----------+
| 249 | 47 | 47 | 47 | 47 | SuperDOM47 | localhost | NULL | NULL | NULL |
| 250 | 48 | 48 | 48 | 48 | SuperDOM48 | localhost | 2014-04-16 05:23:00 | 32A | megahub01 |
| 251 | 49 | 49 | 49 | 49 | SuperDOM49 | localhost | NULL | 22B | megahub01 |
+-----+-------+---------+--------+----------+------------+-----------+---------------------+------+-----------+
Basically I need 1 row for every 'dom' entry, with
the 'domcabling' record with the highest date_change
if record does not exist, I need null fields
ONE entry may have a null date_change field per dom (null datetime field considered older than any other datetime)
the name of the 'hub', when a 'domcabling' entry is found, null otherwise
CREATE TABLE + dummy INSERT for the 3 tables:
livedata_dom (about 5000 entries)
CREATE TABLE `livedata_dom` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mb_id` varchar(12) NOT NULL,
`prod_id` varchar(8) NOT NULL,
`string` int(11) NOT NULL,
`position` int(11) NOT NULL,
`name` varchar(30) NOT NULL,
`cluster` varchar(9) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `mb_id` (`mb_id`),
UNIQUE KEY `prod_id` (`prod_id`),
UNIQUE KEY `name` (`name`),
UNIQUE KEY `livedata_domgood_string_7bff074107b0e5a0_uniq` (`string`,`position`,`cluster`)
) ENGINE=InnoDB AUTO_INCREMENT=5485 DEFAULT CHARSET=latin1;
INSERT INTO `livedata_dom` VALUES (251,'49','49',49,49,'SuperDOM49','localhost'),(250,'48','48',48,48,'SuperDOM48','localhost'),(249,'47','47',47,47,'SuperDOM47','localhost');
livedata_domcabling (about 10000 entries and growing slowly)
CREATE TABLE `livedata_domcabling` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dom_id` int(11) NOT NULL,
`hub_id` int(11) NOT NULL,
`cwd` varchar(3) NOT NULL,
`date_change` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `livedata_domcabling_dc592d9` (`dom_id`),
KEY `livedata_domcabling_4366aa6e` (`hub_id`),
CONSTRAINT `dom_id_refs_id_73e89ce0c50bf0a6` FOREIGN KEY (`dom_id`) REFERENCES `livedata_dom` (`id`),
CONSTRAINT `hub_id_refs_id_179c89d8bfd74cdf` FOREIGN KEY (`hub_id`) REFERENCES `livedata_hub` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5397 DEFAULT CHARSET=latin1;
INSERT INTO `livedata_domcabling` VALUES (1,251,1,'22B',NULL),(2,250,1,'33A',NULL),(6,250,1,'32A','2014-04-16 05:23:00'),(5,250,1,'22B','2013-05-22 00:00:00');
livedata_hub (about 100 entries)
CREATE TABLE `livedata_hub` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(14) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=latin;
INSERT INTO `livedata_hub` VALUES (1,'megahub01');
Try this rewriting (tested in SQL-Fiddle:
SELECT
d.*, dc.date_change, dc.cwd, h.name as hub
FROM
livedata_dom AS d
LEFT JOIN
livedata_domcabling as dc
ON dc.id =
( SELECT id
FROM livedata_domcabling AS dcc
WHERE dcc.dom_id = d.id
ORDER BY date_change DESC
LIMIT 1
)
LEFT JOIN
livedata_hub AS h
ON dc.hub_id = h.id
WHERE
d.cluster = 'localhost' ;
And index on (dom_id, date_change) would help efficiency.
I'm not sure about the selectivity of d.cluster = 'localhost' (how many rows of the livedata_dom table match this condiiton?) but adding an index on (cluster) might help as well.
set #rn := 0, #dom_id := 0;
select d.*, dc.date_change, dc.cwd, h.name as hub
from
livedata_dom d
left join (
select
hub_id, date_change, cwd, dom_id,
if(#dom_id = dom_id, #rn := #rn + 1, #rn := 1) as rn,
#dom_id := dom_id as dm_id
from
livedata_domcabling
order by dom_id, date_change desc
) dc on d.id = dc.dom_id
left join
livedata_hub h on h.id = dc.hub_id
where rn = 1 or rn is null
order by dom_id
The data you posted does not have the dom_id 249. And the #250 has one null date so it comes first. So your result does not reflect what I understand form your question.

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 :)