Solution for subquery in MySQL WHERE IN - mysql

I am trying to solve WHERE subquery or to find different solution.
What I am trying to achieve is based on this query:
SELECT c.orig_point_id,
(SELECT attempts
FROM
(SELECT
orig_carrier_id,
orig_point_id,
term_point_id,
term_route,
currency_sell,
is_special,
COUNT(*) AS attempts
FROM cdr
WHERE 1=1
AND start_time >= '2016-10-01 0:00:00'
AND start_time <= '2016-10-31 23:59:59'
GROUP BY orig_carrier_id, currency_sell) AS c0
WHERE c0.orig_carrier_id=3
AND c0.currency_sell="USD"
LIMIT 1) AS attempts,
(SELECT SPLIT(clear_number) as array
FROM
(SELECT
COUNT(*) as total,
clear_number,
orig_carrier_id,
currency_sell
FROM `cdr`
WHERE `start_time`>='2016-10-01 00:00:00'
AND start_time <= '2016-10-31 23:59:59'
GROUP BY `clear_number`
ORDER BY total DESC) AS c0
WHERE c0.orig_carrier_id=3
AND c0.currency_sell="USD"
LIMIT 1) AS splitted_number
FROM cdr AS c
GROUP BY c.orig_carrier_id, c.currency_sell;
SPLIT is a function. Query in that section finds a number(most frequent) and function splits it in ex. 12345,1234,123,12,1. Problem comes when i try to use that as IN subquery. When used directly mysql says functionality not supported. Looks like query is too complex.
When i alias subquery as a workaround it returns NULL, so workaround doesn't work and i believe its returning NULL because of the same reason that its not feasible.
SELECT
CONCAT_WS(" - ",country,region) AS route_name
FROM numbering_plan_external
WHERE
prefix IN(
SELECT array
FROM
(SELECT SPLIT(clear_number) as array
FROM
(SELECT
COUNT(*) as total,
clear_number,
orig_carrier_id,
currency_sell
FROM `cdr`
WHERE `start_time`>='2016-10-01 00:00:00'
AND start_time <= '2016-10-31 23:59:59'
GROUP BY `clear_number`
ORDER BY total DESC) AS c0
WHERE c0.orig_carrier_id=3
AND c0.currency_sell="USD"
LIMIT 1) AS splitted_number)
ORDER BY prefix DESC LIMIT 1) AS top_route
Am I doing anything wrong here, or is there different approach to achieve this. I can leave just split number and later through PHP find the route. It will require lots of queries depending on the results and I am trying to avoid it if possible.
Thanks in advance guys.
Some sample data
CREATE TABLE IF NOT EXISTS `numbering_plan_external` (
`id` int(11) NOT NULL,
`country` varchar(255) NOT NULL,
`region` varchar(255) DEFAULT NULL,
`prefix` varchar(50) NOT NULL,
`is_mobile` tinyint(1) NOT NULL DEFAULT '0',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`min_sale_price_currency` char(3) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
`min_sale_price_amount` decimal(10,4) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=14004 DEFAULT CHARSET=latin1;
INSERT INTO `numbering_plan_external`
(`id`, `country`, `region`,`prefix`, `is_mobile`, `last_updated`, `min_sale_price_currency`, `min_sale_price_amount`)
VALUES
(13047, 'Tunisia', '', '216', 0, '2016-02-17 12:30:44', NULL, NULL),
(13048, 'Tunisia', 'Mobile (ORANGE)', '2165', 1, '2016-02-17 12:30:44', NULL, NULL),
(13049, 'Tunisia', 'Mobile (ORASCOM)', '2162', 1, '2016-02-17 12:30:44', NULL, NULL),
(13050, 'Tunisia', 'Mobile (TUNTEL)', '21640', 1, '2016-02-17 12:30:44', NULL, NULL),
(13051, 'Tunisia', 'Mobile (TUNTEL)', '21641', 1, '2016-02-17 12:30:44', NULL, NULL),
(13052, 'Tunisia', 'Mobile (TUNTEL)', '2169', 1, '2016-02-17 12:30:44', NULL, NULL);
CREATE TABLE IF NOT EXISTS `cdr` (
`id` int(11) NOT NULL,
`orig_carrier_id` int(11) NOT NULL,
`orig_point_id` int(11) NOT NULL,
`term_carrier_id` int(11) NOT NULL,
`term_point_id` int(11) NOT NULL,
`clear_number` varchar(100) COLLATE latin1_general_ci NOT NULL,
`is_special` tinyint(1) NOT NULL DEFAULT '0',
`start_time` datetime NOT NULL,
`currency_sell` char(3) COLLATE latin1_general_ci NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=16385 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
INSERT INTO `cdr`
(`id`, `orig_carrier_id`, `orig_point_id`, `term_carrier_id`, `term_point_id`, `clear_number`, `is_special`, `start_time`, `currency_sell`) VALUES
(1, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:02:04', 'USD'),
(2, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:02:04', 'USD'),
(3, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:03:56', 'USD'),
(4, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:09:28', 'USD'),
(5, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:16:35', 'USD');

IN considers values as a whole. Whatever your SPLIT() is doing, even though it returns a "csv", that entire list is considered one SINGLE value, e.g. it'll be parsed/executed as the equivalent
WHERE foo IN ('12345,1234,...')
WHERE foo='12345,1234,...'
instead of these
WHERE foo IN ('12345', '1234', '123', ...)
WHERE foo='12345' OR foo='1234' OR ...
You could try using MySQL's find_in_set() instead, which basically does what you want.

Using your sample data with this query:
SELECT
cdr.orig_point_id
, count(cdr.*) attempts
, group_concat(distinct npe.region) regions
FROM cdr
INNER JOIN numbering_plan_external npe
ON cdr.clear_number like concat(npe.prefix,'%') COLLATE latin1_general_ci
AND npe.region <> ''
WHERE cdr.orig_carrier_id=3
AND cdr.currency_sell='USD'
AND cdr.start_time >= '2016-10-01'
AND cdr.start_time < '2016-11-01'
GROUP BY
cdr.orig_point_id
;
Result:
| orig_point_id | attempts | regions |
|---------------|----------|----------------|
| 5 | 5 | Mobile (ORANGE)|
The join between those tables involves comparing the prefix to the starting characters of the clear_number. However you have a collation conflict so you need to specify the collation being used. Using LIKE is not the most efficient style of join condition and it could lead to performance issues as it doesn't make use of indexes. However it does demonstrate that a logical join does exist and that you do not need that split function (which is not good for a join either by the way).
I have left the remainder of my earlier question available for reference:
Query:
SELECT
orig_point_id
, count(*) attempts
FROM cdr
WHERE orig_carrier_id=3
AND currency_sell='USD'
AND start_time >= '2016-10-01'
AND start_time < '2016-11-01'
GROUP BY
orig_point_id
Results:
| orig_point_id | attempts |
|---------------|----------|
| 5 | 5 |
extracted for Original Query:
SELECT c.orig_point_id,
(SELECT attempts
FROM
(SELECT
orig_carrier_id,
orig_point_id,
term_point_id,
/* term_route, */
currency_sell,
is_special,
COUNT(*) AS attempts
FROM cdr
WHERE 1=1
AND start_time >= '2016-10-01 0:00:00'
AND start_time <= '2016-10-31 23:59:59'
GROUP BY orig_carrier_id, currency_sell) AS c0
WHERE c0.orig_carrier_id=3
AND c0.currency_sell="USD"
LIMIT 1) AS attempts
FROM cdr AS c
GROUP BY c.orig_carrier_id, c.currency_sell
Results:
| orig_point_id | attempts |
|---------------|----------|
| 5 | 5 |
Hopefully you can see that you do not need as much complexity in your query as you do now. I suspect that if we know more about the "expected result" we might be able to do it without the split function.

Related

Mysql get percentage change grouped by user

I am not an expert in sql and I would want some help in getting percentage change for every contract_address.
What I want to achieve
Get the percentage change of price price in 7 days using created_at.
-- Adminer 4.8.1 MySQL 5.5.5-10.4.10-MariaDB dump
SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
DROP TABLE IF EXISTS `nft_market_trade`;
CREATE TABLE `nft_market_trade` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`contract_address` varchar(100) DEFAULT NULL,
`token_id` int(11) DEFAULT NULL,
`price` double DEFAULT NULL,
`amount` int(11) DEFAULT NULL,
`type` enum('F','A') DEFAULT NULL,
`from_address` varchar(100) DEFAULT NULL,
`to_address` varchar(100) DEFAULT NULL,
`block_number` mediumtext DEFAULT NULL,
`tx_hash` varchar(100) DEFAULT NULL,
`event` varchar(100) DEFAULT NULL,
`created_at` datetime DEFAULT current_timestamp(),
`updated_at` datetime DEFAULT current_timestamp(),
`timestamp` mediumtext DEFAULT NULL,
`fee_artist_portion` double DEFAULT NULL,
`fee_treasury_portion` double DEFAULT NULL,
`auction_id` mediumtext DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `nft_market_trade` (`id`, `contract_address`, `token_id`, `price`, `amount`, `type`, `from_address`, `to_address`, `block_number`, `tx_hash`, `event`, `created_at`, `updated_at`, `timestamp`, `fee_artist_portion`, `fee_treasury_portion`, `auction_id`) VALUES
(342, '0xeed69d5ac882bbecb7449fe31e916a3c69b3c27a', 3, 0.000003, 1, 'F', '0x68cb4d2da9323586c11d58cc3c22f96282319050', '0x4091320130802794fc301642b8d61d090e419477', '97325503', '0x0648cf42ae8683baedda860c0b64fa061abc06d3353f898d042e118fb8537b48', 'SOLD', '2022-08-27 06:14:54', '2022-07-27 06:14:54', '1658902087', 0.00000006, 0.00000006, NULL),
(343, '0xeed69d5ac882bbecb7449fe31e916a3c69b3c27a', 3, 0.000003, 1, 'F', '0x68cb4d2da9323586c11d58cc3c22f96282319050', '0xb7204b9862d2302d2109d38d548111c966ef78ee', '97322182', '0x83ae44b08d1be5060a1a7d0bd6b82878b2a08734adacc392afdf57f32dde8046', 'PLACE', '2022-08-27 06:14:56', '2022-07-27 06:14:56', '1658898762', 0, 0, NULL),
(344, '0xeed69d5ac882bbecb7449fe31e916a3c69b3c27a', 3, 3.5, 1, 'F', '0x4091320130802794fc301642b8d61d090e419477', '0xb7204b9862d2302d2109d38d548111c966ef78ee', '97326062', '0xf9ccc91d53f696594ab04e6782a30c83a6e11663e641acf2999ca5236df710ad', 'PLACE', '2022-08-27 06:17:42', '2022-07-27 06:17:42', '1658902646', 0, 0, NULL),
(345, '0xeed69d5ac882bbecb7449fe31e916a3c69b3c27a', 3, 3.5, 1, 'F', '0x4091320130802794fc301642b8d61d090e419477', '0x68cb4d2da9323586c11d58cc3c22f96282319050', '97326730', '0xab4db021609514cb50846baa08124667634b80902cb9d1f3bb9189239980d218', 'SOLD', '2022-08-27 06:28:54', '2022-07-27 06:28:54', '1658903314', 0.07, 0.07, NULL),
(346, '0xeed69d5ac882bbecb7449fe31e916a3c69b3c27a', 3, 3.5, 1, 'F', '0x68cb4d2da9323586c11d58cc3c22f96282319050', '0x2b08e3ca40d615606c5068db8b66f41f460da811', '97328265', '0xa252f96edb41a0c6c7c66d7316b40cf9101b629e6342f4043ffce9c1e9322b86', 'PLACE', '2022-08-27 06:54:15', '2022-07-27 06:54:15', '1658904850', 0, 0, NULL)
Here is what I have tried
SELECT a.contract_address, a.price, a.event, a.created_at, max(price) - min(price) / min(price) * 100 as percentchange
FROM `nft_market_trade` a
WHERE a.event = "SOLD"
AND created_at >= now() - interval 7 day
group by a.contract_address;
Problem with my implementation is the percentage is calculated before the where clause, i.e. I am getting the percentage change before applying where clause.
Here is sqlfiddle link to my query. http://sqlfiddle.com/#!9/40809f5d/8
I would appreciate some help
You can write a subquery within your percentage change calculation to achieve this then GROUP BY contract_address. Optionally, you can remove the ROUND function to get a more detailed value.
SELECT contract_address,
price,
event,
created_at,
round(price * 100 / (SELECT SUM(price)
FROM nft_market_trade
WHERE event = "SOLD"
AND created_at >= now() - interval 7 day
GROUP BY contract_address), 2)
as
percentchange
From nft_market_trade
WHERE event = "SOLD"
AND created_at >= now() - interval 7 day
GROUP BY contract_address
Output: 0%
See Fiddle.

select last inserted row based on date

Main Problem Is:- select last inserted row based on date
i want to be able to select distinct ref row with the last created_At date.
this is my table and data
DROP TABLE IF EXISTS `transactions_logs`;
CREATE TABLE IF NOT EXISTS `transactions_logs` (
`trans_log_Id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`etat_de_commande` varchar(100) NOT NULL,
`ref` varchar(10) NOT NULL,
`commentaire` text NOT NULL,
`staffId` bigint(20) UNSIGNED NOT NULL,
`Created_At` datetime NOT NULL,
PRIMARY KEY (`trans_log_Id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
INSERT INTO `transactions_logs` (`trans_log_Id`, `etat_de_commande`, `ref`, `commentaire`, `staffId`, `Created_At`) VALUES
(1, 'waiting confirmation', '429735061', '', 1, '2020-11-09 12:11:43'),
(2, 'waiting confirmation', '472143970', '', 1, '2020-11-09 13:45:57'),
(3, 'confirmed', '429735061', '', 1, '2020-11-09 13:46:12'),
(4, 'ready', '429735061', '', 1, '2020-11-09 13:46:18'),
(5, 'picked', '429735061', '', 1, '2020-11-09 14:46:25');
COMMIT;
I want to be able to get this result
(2,'waiting confirmation','472143970',1,'2020-11-09 13:45:57'),
(5,'picked','429735061',1,'2020-11-09 14:46:25')
One option uses window functions, available in MySQL 8.0:
select *
from (
select t.*,
rank() over(partition by ref order by created_at desc) rn
from transactions_logs t
) t
where rn = 1
You can also use a correalted subquery for filtering - this works in all MySQL versions:
select t.*
from transactions_logs t
where t.created_at = (
select max(t1.created_at)
from transactions_logs t1
where t1.ref = t.ref
)
The latter would take advantage of an index on (ref, created_at).

specify conditions from outer query on a materialized subquery

i have got the below query which references couple of views 'goldedRunQueries' and 'currentGoldMarkings'. My issue seems to be from the view that is referred in the subquery - currentGoldMarkings. While execution, MySQL first materializes this subquery and then implements the where clauses of 'queryCode' and 'runId', which therefore results in execution time of more than hour as the view refers tables that has got millions of rows of data. My question is how do I enforce those two where conditions on the subquery before it materializes.
SELECT goldedRunQueries.queryCode, goldedRunQueries.runId
FROM goldedRunQueries
LEFT OUTER JOIN
( SELECT measuredRunId, queryCode, COUNT(resultId) as c
FROM currentGoldMarkings
GROUP BY measuredRunId, queryCode
) AS accuracy ON accuracy.measuredRunId = goldedRunQueries.runId
AND accuracy.queryCode = goldedRunQueries.queryCode
WHERE goldedRunQueries.queryCode IN ('CH001', 'CH002', 'CH003')
and goldedRunQueries.runid = 5000
ORDER BY goldedRunQueries.runId DESC, goldedRunQueries.queryCode;
Here are the two views. Both of these also get used in a standalone mode and so integrating any clauses into them is not possible.
CREATE VIEW currentGoldMarkings
AS
SELECT result.resultId, result.runId AS measuredRunId, result.documentId,
result.queryCode, result.queryValue AS measuredValue,
gold.queryValue AS goldValue,
CASE result.queryValue WHEN gold.queryValue THEN 1 ELSE 0 END AS correct
FROM results AS result
INNER JOIN gold ON gold.documentId = result.documentId
AND gold.queryCode = result.queryCode
WHERE gold.isCurrent = 1
CREATE VIEW goldedRunQueries
AS
SELECT runId, queryCode
FROM runQueries
WHERE EXISTS
( SELECT 1 AS Expr1
FROM runs
WHERE (runId = runQueries.runId)
AND (isManual = 0)
)
AND EXISTS
( SELECT 1 AS Expr1
FROM results
WHERE (runId = runQueries.runId)
AND (queryCode = runQueries.queryCode)
AND EXISTS
( SELECT 1 AS Expr1
FROM gold
WHERE (documentId = results.documentId)
AND (queryCode = results.queryCode)
)
)
Note: The above query reflects only a part of my actual query. There are 3 other left outer joins which are similar in nature to the above subquery which makes the problem far more worse.
EDIT: As suggested, here is the structure and some sample data for the tables
CREATE TABLE `results`(
`resultId` int auto_increment NOT NULL,
`runId` int NOT NULL,
`documentId` int NOT NULL,
`queryCode` char(5) NOT NULL,
`queryValue` char(1) NOT NULL,
`comment` varchar(255) NULL,
CONSTRAINT `PK_results` PRIMARY KEY
(
`resultId`
)
);
insert into results values (100, 242300, 'AC001', 'I', NULL)
insert into results values (100, 242300, 'AC001', 'S', NULL)
insert into results values (150, 242301, 'AC005', 'I', 'abc')
insert into results values (100, 242300, 'AC001', 'I', NULL)
insert into results values (109, 242301, 'PQ001', 'S', 'zzz')
insert into results values (400, 242400, 'DD006', 'I', NULL)
CREATE TABLE `gold`(
`goldId` int auto_increment NOT NULL,
`runDate` datetime NOT NULL,
`documentId` int NOT NULL,
`queryCode` char(5) NOT NULL,
`queryValue` char(1) NOT NULL,
`comment` varchar(255) NULL,
`isCurrent` tinyint(1) NOT NULL DEFAULT 0,
CONSTRAINT `PK_gold` PRIMARY KEY
(
`goldId`
)
);
insert into gold values ('2015-02-20 00:00:00', 138904, 'CH001', 'N', NULL, 1)
insert into gold values ('2015-05-20 00:00:00', 138904, 'CH001', 'N', 'aaa', 1)
insert into gold values ('2016-02-20 00:00:00', 138905, 'CH002', 'N', NULL, 0)
insert into gold values ('2015-12-12 00:00:00', 138804, 'CH001', 'N', 'zzzz', 1)
CREATE TABLE `runQueries`(
`runId` int NOT NULL,
`queryCode` char(5) NOT NULL,
CONSTRAINT `PK_runQueries` PRIMARY KEY
(
`runId`,
`queryCode`
)
);
insert into runQueries values (100, 'AC001')
insert into runQueries values (109, 'PQ001')
insert into runQueries values (400, 'DD006')
CREATE TABLE `runs`(
`runId` int auto_increment NOT NULL,
`runName` varchar(63) NOT NULL,
`isManual` tinyint(1) NOT NULL,
`runDate` datetime NOT NULL,
`comment` varchar(1023) NULL,
`folderName` varchar(63) NULL,
`documentSetId` int NOT NULL,
`pipelineVersion` varchar(50) NULL,
`isArchived` tinyint(1) NOT NULL DEFAULT 0,
`pipeline` varchar(50) NULL,
CONSTRAINT `PK_runs` PRIMARY KEY
(
`runId`
)
);
insert into runs values ('test1', 0, '2015-08-04 06:30:46.000000', 'zzzz', '2015-08-04_103046', 2, '2015-08-03', 0, NULL)
insert into runs values ('test2', 1, '2015-12-04 12:30:46.000000', 'zzzz', '2015-08-04_103046', 2, '2015-08-03', 0, NULL)
insert into runs values ('test3', 1, '2015-06-24 10:56:46.000000', 'zzzz', '2015-08-04_103046', 2, '2015-08-03', 0, NULL)
insert into runs values ('test4', 1, '2016-05-04 11:30:46.000000', 'zzzz', '2015-08-04_103046', 2, '2015-08-03', 0, NULL)
First, let's try to improve the performance via indexes:
results: INDEX(runId, queryCode) -- in either order
gold: INDEX(documentId, query_code, isCurrent) -- in that order
After that, update the CREATE TABLEs in the question and add the output of:
EXPLAIN EXTENDED SELECT ...;
SHOW WARNINGS;
What version are you running? You effectively have FROM ( SELECT ... ) JOIN ( SELECT ... ). Before 5.6, neither subquery had an index; with 5.6, an index is generated on the fly.
It is a shame that the query is built that way, since you know which one to use: and goldedRunQueries.runid = 5000.
Bottom Line: add the indexes; upgrade to 5.6 or 5.7; if that is not enough, then rethink the use of VIEWs.

MySQL query using subselects needs to use joins or exists

I've got this query that has been added to over time and even merged with other queries etc, so it has become quite a mess.
It takes WAY too long to execute now. I tried using EXPLAIN EXTENDED and add any indexes / keys I could but nothing I did helped for some reason.
I'm pretty sure the reason is all the sub-selects as mysql has to create a temporary table in memory and perform a non-indexed lookup on that table for every single row (at least, that's what I've been reading).
I've been reading up on sub-queries, joins, and using exists to try to optimize this thing, but I am just not understanding the best way to go about this.
Is there any way to use joins or use exists to replace some of the sub-queries and get this query to run faster?
The query:
SELECT
s.*,
case when m_id.make_name is not null
then m_id.make_name
when m.make_name is not null
then m.make_name
else s.brand end as brandname
FROM
services as s
left join makelist as m_id
on cast(s.brand as unsigned) = m_id.id
left join makelist as m
on s.brand = m.make_name
WHERE
s.is_delete = 'n'
and UNIX_TIMESTAMP(s.`date`) >= 1420070400
and UNIX_TIMESTAMP(s.`date`) <= 1451563199
and s.service_id in ( select ticket_id
from messages
where edit_id = 0
and waiting = 1
and message_id not in ( select edit_id
from messages
where edit_id != 0 )
)
or service_id in ( select ( select m3.ticket_id
from messages m3
where m88.edit_id = m2.message_id ) as ticket_id
from
messages m88
where m88.edit_id in ( select t11.edit_id
from
( select max(`datetime`) as newdate
from messages
where edit_id != 0
group by edit_id ) as t22,
messages as t11
where t11.`datetime` = t22.newdate
and `waiting` = 1 )
)
and s.service_id in ( select ticket_id
from messages
where edit_id = 0
and warning = 1
and message_id not in ( select edit_id
from messages
where edit_id != 0 )
)
or service_id in ( select
( select m33.ticket_id
from messages m33
where m888.edit_id = m22.message_id ) as ticket_id
from messages m888
where m888.edit_id in ( select t111.edit_id
from ( select max(`datetime`) as newdate
from messages
where edit_id != 0
group by edit_id ) as t222,
messages as t111
where t111.`datetime` = t222.newdate
and `warning = 1 )
)
order by
s.`date` desc
limit
0, 10
And ... the data sample...
table: messages
CREATE TABLE IF NOT EXISTS `messages` (
`message_id` int(10) NOT NULL AUTO_INCREMENT,
`employee_id` int(10) NOT NULL,
`admin_id` int(10) NOT NULL,
`ticket_id` int(10) NOT NULL,
`message` text NOT NULL,
`status` char(1) NOT NULL COMMENT 'r=read, u=unread',
`datetime` datetime NOT NULL,
`warning` tinyint(1) NOT NULL DEFAULT '0',
`waiting` tinyint(1) NOT NULL DEFAULT '0',
`edit_id` int(10) NOT NULL,
PRIMARY KEY (`message_id`),
KEY `message_id` (`message_id`),
KEY `edit_id` (`edit_id`),
KEY `ticket_id` (`ticket_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=197 ;
INSERT INTO `messages` (`message_id`, `employee_id`, `admin_id`, `ticket_id`, `message`, `status`, `datetime`, `warning`, `waiting`, `edit_id`) VALUES
(189, 18, 0, 4049, 'Ordered battery ', 'u', '2015-06-02 13:14:38', 0, 1, 0),
(190, 18, 0, 4069, 'Ordered Ram', 'u', '2015-06-04 09:17:57', 0, 0, 0),
(191, 18, 0, 4069, 'Ordered Ram', 'u', '2015-06-04 09:18:43', 0, 1, 0),
(192, 18, 0, 4068, 'Ordered Hard Drive', 'u', '2015-06-04 13:40:13', 0, 1, 0),
(193, 1, 0, 3712, 'customer called just now and said data was missing from last time it was here, i informed her that we keep backups for a month (not 4) and that was definitely gone, and that her screen was still going blank, and i informed her she needed to drop it by for free test. she said her daughter has it in another county and it will be a while before she can bring it in. ', 'u', '2015-06-06 09:59:27', 1, 0, 0),
(194, 18, 0, 4089, 'Ordered Keyboard ', 'u', '2015-06-09 09:51:33', 0, 1, 0),
(195, 18, 0, 4103, 'Battery PA3817u-1BRS.... $39 or Jack $100.. customer said will bring it back next week. ', 'u', '2015-06-11 16:53:16', 0, 0, 0),
(196, 18, 0, 4105, 'Ordered Screen ', 'u', '2015-06-12 11:26:09', 0, 1, 0);
table: makelist
CREATE TABLE IF NOT EXISTS `makelist` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`make_name` varchar(255) NOT NULL,
`make_desc` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=31 ;
INSERT INTO `makelist` (`id`, `make_name`, `make_desc`) VALUES
(1, 'Acer', ''),
(2, 'Apple', ''),
(3, 'ASUS', ''),
(4, 'Compaq', ''),
(5, 'Dell', ''),
(6, 'Gateway', ''),
(7, 'HP', ''),
(8, 'IBM', ''),
(9, 'Lenovo', ''),
(10, 'Sony', ''),
(11, 'Toshiba', ''),
(27, 'Microsoft', ''),
(26, 'Printer Only', ''),
(25, 'Custom', ''),
(23, 'eMachine', ''),
(24, 'MSI', ''),
(30, 'Panasonic', ''),
(28, 'Samsung', '');
table: services
CREATE TABLE IF NOT EXISTS `services` (
`service_id` int(10) NOT NULL AUTO_INCREMENT,
`employee_id` int(10) NOT NULL,
`customer_id` int(10) NOT NULL,
`name` varchar(255) NOT NULL,
`date` datetime NOT NULL,
`phone` text NOT NULL,
`alternate_phone` text NOT NULL,
`email` varchar(50) NOT NULL,
`brand` varchar(50) NOT NULL,
`model` varchar(50) NOT NULL,
`serial_tag` varchar(50) NOT NULL,
`password` varchar(25) NOT NULL,
`type` char(1) NOT NULL,
`emergency` char(1) NOT NULL,
`symptoms` varchar(100) NOT NULL,
`left_items` text NOT NULL,
`employee_note` text NOT NULL,
`is_delete` char(1) NOT NULL DEFAULT 'n' COMMENT 'y=yes, n=no',
`pickedup` tinyint(1) NOT NULL,
`pickup_time` datetime NOT NULL,
`how_paid` varchar(255) NOT NULL DEFAULT 'NA',
`on_call_list` tinyint(1) NOT NULL,
`call_list_note` mediumtext NOT NULL,
`exclude` tinyint(1) NOT NULL DEFAULT '0',
`paymentAmount` decimal(7,2) NOT NULL,
`typeother` varchar(255) NOT NULL,
`na_reason` varchar(255) NOT NULL,
PRIMARY KEY (`service_id`),
KEY `service_id` (`service_id`),
KEY `employee_id` (`employee_id`),
KEY `customer_id` (`customer_id`),
KEY `is_delete` (`is_delete`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=4121 ;
INSERT INTO `services` (`service_id`, `employee_id`, `customer_id`, `name`, `date`, `phone`, `alternate_phone`, `email`, `brand`, `model`, `serial_tag`, `password`, `type`, `emergency`, `symptoms`, `left_items`, `employee_note`, `is_delete`, `pickedup`, `pickup_time`, `how_paid`, `on_call_list`, `call_list_note`, `exclude`, `paymentAmount`, `typeother`, `na_reason`) VALUES
(4118, 18, 0, 'custnameone', '2015-06-12 13:36:00', '(111) 111-1442', '', '', 'Other:::Packard Bell', 'MS2290', '', 'pass', 'l', '', '::diagnostics::', 'power_cord::', 'Will not turn on.. ', 'n', 0, '0000-00-00 00:00:00', 'NA', 0, '', 0, '0.00', '', ''),
(4119, 18, 0, 'custnametwo', '2015-06-12 15:51:00', '(111) 111-9390', '(111) 111-8207 cell', 'email#yahoo.com', '11', 'Satellite L675', '', '', 'l', 'n', ':virus:::', '::', 'Clean up.. Virus\r\n', 'n', 0, '0000-00-00 00:00:00', 'NA', 0, '', 0, '0.00', '', ''),
(4120, 18, 0, 'custnamethree', '2015-06-12 17:57:00', '(111) 111-1455', '', 'email#yahoo.com', '10', 'Vaio E - Sve151D11L', '', '1234', 'l', 'n', ':virus:diagnostics::', 'power_cord::', 'Will not boot to windows ', 'n', 0, '0000-00-00 00:00:00', 'NA', 0, '', 0, '0.00', '', '');
Update, after request for more detail:
This query is listing all the records in the services table and is generated dynamically through PHP. Each record in services table can have 1 or more messages attached to it, linked through services.service_id = messages.ticket_id. When someone posts a message (or edits one) they can select to flag it as "warning" and/or "waiting". This query is pulling up the tickets who have messages with warning or waiting set to 1.
However, another layer of the onion peeled back and we come to the message edits. Message edits are stored in the same table, the difference is that a message edit has no ticket_id but instead has and edit_id which equals the message_id of the original message. So the query has to find the ticket, find the messages associated with the ticket, find out if those messages have edits, and determine which one is the most recent current version of the message and if that current version of the message is marked with a warning or waiting. Hence the messy cumbersome query.
The stuff dealing with the makelist table and brand is just there for completeness as it was tricky to get working and want to make sure whatever solution has that included. It's not really pertinent in this case, but the makelist/brand stuff is basically looking up the name of the brand based on the brand id that is stored in the services table in the brand column.
Whatever comments you supply to this answer, I will continue with helping try revision for you, but was too much to describe in a comment to your original post.
You are looking at services within a given UNIX time range, yet your qualifier on service_id in sub-selects is looking against ALL messages. So, are you only interested in tickets that first qualify for the time range in question?
your complex WHERE clause (abbreviated...)
WHERE
s.is_delete = 'n'
and UNIX_TIMESTAMP(s.`date`) >= 1420070400
and UNIX_TIMESTAMP(s.`date`) <= 1451563199
and s.service_id in ... (sub-qualify 1)
or service_id in ... (sub-qualify 2)
and s.service_id in ... (sub-qualify 3)
or service_id in ... (sub-qualify 4)
is actually running against ALL messages (per the sub-qualify instances 1-4).
It MIGHT help to do a prequery of original tickets (message_id's) ONLY within the date range first and their qualified child message edits to find the max date vs the entire.
Here is something I came up with and will try to describe and you digest.
SELECT
s2.*,
COALESCE( m_id.make_name, COALESCE( m.make_name, s2.brand )) as brandname
from
( SELECT
m.ticket_id,
SUM( case when edits.edit_id = 0 then 0 else 1 end ) as NumberOfEdits,
SUM( m.waiting + coalesce( edits.waiting, 0 ) ) as WaitingMsgs,
SUM( m.warning + coalesce( edits.warning, 0 )) as WarningMsgs,
SUM( m.waiting
+ m.warning
+ coalesce( edits.waiting, 0 )
+ coalesce( edits.warning, 0 ) ) as WaitOrWarnCount,
MAX( case when edits.waiting = 1 then edits.`datetime` else null end ) as LatestWaitingDate,
MAX( case when edits.warning = 1 then edits.`datetime` else null end ) as LatestWarningDate,
MAX( case when edits.waiting = 1 then edits.message_id else null end ) as LatestWaitingMsgID,
MAX( case when edits.warning = 1 then edits.message_id else null end ) as LatestWarningMsgID
from
services as s
LEFT JOIN messages m
ON s.service_id = m.ticket_id
LEFT JOIN messages edits
ON m.message_id = edits.edit_id
WHERE
s.is_delete = 'n'
and UNIX_TIMESTAMP(s.`date`) >= 1420070400
and UNIX_TIMESTAMP(s.`date`) <= 1451563199
GROUP BY
m.ticket_id ) PreQual
JOIN services s2
ON PreQual.ticket_id = s2.service_id
LEFT JOIN makelist as m_id
ON CAST(s2.brand as unsigned) = m_id.id
LEFT JOIN makelist as m
ON s2.brand = m.make_name
LEFT JOIN messages origMsg
ON PreQual.ticket_id = origMsg.ticket_id
LEFT JOIN messages waitMsg
ON PreQual.LatestWaitingMsgID = waitMsg.Message_ID
LEFT JOIN messages warnMsg
ON PreQual.LatestWaarningMsgID = warnMsg.Message_ID
where
( PreQual.NumberOfEdits = 0
AND PreQual.WaitOrWarnCount > 0 )
OR
waitMsg.message_id > 0
OR
warnMsg.message_id > 0
The first FROM source is actually a sub-select of just those service tickets within the unix date range and status you are interested in. It is LEFT-JOINED to the messages table only for that service ticket ID. That primary message is then LEFT-JOINED to itself based on ANY edits to the original message for the given service ticket.
Now, if there can be multiple messages per ticket, I am doing a simple count via SUM(case/when) if ANY edits are associated to the message. Next, I am getting a sum() based on either the ORIGINAL TICKET message OR any EDIT messages are stamped as "Waiting", so this way, I know up-front if there are ANY Waiting messages. Similarly checking for any WARNING looking at both the original message or any edits. For grins, I am also summing an overall count of ANY WAITING or WARNING associated messages per service ticket.
Next, I am getting the maximum date/time stamp for any possible edit that is associated with waiting or warning for the ticket in question.
Finally, I am getting the latest MESSAGE ID VALUE for the corresponding waiting or warning per the specific ticket if/when applicable. I am using THIS value as IT is the direct message for the service edit ticket regardless of all other service tickets.
So all this is rolled-up to a single row per originally qualified service "Ticket_ID" within the date / status you start with.
Now, the additional joins. Here, I am joining back to the services table on the qualified ticket PreQual result, so I do not have to reapply the unix date/time for the outer query portion... I already have the ticket ID. I then to LEFT JOINs to the original message, whatever the LATEST waiting message and warning message as applicable to EACH TICKET.
At last, now I can apply the overall WHERE clause, and you need to confirm or adjust as needed.
My first criteria of tickets you are interested in are those service tickets that have no pending edits that are waiting or warning, but the ORIGINAL message was at least a status of waiting or warning.
The second criteria (OR'd) is IF THERE WAS an entry with an edit status of WAITING (already qualified from the sum case/when of prequal, I already KNOW it's status was waiting)
The third criteria (OR'd) is likewise for an edit status that has a WARNING (again, from sum case/when of the prequal query).
So, if your data is a year or more, and you are only looking for tickets within the current day / week (or whatever) range, you are only considering those messages and edits, not the entire history of everything.
Again, you may need to finalize what you want, but I think I am very close...
ONE FINAL ADDITION...
Not knowing the context of the messages if whatever the last message is, supersedes all prior, this could SIGNIFICANTLY reduce your issues too, so please clarify after.
If a given product is in for service, it gets a ticket ID and default message that a person is "WAITING" for the unit to be picked up. Something happens and an edit is made to the original message, thus an EDIT entry created and warning is indicated, so now, you have the original entry of waiting, and the follow-up as warning. After the customer is contacted and the warning resolved, the product finishes its service and another edit is made and the ticket is closed, so the FINAL edit has no value for either waiting or warning. In this case, the LAST EDIT, regardless of any prior message or edit to prior message "wins" the overall status... The ticket is complete.
Similarly, if a ticket starts as WAITING, and then an edit is made for WARNING, the WARNING (now the most recent) is the primary consideration of status.
If this latest scenario better describes the work flow of service ticket operations, please confirm and I will revise the query to even further simplify.

Mysql compare sum of columns to columns in another table

How can I select a row from another table based on the sum of column from the left table
SELECT Group_concat(c.cartid SEPARATOR ',') AS CartIDs,
Sum(c.grandtotal) AS Sum,
r.percentage
FROM carts c
LEFT JOIN rebates r
ON Sum(c.grandtotal) >= r.fromamountpurchased
AND Sum(c.grandtotal) <= r.toamountpurchased
WHERE c.ispaid = '1'
AND c.addedtorebates = '0'
GROUP BY c.customerid
But this doesn't work. I also tried HAVING also doesn't work.
Is it possible in one query?
Thanks!
UPDATE:
CREATE TABLE IF NOT EXISTS `carts` (
`CartID` bigint(20) NOT NULL AUTO_INCREMENT,
`CustomerID` bigint(20) NOT NULL,
`GrandTotal` decimal(10,2) NOT NULL,
`IsPaid` enum('0','1','2') NOT NULL,
`AddedToRebates` enum('0','1') NOT NULL,
PRIMARY KEY (`CartID`)
)
INSERT INTO `carts` (`CartID`, `CustomerID`, `GrandTotal`, `IsPaid`,
`AddedToRebates`, ) VALUES
(71, 28, '57450.00', '1', '0' ),
(73, 28, '57450.00', '1', '0');
CREATE TABLE IF NOT EXISTS `rebates` (
`RebateID` bigint(20) NOT NULL AUTO_INCREMENT,
`Percentage` varchar(255) NOT NULL COMMENT 'in %',
`FromAmountPurchased` decimal(10,2) NOT NULL,
`ToAmountPurchased` decimal(10,2) NOT NULL,
`CashEquivalent` decimal(10,2) NOT NULL,
PRIMARY KEY (`RebateID`)
)
INSERT INTO `rebates` (`RebateID`, `Percentage`, `FromAmountPurchased`,
`ToAmountPurchased`, `CashEquivalent`) VALUES
(1, '5', '50000.00', '69999.00', '3000.00'),
(2, '10', '70000.00', '79999.00', '5000.00'),
(3, '15', '80000.00', '89999.00', '6000.00'),
(4, '20', '90000.00', '99999.00', '7000.00'),
(5, '25', '100000.00', '150000.00', '8000.00'),
(6, '0', '0.00', '49999.00', '0.00');
Try this:
select q1.CartIDs, q1.total, r.percentage
from
(select group_concat(c.cartid) as CartIDs, sum(c.grandtotal) as total
from carts c
where c.ispaid = '1'
and c.addedtorebates = '0'
group by c.customerid ) q1
left join rebates r
on q1.total >= r.fromamountpurchased
and q1.total <= r.toamountpurchased
Here is a demo fiddle for you: http://sqlfiddle.com/#!9/d27f5/3
You cannot use aggregate functions like SUM() in the join predicate, so in this instance, a subquery is useful
You can achieve your result with a sub query. Please note that this sub query requires an additional scan of carts.
SELECT GROUP_CONCAT(c.CartID SEPARATOR ',') AS CartIDs, SUM(c.GrandTotal) as Sum, r.Percentage
FROM carts c
INNER JOIN (
SELECT SUM(GrandTotal) as grandTotal, CustomerID
FROM carts
GROUP BY CustomerID
) cSums ON cSums.CustomerID = c.CustomerID
LEFT JOIN rebates r ON cSums.grandTotal >= r.FromAmountPurchased AND cSums.grandTotal <= r.ToAmountPurchased
WHERE c.IsPaid = '1' AND c.AddedToRebates = '0' GROUP BY c.CustomerID