Mysql order by with grouping - mysql

Hello everyone today i got in to a problem..
first thing i have a two table each table i have "product_seq_id" column and i joined table using the same "product_seq_id"
in the second table there are multiple rows for "product_seq_id" i want only one with below condition
table2.date_start not be null
table2.date_start is equal to '0000-00-00' or table2.date_start <= CURDATE()
table2.date_end is equal to '0000-00-00' or table2.date_start >= CURDATE()
get highest table2.priority if 2 or more rows match on the same day
I have already did some work.. but the problem is in that it's not taking highest priority number while ordering the column with grouped
//My Query
SELECT
psp . *, pcp . *
FROM
sk_product_category_path pcp
left join
sk_product_special_price psp ON (psp.product_seq_id = pcp.product_seq_id)
where
pcp.category_seq_id = 146
AND psp.product_seq_id IS NOT NULL
AND CASE
WHEN
psp.date_start IS NOT NULL
THEN
(psp.date_start = '0000-00-00'
OR psp.date_start <= CURDATE())
AND (psp.date_end = '0000-00-00'
OR psp.date_end >= CURDATE())
ELSE 1 = 1
END
group by psp.product_seq_id
order by psp.priority desc
Result Came for above code:
# product_special_price_seq_id, product_special_price, date_start, date_end, priority, product_seq_id, product_category_path_seq_id, product_seq_id, category_seq_id
2309 123123 0000-00-00 0000-00-00 0 3196 1 3196 146
2307 12313 0000-00-00 0000-00-00 0 3197 3 3197 146
Result I wanted:
# product_special_price_seq_id, product_special_price, date_start, date_end, priority, product_seq_id, product_category_path_seq_id, product_seq_id, category_seq_id
2309 12200 0000-00-00 0000-00-00 1 3196 2 3196 146
2307 12313 0000-00-00 0000-00-00 0 3197 3 3197 146
// Table Data
CREATE TABLE IF NOT EXISTS `sk_product_category_path` (
`product_category_path_seq_id` int(11) NOT NULL AUTO_INCREMENT,
`product_seq_id` int(11) NOT NULL,
`category_seq_id` int(11) NOT NULL,
PRIMARY KEY (`product_category_path_seq_id`),
UNIQUE KEY `product_seq_id` (`product_seq_id`,`category_seq_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `sk_product_category_path` (`product_category_path_seq_id`, `product_seq_id`, `category_seq_id`) VALUES
(1, 3196, 146),
(2, 3197, 146),
(3, 3198, 146);
CREATE TABLE IF NOT EXISTS `sk_product_special_price` (
`product_special_price_seq_id` int(11) NOT NULL AUTO_INCREMENT,
`product_special_price` bigint(20) DEFAULT NULL,
`date_start` date DEFAULT NULL,
`date_end` date DEFAULT NULL,
`priority` int(11) DEFAULT NULL,
`product_seq_id` int(11) NOT NULL,
PRIMARY KEY (`product_special_price_seq_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `sk_product_special_price` (`product_special_price_seq_id`, `product_special_price`, `date_start`, `date_end`, `priority`, `product_seq_id`) VALUES
(1, 12313, '0000-00-00', '0000-00-00', 0, 3197),
(2, 12200, '2014-02-11', '2014-02-11', 1, 3197),
(3, 123123, '0000-00-00', '0000-00-00', 0, 3196);

During GROUP BY in MySQL, it picks first matching row for each group unless you are using an aggregate function. The first matching need not be always row with min(id) .
The possible query should be something like :
SELECT t.*
from table_name t
inner join (
select min(id) as id
from table_name t
group by col) as s
on s.id = t.id

Please find the below query.. let me know is this is your requirement?
SELECT *
FROM sk_product_special_price pspo
WHERE pspo.priority IN(SELECT MAX(psp.priority)
FROM sk_product_special_price psp
JOIN sk_product_category_path pcp
ON(pcp.product_seq_id=psp.product_seq_id)
WHERE psp.date_start IS NOT NULL
AND psp.date_start BETWEEN '0000-00-00' AND CURDATE()
AND (psp.date_end>=CURDATE() OR psp.date_end='0000-00-00')
AND pcp.product_seq_id=pspo.product_seq_id);
I have updated the end date "'2014-02-11" to "2014-02-12" for my code to fetch end date >=today's date.
this query will return the table2 details i.e table sk_product_special_price for each all the product based on the priyority values.
the output will be
product_special_price_seq_id, product_special_price, date_start, date_end, priority, product_seq_id
2, 12200, '2014-02-11', '2014-02-12', 1, 3197
3, 123123, '0000-00-00', '0000-00-00', 0, 3196

Related

How to join two table to get day sale

I want to join two table with some condition
Table 1
CREATE TABLE IF NOT EXISTS `payment` (
`paymentcode` int(6) NOT NULL,
`date` int(3) unsigned NOT NULL,
`amount` varchar(200) NOT NULL,
`customer` varchar(200) NOT NULL,
`store` varchar(200) NOT NULL
)
('2', '20190120', '10050','C1','A'),
('2', '20190120', '10050','c2','A'),
('6', '20190120', '9050','c3','A'),
('4', '20190120', '9045','c4','B'),
('6', '20190121', '10050','c5','B'),
('2', '20190121', '20050','c6','A');
Table 2
CREATE TABLE IF NOT EXISTS `customer` (
`code` int(6) NOT NULL,
`name` int(3) NOT NULL,
)
( 'C1','Customer1'),
( 'c2','Customer2'),
( 'c3','Customer3'),
( 'c4','Customer4'),
( 'c5','Customer5'),
( 'c6','Customer6');
select a.date,a.Paymentcode,a.store,b.Amount as document_total
from
(select date ,Paymentcode,store,sum(amount) from payment
group by date ,Paymentcode,store
) a
join
(select date ,store, sum(amount)as Amount from Payment
group by date ,store) b
on a.date = b.date and a.store = b.store
From above query I can fetch below value:
date paymentcode Amount documet_total
20190120 2 20100 29150
20190120 4 9045 9045
20190120 6 18095 29150
20190121 4 20050 20050
20190121 2 10050 10050
This was the query I was trying. Now I want to JOIN customer table to get customer code if Paymentcode='2' else need to take store value
My expected out is below:
date paymentcode Amount customer_type document_total
20190120 2 10050 C1 29150
20190120 2 10050 C2 29150
20190120 4 9045 B 9045
20190120 6 18095 A 29150
20190121 4 20050 B 20050
20190121 2 10050 C6 10050
Where I need to calculate amount based on date,store,customer_type and Paymentcode, customer_type should customercode it paymentcode='2' else store code, document_total is based on date,store.
Please advice me how to join these table and get the output

mysql LEFT JOIN not acting like left join

Here's my problem. I have to get a list of questions and their answers in the same query. If the answer is older than 30 days, I want to get an empty reply, but I still want the question:
Im trying to do a left join, but if there is no match the question is still not showing up. You can see the schema and my select in this sqlfiddle and also down here:
http://sqlfiddle.com/#!9/a88184/1
SELECT p.*
, r.nota
, r.replied
FROM preguntas p
LEFT
JOIN respuestas r
ON p.id = r.id_pregunta
AND r.uuid_user ="f6912e4bb23130b9"
WHERE r.replied > DATE_SUB(NOW(),INTERVAL 30 DAY)
AND p.id_formulario = "1"
AND activo ="1"
ORDER
BY orden ASC
Schema
CREATE TABLE `preguntas` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`id_formulario` INT(11) NOT NULL,
`pregunta` TEXT NULL COLLATE 'utf8_spanish_ci',
`opcional` TINYINT(1) NOT NULL DEFAULT '0',
`activo` TINYINT(1) NOT NULL DEFAULT '0',
`orden` INT(11) NOT NULL,
PRIMARY KEY (`id`))
COLLATE='utf8_spanish_ci'
ENGINE=InnoDB
ROW_FORMAT=DYNAMIC
AUTO_INCREMENT=302
;
CREATE TABLE `respuestas` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`intento` INT(11) NOT NULL DEFAULT '1',
`id_formulario` INT(11) NOT NULL,
`id_pregunta` INT(11) NULL DEFAULT NULL,
`uuid_user` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_spanish_ci',
`nota` INT(11) NULL DEFAULT NULL,
`replied` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`))
COLLATE='utf8_spanish_ci'
ENGINE=InnoDB
ROW_FORMAT=DYNAMIC
AUTO_INCREMENT=1259;
INSERT INTO `preguntas` (`id`, `id_formulario`, `pregunta`, `opcional`, `activo`, `orden`) VALUES (126, 1, 'INICIATIVA PERSONAL', 0, 1, 1);
INSERT INTO `preguntas` (`id`, `id_formulario`, `pregunta`, `opcional`, `activo`, `orden`) VALUES (139, 1, 'TENER RAPIDEZ Y AGILIDAD', 0, 1, 5);
INSERT INTO `respuestas` (`id`, `intento`, `id_formulario`, `id_pregunta`, `uuid_user`, `nota`, `replied`) VALUES (174, 1, 1, 126, 'f6912e4bb23130b9', 4, '2019-05-23 18:08:15');
INSERT INTO `respuestas` (`id`, `intento`, `id_formulario`, `id_pregunta`, `uuid_user`, `nota`, `replied`) VALUES (175, 1, 1, 139, 'f6912e4bb23130b9', 4, '2019-04-03 18:08:15');
Current result:
id id_formulario pregunta opcional activo orden nota replied
126 1 INICIATIVA PERSONAL false true 1 4 2019-05-23T18:08:15Z
Expected result:
id id_formulario pregunta opcional activo orden nota replied
126 1 INICIATIVA PERSONAL false true 1 4 2019-05-23T18:08:15Z
139 1 TENER RAPIDEZ Y AGILIDAD false true 5 (empty) (empty)
Putting the left table's columns in where clause effectively turns the left join into an inner join.
To prevent that, Move the condition to join:
SELECT p.*, r.nota, r.replied FROM preguntas p
LEFT JOIN respuestas r ON p.id = r.id_pregunta
AND r.uuid_user ="f6912e4bb23130b9" and r.replied > DATE_SUB(NOW(),INTERVAL 30 DAY)
where p.id_formulario = "1" AND activo ="1" ORDER BY orden ASC
sqlfiddle
The reason you don't get the results you want is the WHERE clause.
Put the conditions in the ON clause:
SELECT p.*, r.nota, r.replied FROM preguntas p
LEFT JOIN respuestas r
ON p.id = r.id_pregunta AND r.uuid_user ="f6912e4bb23130b9"
AND r.replied > DATE_SUB(NOW(),INTERVAL 30 DAY)
AND p.id_formulario = "1" AND activo ="1"
ORDER BY orden ASC
This condition:
r.replied > DATE_SUB(NOW(),INTERVAL 30 DAY)
removes the row from the results when it is placed in the WHERE clause, this is why you don't see it. By placing the condition in the ON clause of the join it is still there although there is no match in the other table.
For the other 2 conditions:
p.id_formulario = "1" AND activo ="1"
I'm not sure if you want them to reduce the results, so keep them in WHERE.

How to get summary data for every months in mysql

I want to count the number of items sold(item_count) every month for every item,
--
-- Table structure for table `sales`
--
CREATE TABLE `sales` (
`id` int(11) NOT NULL,
`item_id` int(11) NOT NULL,
`date` date NOT NULL,
`item_count` int(11) NOT NULL,
`amount` float NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `sales`
--
INSERT INTO `sales` (`id`, `item_id`, `date`, `item_count`, `amount`) VALUES
(1, 1, '2018-01-15', 11, 110),
(2, 2, '2018-01-21', 5, 1000),
(3, 1, '2018-02-02', 7, 700),
(4, 2, '2018-02-11', 3, 3000);
I have tried this SQL, but it's not showing the data correctly.
SELECT `sales`.`item_id`,
(CASE WHEN MONTH(sales.date)=1 THEN sum(sales.item_count) ELSE NULL END) as JAN,
(case when MONTH(sales.date)=2 THEN sum(sales.item_count) ELSE NULL END) as FEB
FROM sales WHERE 1
GROUP BY sales.item_id
ORDER BY sales.item_id
This is my expected result,
item_id JAN FEB
1 11 7
2 5 3
I am getting this,
item_id JAN FEB
1 18 NULL
2 8 NULL
Here is an immediate fix to your query. You need to sum over a CASE expression, rather than the other way around.
SELECT
s.item_id,
SUM(CASE WHEN MONTH(s.date) = 1 THEN s.item_count END) AS JAN,
SUM(CASE WHEN MONTH(s.date) = 2 THEN s.item_count END) AS FEB
FROM sales s
GROUP BY
s.item_id
ORDER BY
s.item_id;
But the potential problem with this query is that in order to support more months, you need to add more columns. Also, if you want to cover mulitple years, then this approach also might not scale. Assuming you only have a few items, here is another way to do this:
SELECT
DATE_FORMAT(date, '%Y-%m') AS ym,
SUM(CASE WHEN item_id = 1 THEN item_count END) AS item1_total,
SUM(CASE WHEN item_id = 2 THEN item_count END) AS item2_total
FROM sales
GROUP BY
DATE_FORMAT(date, '%Y-%m');
This would generate output looking something like:
ym item1_total item2_total
2018-01 11 5
2018-02 7 3
Which version you use depends on how many months your report requires versus how many items might appear in your data.

filter all two tables to get all the data

I created a database for survey software. The two tables of the database are what I want to do, I want to get the average scores from the two date ranges and from a place, and get the ones without the answer as null or 0. I tried
SELECT
AVG(tbAnswers.averageScore)
FROM
tbDrivers
LEFT JOIN tbAnswers ON tbDrivers.driverId = tbAnswers.driverId
WHERE
tbDrivers.place = 'WDC'
GROUP BY
tbDrivers.driverId
But when I specify the date range, is not get the data of the drivers without answer.
SELECT AVG(tbAnswers.averageScore)
FROM tbDrivers LEFT JOIN tbAnswers ON tbDrivers.driverId = tbAnswers.driverId
WHERE tbDrivers.place = 'WDC'
AND answerDate BETWEEN '2018-11-28' AND '2018-12-03'
GROUP BY tbDrivers.driverId
Table structures:
CREATE TABLE `tbAnswers` (
`answerId` int(11) NOT NULL,
`answerDate` date NOT NULL,
`driverId` int(11) NOT NULL,
`score1` int(11) NOT NULL,
`score2` int(11) NOT NULL,
`score3` int(11) NOT NULL,
`averageScore` float NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tbAnswers` (`answerId`, `answerDate`, `driverId`, `score1`, `score2`, `score3`, `averageScore`) VALUES
(10, '2018-11-28', 1032, 0, 0, 0, 0),
(11, '2018-11-29', 1032, 9, 8, 3, 6.67),
(12, '2018-11-30', 1032, 0, 3, 2, 1.67),
(13, '2018-11-30', 1035, 10, 2, 10, 7.34),
(14, '2018-11-01', 1032, 5, 5, 5, 5),
(15, '2018-12-03', 1035, 5, 5, 7, 5.67);
CREATE TABLE `tbDrivers` (
`driverId` int(11) NOT NULL,
`nameSurname` varchar(32) NOT NULL,
`place` varchar(64) NOT NULL,
`plate` varchar(8) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tbDrivers` (`driverId`, `nameSurname`, `place`, `plate`) VALUES
(1032, 'Nick Oliver', 'WDC', 'B16186D'),
(1033, 'Nicholas Keller', 'WDC', 'ACG8095'),
(1034, 'Felipe Mendez', 'WDC', 'C26106E'),
(1035, 'Lowell Butler', 'WDC', '5123QK');
How can I solve this problem?
The problem arises because you have no records for driverid in tbanswers table.
Either make an entry in tbanswers or Use Query given by Forpas above or use this query
SELECT tbdrivers.driverid,
Avg(tbanswers.averagescore)
FROM tbdrivers
LEFT JOIN tbanswers
ON tbdrivers.driverid = tbanswers.driverid
WHERE tbdrivers.place = 'WDC'
AND answerdate BETWEEN '2018-11-28' AND '2018-12-03'
OR answerdate IS NULL
GROUP BY tbdrivers.driverid
Use your query which fetches the drivers that have at least 1 answer, UNION the drivers that have no answer:
(SELECT tbDrivers.driverId, AVG(tbAnswers.averageScore) AS avgscore
FROM tbDrivers LEFT JOIN tbAnswers ON tbDrivers.driverId = tbAnswers.driverId
WHERE tbDrivers.place = 'WDC'
AND answerDate BETWEEN '2018-11-28' AND '2018-12-03'
GROUP BY tbDrivers.driverId )
UNION
(SELECT t.driverId, NULL AS avgscore
FROM tbDrivers t
WHERE
NOT EXISTS (SELECT 1 FROM tbAnswers WHERE tbAnswers.driverId = t.driverId))
ORDER BY driverId
the result is:
driverId avgscore
1032 2.7800000111262
1033 (null)
1034 (null)
1035 6.505000114440918

Seek helps to speed up my mysql query

Edited for detail of my case.
CREATE TABLE IF NOT EXISTS `tbl_user` (
`id` int(50) NOT NULL auto_increment,
`fbuid` bigint(20) unsigned NOT NULL,
`fullname` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `fbuid` (`fbuid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
INSERT INTO `tbl_user` (`id`, `fbuid`, `fullname`) VALUES
(1, 1002, 'User B'),
(2, 1001, 'User A'),
(3, 1003, 'User C'),
(4, 1004, 'User D'),
(5, 1005, 'User E'),
(6, 1006, 'User F');
CREATE TABLE IF NOT EXISTS `tbl_userscores` (
`fbuid` bigint(20) NOT NULL,
`game_id` varchar(255) NOT NULL,
`score1` bigint(20) NOT NULL default '0',
`score2` bigint(20) NOT NULL default '0',
`score3` bigint(20) NOT NULL default '0',
`score4` bigint(20) NOT NULL default '0',
`created_date` datetime NOT NULL,
`updated_date` datetime NOT NULL,
PRIMARY KEY (`game_id`),
UNIQUE KEY `fbuid` (`fbuid`,`game_id`),
KEY `fbuid_2` (`fbuid`,`game_id`,`score4`),
KEY `fbuid_3` (`fbuid`,`game_id`,`score4`,`updated_date`),
KEY `fbuid_4` (`fbuid`,`game_id`,`score1`,`score2`,`score3`,`score4`,`created_date`,`updated_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tbl_userscores` (`fbuid`, `game_id`, `score1`, `score2`, `score3`, `score4`, `created_date`, `updated_date`) VALUES
(1001, '13361975565253060', 650, 3300, 7675, 14500, '2012-05-05 13:59:55', '2012-05-05 14:01:50'),
(1001, '1336278398787510', 3100, 87725, 326675, 573625, '2012-05-06 12:28:20', '2012-05-06 12:33:27'),
(1001, '13368015862343980', 12875, 82550, 158625, 299550, '2012-05-12 13:48:08', '2012-05-12 13:53:15'),
(1001, '13369691453105020', 7925, 58525, 283100, 368225, '2012-05-14 12:20:47', '2012-05-14 12:25:54'),
(1002, '1336328839124400', 1275, 11475, 31450, 50475, '2012-05-07 02:27:34', '2012-05-07 02:28:20'),
(1002, '13363686059958120', 11025, 48900, 72725, 115150, '2012-05-07 13:30:21', '2012-05-07 13:31:07'),
(1002, '13364088902032830', 6650, 6700, 10200, 17625, '2012-05-08 00:41:46', '2012-05-08 00:42:32'),
(1002, '13364910479425300', 3600, 17050, 60450, 114800, '2012-05-08 23:31:03', '2012-05-08 23:31:49'),
(1002, '13364949763272710', 17250, 168125, 479475, 596925, '2012-05-07 00:37:33', '2012-05-07 00:41:21'),
(1003, '13363240964199380', 84150, 84150, 84150, 84150, '2012-05-07 01:11:37', '2012-05-07 01:12:22'),
(1003, '1336465518338010', 297275, 351300, 437150, 468350, '2012-05-08 16:31:52', '2012-05-08 16:32:38'),
(1003, '13368122913207860', 0, 82350, 94150, 102750, '2012-05-12 16:45:20', '2012-05-12 16:48:09'),
(1003, '13368125091164060', 423925, 428125, 521875, 589750, '2012-05-12 16:54:00', '2012-05-12 16:54:47'),
(1004, '13363118226930570', 3275, 10975, 16250, 22900, '2012-05-06 21:43:58', '2012-05-06 21:44:43'),
(1004, '13366228756934380', 23275, 149100, 380600, 382075, '2012-05-10 12:08:46', '2012-05-10 12:10:49'),
(1004, '13366232802957960', 3650, 23525, 49975, 49975, '2012-05-10 12:14:55', '2012-05-10 12:15:42'),
(1005, '13361215491096720', 1200, 16250, 39125, 55800, '2012-05-04 16:52:59', '2012-05-04 16:54:29'),
(1005, '13361216729657120', 11000, 29800, 82575, 188550, '2012-05-04 16:55:03', '2012-05-04 16:56:33'),
(1005, '13361364491988250', 6925, 50925, 89100, 180425, '2012-05-04 21:01:12', '2012-05-04 21:02:43'),
(1005, '13362204979150640', 11300, 39800, 63675, 78725, '2012-05-05 20:22:08', '2012-05-05 20:23:36'),
(1005, '13362311869003160', 11575, 61500, 134200, 233600, '2012-05-05 23:20:17', '2012-05-05 23:21:48'),
(1005, '133628163373910', 3500, 40175, 131375, 251725, '2012-05-06 13:21:03', '2012-05-06 13:22:35'),
(1006, '13361224889844730', 6700, 30575, 49650, 50475, '2012-05-04 17:08:24', '2012-05-04 17:09:10'),
(1006, '13366294182421110', 16800, 87675, 119150, 206500, '2012-05-10 13:57:42', '2012-05-10 14:00:15'),
(1006, '13366296357158010', 23050, 99025, 229075, 381925, '2012-05-10 14:01:27', '2012-05-10 14:03:58'),
(1006, '13368319289949330', 22975, 130375, 350600, 355150, '2012-05-12 22:13:00', '2012-05-12 22:15:08');
With above data, I use sql below to get weekly highscore.
SELECT U1.fbuid, U1.fullname, U2.score4 AS weeklyhighscore, U2.created_date, U2.updated_date, TIMEDIFF( U2.updated_date, U2.created_date ) AS Duration
FROM tbl_user AS U1, (
SELECT fbuid, score4, MIN( updated_date ) AS updated_date, created_date
FROM tbl_userscores AS A
WHERE A.score4
IN (
SELECT MAX( `score4` ) AS best
FROM tbl_userscores AS B
WHERE A.fbuid = B.fbuid
AND B.score1 >0
AND B.score2 >0
AND B.score3 >0
AND B.score4 >0
AND `updated_date` >= '2012-05-06 00:00:00' AND `updated_date` <= '2012-05-12 23:59:59'
GROUP BY fbuid
)
GROUP BY A.fbuid
ORDER BY `A`.`score4` DESC , updated_date ASC
) AS U2
WHERE U1.fbuid = U2.fbuid
ORDER BY weeklyhighscore DESC
LIMIT 0 , 30
Expected result :
+-------+----------+-----------------+---------------------+---------------------+----------+
| fbuid | fullname | weeklyhighscore | created_date | updated_date | Duration |
| 1002 | User B | 596925 | 2012-05-07 00:37:33 | 2012-05-07 00:41:21 | 00:03:48 |
| 1003 | User C | 589750 | 2012-05-12 16:54:00 | 2012-05-12 16:54:47 | 00:00:47 |
| 1001 | User A | 573625 | 2012-05-06 12:28:20 | 2012-05-06 12:33:27 | 00:05:07 |
| 1004 | User D | 382075 | 2012-05-10 12:08:46 | 2012-05-10 12:10:49 | 00:02:03 |
| 1006 | User F | 381925 | 2012-05-10 14:01:27 | 2012-05-10 14:03:58 | 00:02:31 |
| 1005 | User E | 251725 | 2012-05-06 13:21:03 | 2012-05-06 13:22:35 | 00:01:32 |
+-------+----------+-----------------+---------------------+---------------------+----------+
I have two table, tbl_user and tbl_userscores. Each time user played a game, it will save times as score1 to score4 (4 session of scores, which score4 is final score).
tbl_userscores was indexed with (fbuid,score4,updated_date,create_date). It have 45K records, and keep growing.
I want to get top 30 weekly highscorer. This query took me average 45sec to complete.
So I would like to seek expert's advice on how to make it much better.
Thanks in advance.
I believe that most of the time is spent in correlated subquery that extracts max(score4) per user. It might be restructured to get top 30 scores at once and used as a filter to main table. Unfortunately, as you might get duplicates and need to take earliest updated_date to avoid them, there is additional derived table to get this filter. If this proves to be the slowest part, you might remove minUpdated derived table, envelop complete query and use not exists to select records with minimal updated_date per score4 only. This should be faster as you will tipically have just a bit over 30 records.
SELECT U1.fbuid,
U1.fullname,
U2.score4 AS weeklyhighscore,
U2.created_date,
U2.updated_date,
TIMEDIFF( U2.updated_date, U2.created_date ) AS Duration
FROM tbl_user AS U1
INNER JOIN tbl_userscores U2
ON U1.FbUid = U2.FbUid
/* Top 30 scores by user */
INNER JOIN
(
SELECT B.fbuid,
MAX(`score4`) AS best
FROM tbl_userscores AS B
WHERE B.score1 > 0
AND B.score2 > 0
AND B.score3 > 0
AND B.score4 > 0
AND `updated_date` >= '2012-05-06 00:00:00'
AND `updated_date` < '2012-05-13 00:00:00'
GROUP BY fbuid
ORDER BY best DESC
LIMIT 30
) A
ON U2.FbUid = A.FbUid
AND U2.Score4 = best
/* Filter by min(updated_date) in case of several same scores per user */
INNER JOIN
(
SELECT FbUid, Score4, MIN(updated_date) updated_date
FROM tbl_userscores
GROUP BY FbUid, Score4
) minUpdated
ON U2.FbUid = minUpdated.FbUid
AND U2.Score4 = minUpdated.Score4
AND U2.Updated_date = minUpdated.Updated_date
ORDER BY weeklyhighscore DESC
I've replaced date comparison with more promising pattern >= and <. This change avoids the datetime resolution issues (you might loose a record if an update is made in last 999 milliseconds of a day). This is also great defence tool - your query will work even if somebody somehow manages to input time portion of a date where your business logic does not expect one.