Select 2 colums together by max of one - mysql

I have a simple forum on my site which is almost perfect. So close, but yet so far...
The following SQL is used to create the front page view of the forum where users can view threads and a summary of who is replying:
SELECT COUNT(forumtopics.forumtopicline) AS totalthreads
, forumtopicline
, forumtopics.whenadded AS threaddate
, forumtopics.whoadded AS author
, forumtopics.title
, COUNT(forumreply.forumtopic) AS replies
, MAX(forumreply.whenadded) AS replydate
, forumreply.whoadded AS replier
FROM forumtopics
LEFT JOIN forumreply ON forumtopicline = forumreply.forumtopic
WHERE forumtopics.topic LIKE '%%'
OR forumtopics.title LIKE '%%'
OR forumreply.reply LIKE '%%'
GROUP BY forumtopicline
ORDER BY
CASE
WHEN COUNT(forumreply.forumtopic) > 0 THEN MAX(forumreply.whenadded)
WHEN COUNT(forumreply.forumtopic) = 0
OR forumreply.forumtopic IS NULL THEN MAX(forumtopics.whenadded)
END
DESC
LIMIT 0 , 30
Does the job almost perfectly. This provides me with the date the thread was started, the thread author and the date of the last reply and it correctly ranks them with the post having the latest reply at the top.
The problem is with
MAX( forumreply.whenadded ) AS replydate, forumreply.whoadded AS replier
This returns the date of the last post to the thread but the first responder's name regardless of the date. I am sure there must be a way to complete this rather elegant SQL to avoid application level manipulation. Any help gratefully received. Rather obviously, I need the forumreply.whoadded to be from the same row as the MAX(forumreply.whenadded)
DB=Mysql. Schema:
CREATE TABLE forumtopics (
forumtopicline SERIAL
,title VARCHAR(256)
,topic TEXT
,whenadded DATETIME
,whoadded VARBINARY(128)
,whenaltered DATETIME
,whoaltered VARBINARY(128)
,inuse BOOL
);
CREATE TABLE forumreply (
forumreplyline SERIAL
,forumtopic BIGINT
,reply TEXT
,whenadded DATETIME
,whoadded VARBINARY(128)
,whenaltered DATETIME
,whoaltered VARBINARY(128)
,inuse BOOL
);

Try this:
SELECT
totalthreads,
forumtopicline,
threaddate,
author,
title,
replies,
replydate,
(
SELECT
fr.whoadded
FROM
forumreply fr
WHERE
fr.forumtopic = forumtopicline
AND fr.reply LIKE '%%'
AND fr.whenadded = replydate
) as replier
FROM (
SELECT COUNT(forumtopics.forumtopicline) AS totalthreads
, forumtopicline
, forumtopics.whenadded AS threaddate
, forumtopics.whoadded AS author
, forumtopics.title
, COUNT(forumreply.forumtopic) AS replies
, MAX(forumreply.whenadded) AS replydate
, forumreply.whoadded AS replier
FROM forumtopics
LEFT JOIN forumreply ON forumtopicline = forumreply.forumtopic
WHERE forumtopics.topic LIKE '%%'
OR forumtopics.title LIKE '%%'
OR forumreply.reply LIKE '%%'
GROUP BY forumtopicline
ORDER BY
CASE
WHEN COUNT(forumreply.forumtopic) > 0 THEN MAX(forumreply.whenadded)
WHEN COUNT(forumreply.forumtopic) = 0
OR forumreply.forumtopic IS NULL THEN MAX(forumtopics.whenadded)
END
DESC
) as tmp

Related

How to speed up a very slow MySQL query?

I have a very slow MySQL syntax which is basically unusable since the table has grown to over 5000 entries. It takes more than 30 sec so the server sends error code and quits.
The syntax is:
SELECT
id,
user_id,
date
FROM
table
WHERE
id IN (
SELECT
MAX(id)
FROM
table
GROUP BY date
)
AND
company_id = '1'
AND
date > '1473700785'
AND
complete = '1'
AND
name = "random string"
ORDER BY id ASC
Structure:
id - int(11)
user_id - int(10)
company_id - int(11)
date - varchar(20)
complete - varchar(2)
name - varchar(75)
Do you have any idea what could be slowing it? It used to function as expected with a much smaller table size (under 1000 entries).
Apart from subquery (like below), the best method is indexing. Like what most people here suggested
SELECT id, user_id, date
FROM table min
--sub queries sometimes run faster than IN / NOT IN
JOIN (
SELECT SELECT MAX(id)
FROM table
GROUP BY date
)
max on max.id = min.id
WHERE min.company_id = '1'
AND min.date > '1473700785'
AND min.complete = '1'
AND min.name = "random string"
ORDER BY min.id ASC
At first you need index for date field.
And you need store date as integer, because you use this expression
date > '1473700785'
Indexing is good, but I don't see the need for a SUB-SELECT
SELECT
MAX(t.id) as id,
u.user_id,
t.date
FROM table t
JOIN table u ON u.id=MAX(t.id )
WHERE
t.company_id = '1'
AND
t.date > '1473700785'
AND
t. complete = '1'
AND
t.name = "random string"
GROUP BY t.date
ORDER BY t.id ASC

Get the count and total count joining 2 tables in mysql

I have 2 tables in the MySQL database :
1.
p_code{
code varchar(10) primary key,
discount decimal(4,2) not null,
valid_till date not null,
daily int not null,
total int non null,
max_amount decimal (6, 2) not null
}
2.
p_user{
code varchar(10) not null,
email varchar(50) not null,
date date not null,
primary key (code, email, date),
foreign key (code) references p_code(code)
}
now I want to get for a code in p_code total how many times an email has been used, total how many time the email has been used today and the details of the code.
I have tried the following query :
SELECT pc.discount, pc.valid, pc.daily, pc.total, pc.max_amount, c.tcount, c.count
FROM p_code AS pc
LEFT JOIN (
SELECT t.code, t.email, t.tcount, p.count
FROM (
SELECT code, email, COUNT( email ) AS tcount
FROM p_user
GROUP BY code, email
) AS t
LEFT JOIN (
SELECT code, email, COUNT( email ) AS count
FROM p_user
WHERE `date` = CURDATE( )
GROUP BY code, email
) AS p ON ( t.code, t.email ) = ( p.code, p.email )
) AS c ON pc.code = c.code
WHERE c.email = ?
AND pc.code = ?
But the problem is that if I do not have any entry for the code and email in the table p_user, it does not return any row.
What I require that it should return all the columns from p_code and 0 and 0 for tcount and count columns.
I think you can simplifiy your query this way, and anyway you'll need to put the condition on the left joined data... in the left join.
SELECT
c.discount,
c.valid,
c.daily,
c.total,
c.max_amount,
count(u.email) as totalCount,
sum(case when u.`date` = CURDATE() then 1 else 0 end) as dailyCount
FROM p_code c
LEFT JOIN p_user u on u.code = c.code and u.email = ?
WHERE c.code = ?
GROUP BY c.discount, c.valid, c.daily, c.total, c.max_amount
You could also do, for the "filter" on email :
WHERE c.code = ? and (u.email is null or u.email = ?)
You need to use the IFNULL function.
IFNULL(expr1,expr2)
If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns
expr2.
You need to modify your query like:
SELECT pc.discount
, pc.valid
, pc.daily
, pc.total
, pc.max
, IFNULL(c.tcount, 0) AS tcount
, IFNULL(c.count, 0) as count
FROM p_code AS pc
...

fetch datas from two tables and differentiate between them

I have two tables and want displays rows from the two one in the same page ordered by date created.
Here my query:
SELECT R.*, R.id as id_return
FROM return R
UNION
ALL
SELECT A.*, A.id as id_buy
FROM buy A
WHERE
R.id_buyer = '$user' AND R.id_buyer = A.id_buyer AND (R.stats='1' OR R.stats='3') OR A.stats='4'
ORDER
BY R.date, A.date DESC LIMIT $from , 20
With this query i get this error message:
Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given in ...
And here how i think i can differentiate between the results: (Knowing if the result is from the table RETURN or from the table BUY)
if(isset($hist_rows["id_return"])) {
// show RETURN rows
} else {
// show BUY rows
}
Please what is wrong with the query, and if the method to differentiate between tables are correct ?
EDIT
Here my tables sample:
CREATE TABLE IF NOT EXISTS `return` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_buyer` INT(12) NOT NULL,
`id_seller` INT(12) NOT NULL,
`message` TEXT NOT NULL,
`stats` INT(1) NOT NULL,
`date` varchar(30) NOT NULL,
`update` varchar(30)
PRIMARY KEY (`id`)
)
CREATE TABLE IF NOT EXISTS `buy` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_buyer` INT(12) NOT NULL,
`product` INT(12) NOT NULL,
`title` VARCHAR(250) NOT NULL,
`stats` INT(1) NOT NULL,
`date` varchar(30) NOT NULL
PRIMARY KEY (`id`)
)
Be sure the two table return and buy have the same number (and type sequence) of colummns .. if not the query fails
try select only the column you need from both the table and be sure that these are in correspondenting number and type
SELECT R.col1, R.col2, R.id as id_return
FROM return R
UNION ALL
SELECT A.col1, A.col2, A.id as id_buy
FROM buy A
WHERE
........
Looking to your code you should select the same number and type of column form boith the table eg de sample below:
(where i have added the different column and selecting null from the table where are not present)
I have aslore referred the proper where condition to each table ..
SELECT
R.'from return' as `source_table`
, R.`id`
, R.`id_buyer`
, null as product
, null as title
, R.`id_seller` as id_seller
, R-`message`
, R.`stats`
, R.`date`
, R.`update`
FROM return R
WHERE R.id_buyer = '$user'
AND (R.stats='1' OR R.stats='3')
UNION ALL
SELECT
A.'from buy'
, A.`id`
, A.`id_buyer`
, A.`product`
, A.`title`
, null
, null
, A.`stats`
, A.`date`
, null
FROM buy A
WHERE
A.id_buyer = '$user'
AND A.stats='4'
ORDER BY `source table`, date DESC LIMIT $from , 20
for retrive te value of the first column you should use in your case
echo $hist_rows["source_table"];
Otherwise i the two table are in some way related you should look at a join (left join) for link the two table and select the the repated column
(but this is another question)
But if you need left join you can try
SELECT
R.`id`
, R.`id_buyer`
, R.`id_seller` as id_seller
, R-`message`
, R.`stats`
, R.`date`
, R.`update`
, A.`id`
, A.`id_buyer`
, A.`product`
, A.`title`
, null
, null
, A.`stats`
, A.`date`
FROM return R
LEFT JOIN buy A ON R.id_buyer = A.id_buyer
AND R.id_buyer = '$user'
AND (R.stats='1' OR R.stats='3')
AND A.stats='4'
ORDER BY R.date DESC LIMIT $from , 20
When you use union all, the queries need to have exactly the same columns in the same order. If the types are not quite the same, then they are converted to the same type.
So, you don't want union all. I'm guessing you want a join. Something like this:
SELECT r.co1, r.col2, . . ., r.id as id_return,
b.col1, b.col2, . . ., b.id as id_buy
FROM return r JOIN
buy b
ON r.id_buyer = b.id_buyer
WHERE r.id_buyer = '$user' and
(r.stats in (1, 3) OR A.stats = 4)
ORDER BY R.date, A.date DESC
LIMIT $from, 20;
This query is only a guess as to what you might want.
Since you're using a union, select a string that you set identifying each query:
SELECT 'R', R.*, R.id as id_return
FROM return R
UNION
ALL
SELECT 'A', A.*, A.id as id_buy
This way your string 'R' or 'A' is the first column, showing you where it came from. We can't really know why it's failing without the full query, but I'd guess your $from might be empty?
As for your
Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given in ...
Run the query directly first to get the sql sorted out before putting it into your PHP script. The boolean false indicates the query failed.

mysql explain result interpretation

The query below does exactly what I expect it to do, is intuitive and doesn't generate intermediary tables. The downside is that it takes a long time to complete.
What I'll do in such cases is break the query in steps and create those intermediary tables & indexes. This time around I'd like to get a better handle on the hints provided by explain, and would appreciate any pointers: what obvious optimization steps am I missing in the query below?
Following the advice in MySQL query optimization and EXPLAIN for a noob I've created indices on order_number , order_type and item in orders_raw. It's unclear however how these would carry over character processing/regexes.
SELECT bundle_headers.order_number , bundle_headers.title , digital_subs.subscription_id , 1 as bundle_component
from
(
select order_number , substring( item , 1 , 3 ) as title , quantity from orders_raw
where order_type in (4,6)
) bundle_headers
inner join
(
select order_number , subscription_id , item as title , quantity from orders_raw
where order_type = 0 and length( item ) = 4
) digital_subs
on bundle_headers.order_number = digital_subs.order_number and
digital_subs.title regexp concat( '.*' , bundle_headers.title , '.*' ) and
bundle_headers.quantity = digital_subs.quantity
UNION
SELECT bundle_headers.order_number , bundle_headers.title , print_subs.subscription_id , 1 as bundle_component
from
(
select order_number , substring( item , 1 , 3 ) as title , quantity from orders_raw
where order_type in (4,6)
) bundle_headers
inner join
(
select order_number , subscription_id , item as title , quantity from orders_raw
where order_type = 0 and length( item ) = 3
) print_subs
on bundle_headers.order_number = print_subs.order_number and
print_subs.title regexp concat( '.*' , bundle_headers.title , '.*' ) and
bundle_headers.quantity = print_subs.quantity;
EDIT, #tin tran: I've yet to rigorously time both the query above and your query (after a couple corrections, copied below) starting out on an idle machine. I did submit it, and didn't see an obvious reduction in run time.
SELECT bundle_headers.order_number,
substring(bundle_headers.item,1,3) as title,
subs.subscription_id,
1 as bundle_component
FROM orders_raw bundle_headers
INNER JOIN orders_raw subs ON (bundle_headers.order_number = subs.order_number)
WHERE (bundle_headers.order_type = 4 OR bundle_headers.order_type = 6)
AND subs.order_type = 0
AND bundle_headers.quantity = subs.quantity
AND subs.item LIKE CONCAT('%',substring(bundle_headers.item,1,3),'%')
AND (length(subs.item) = 4 OR length(subs.item) = 3)
please try this query see if it produces the same result. And if it's any faster
SELECT bundle_headers.order_number,substring(bundle_headers.title,1,3) as title,subs.subscription_id,1 as bundle_component
FROM order_type bundle_headers
INNER JOIN orders_raw subs ON (bundle_headers.order_number = subs.order_number)
WHERE (bundle_headers.order_type = 4 OR bundle_headers.order_type = 6)
AND subs.order_type = 0
AND bundle_headers.quantity = subs.quantity
AND subs.title LIKE CONCAT('%',substring(bundle_headers.title,1,3),'%')
AND (length(subs.item) = 4 OR length(subs.item) = 3)

order by unselected column with union

Hey i have a query ordered by date, and i want in case the date is identical to order by time added to maintain stable results.
I have the following query :
(
SELECT 'album' AS RowType, id, DATE, title, time_added
FROM albums
WHERE is_temp =0
AND subject_id = '3'
)
UNION ALL (
SELECT 'video' AS RowType, id, DATE, title, time_added
FROM videos
WHERE is_temp =0
AND subject_id = '3'
)
UNION ALL (
SELECT 'story' AS RowType, id, DATE, title, time_added
FROM stories
WHERE is_temp =0
AND subject_id = '3'
)
ORDER BY DATE, time_added
Which works perfectly but i don't want to select the time_added column as i don't need it in my result set.
Can i somehow achive this? if i remove the time_added the query won't run.
I have found the following :
The ORDER BY clause causes the output rows to be sorted.
The argument to ORDER BY is a list of expressions that are used as the
key for the sort. The expressions do not have to be part of the result
for a simple SELECT, but in a compound SELECT each sort expression
must exactly match one of the result columns. Each sort expression may
be optionally followed by a COLLATE keyword and the name of a
collating function used for ordering text and/or keywords ASC or DESC
to specify the sort order.
Which i can't decide if means it's impossible or i should try harder.
try this
SELECT RowType
, id
, DATE
, title
FROM
(
SELECT 'album' AS RowType , id , DATE , title , time_added
FROM albums
WHERE is_temp = 0 AND subject_id = '3'
UNION ALL
SELECT 'video' AS RowType , id , DATE , title , time_added
FROM videos
WHERE is_temp = 0 AND subject_id = '3'
UNION ALL
SELECT 'story' AS RowType , id , DATE , title , time_added
FROM stories
WHERE is_temp = 0 AND subject_id = '3'
) s
ORDER BY DATE , time_added