MySQL GROUP unless value equals 0 - mysql

I would like a query that groups the results together unless the value of job_num = 0 but I have been unable to work out how to do it.
This is my query as it stands (and doesn't work, I get a SQL error)...
SELECT SQL_CALC_FOUND_ROWS * FROM calls
WHERE parent_id = '$term' GROUP BY IF (job_num != 0)
ORDER BY date_start DESC LIMIT $page_position, $item_per_page
I have tried replacing job_num != 0 with job_num IS NOT NULL and get the same result.
The rest of the query works fine until I tried to exclude the job_num != 0
This a simplified version of the table
id | call_ref | job_num
_______________________
1 | 123445 | 2389
_______________________
2 | 342537 | 0
_______________________
3 | 876483 | 2389
_______________________
4 | 644686 | 5643
_______________________
5 | 654532 | 0
I would like to group the rows where the job_num != 0 but I still want to display the rows where job_num = 0 just not grouped together. The call_ref is not unique and there are a further 31 columns in the table I need the values of.
Using the above example rows with ids 1 and 3 would be grouped and 2,4 and 5 would still return results but not grouped.
The results I would like...
1 and 3 grouped (because the job_nums are the same but != 0)
2, 4, 5 not grouped

Can you try it with UNION?
(SELECT SQL_CALC_FOUND_ROWS * FROM calls
WHERE parent_id = '$term' where job_num != 0 GROUP BY (job_num)
ORDER BY date_start DESC LIMIT $page_position, $item_per_page)
UNION
(SELECT SQL_CALC_FOUND_ROWS * FROM calls
WHERE parent_id = '$term' where job_num = 0
ORDER BY date_start DESC LIMIT $page_position, $item_per_page);

if you use this request in PHP code Try
SELECT SQL_CALC_FOUND_ROWS FROM calls
WHERE parent_id = \'$term\' GROUP BY IF job_num <> 0
ORDER BY date_start DESC LIMIT $page_position, $item_per_page;

Try this one
SELECT *
FROM
(SELECT SQL_CALC_FOUND_ROWS *
FROM calls
WHERE parent_id = '$term'
AND job_num != 0
GROUP BY job_num
UNION SELECT SQL_CALC_FOUND_ROWS *
FROM calls
WHERE parent_id = '$term'
AND job_num = 0) AS T
ORDER BY date_start DESC LIMIT $page_position,
$item_per_page

Based on this comment:
#mcNets because ideally I'd like to display the results with job_num
!= 0, I just don't want the results with that value grouped together
You do not want group by (although I don't understand what "that value" refers to). I do note this is in direct contradiction of the first sentence of the question.
Does this do what you want?
SELECT SQL_CALC_FOUND_ROWS c.*
FROM calls c
WHERE c.parent_id = '$term' AND job_num <> 0
ORDER BY date_start DESC
LIMIT $page_position, $item_per_page;
EDIT:
It occurs to me that you might just want rows with the value of 0 to appear together and the rest to appear together. If that is the case, put the condition in the ORDER BY:
SELECT SQL_CALC_FOUND_ROWS c.*
FROM calls c
WHERE c.parent_id = '$term'
ORDER BY (job_num = 0), date_start DESC
LIMIT $page_position, $item_per_page;

You can use a derived table for the main query, then LEFT JOIN it on to the table that you want to display multiple rows for. For example:
SELECT *
FROM (
SELECT a.account_id 'CRM ID', a.lead_status 'Status', a.account_name 'Client Name', DATE_FORMAT(DATE_ADD(FROM_UNIXTIME(0), INTERVAL a.date_of_birth SECOND), '%d-%m-%Y') AS 'DOB', a.postcode 'Postcode',
a.phone 'Phone #.', a.email 'Email', IFNULL(i.invoice_date, 'Never') AS 'Last Invoice'
FROM mydb.invoices i
RIGHT JOIN mydb.members m ON i.member_id = m.member_id
RIGHT JOIN mydb.accounts a ON m.crm_id = a.account_id
WHERE (i.invoice_status LIKE 'SAVE%' OR i.invoice_date IS NULL)
AND a.lead_status IN ('SIGNED UP', 'BILLING')
AND a.account_position != 'DELETED'
GROUP BY a.account_id
) x
LEFT JOIN mydb.account_owners o ON x.`CRM ID` = o.account_id
In this query, the final LEFT JOIN is the one that makes the row display more than once, despite the GROUP BY, e.g.:
Aside: ignore the fact that the columns in the above query have got 'friendly' names. This query is taken from a reporting tool that shows the columns based upon what SQL returns :)

Related

mysql query does not find the latest record but the first one

I have the following table patient:
id
status
first_name
last_name
father_name
department
face_dev_id
created_at
updated_at
1
1
x
x
x
x
x
.........
.......
I also have the following patient_case_number table:
id
id_number
case_number
created_at
updated_at
1
1
2020
2021-04-18 12:17:10
....
2
1
3030
2021-04-18 12:22:20
....
Now I have the following query:
SELECT p.id_number, p.first_name, p.last_name, p.father_name, p.department, f.case_number
FROM poria.patient AS p
JOIN (
SELECT pcn.id_number, pcn.case_number
FROM poria.patient_case_number AS pcn
ORDER BY pcn.created_at ASC
) AS f
ON f.id_number = p.id
WHERE p.face_dev_id = 'x'
LIMIT 1
Long story short -
My target is, given face_dev_id value, get the latest case_number value off patient_case_number table.
This is how it should work:
Given face_dev_id value - search in patient table matching record with same value of face_dev_id (Note that this column is UNIQUE). Then - find the latest case_number value off patient_case_number table (using the created_at value). Search it in this table using the id_number column which matches the id column in patient table.
Currently this query would return me case_number=2020 but not case_number=3030 (and I need it to be 3030). Can you help fixing it?
The ORDER BY clause inside the subquery is useless.
You should place it in the outer query:
SELECT p.id_number, p.first_name, p.last_name, p.father_name, p.department, f.case_number
FROM poria.patient AS p JOIN poria.patient_case_number AS f
ON f.id_number = p.id
WHERE p.face_dev_id = 'x'
ORDER BY f.created_at DESC -- you get the latest with DESC not ASC
LIMIT 1
Just add LIMIT 1 in the inner query and change it from ASC to DESC - otherwise it would query the first one by most late created_at:
SELECT p.id_number, p.first_name, p.last_name, p.father_name, p.department, f.case_number
FROM poria.patient AS p
JOIN (
SELECT pcn.id_number, pcn.case_number
FROM poria.patient_case_number AS pcn
ORDER BY pcn.created_at DESC LIMIT 1
) AS f
ON f.id_number = p.id
WHERE p.face_dev_id = 'x'
LIMIT 1

sql optimization: count all rows through subquery or own query / other improvements

I'm trying to improve my mysql query. At first I'm trying to optimize that simple one:
SELECT * ,
(
SELECT COUNT(id)
FROM animal
WHERE type = :type AND timestampadopt > 0 AND (date BETWEEN DATE_FORMAT(CURDATE() , '%Y-%m-%d') - INTERVAL 1 YEAR AND DATE_FORMAT(CURDATE(),'%Y-%m-%d'))
) AS countanimals
FROM animal
WHERE type = :type AND timestampadopt > 0 AND (date BETWEEN DATE_FORMAT(CURDATE() , '%Y-%m-%d') - INTERVAL 1 YEAR AND DATE_FORMAT(CURDATE(),'%Y-%m-%d'))
ORDER BY timestamp DESC
LIMIT 1, 20;
COLUMNS:
id | timestampadd | timestampadopt | dateborn | animaltype | gender | chipped | smalldescger | smalldesceng | imagepath
On that affected site I loop all animals, with pagination. So you can see 20 animals and for the next 20 you have to use the next button.
I need to know for the pagination how many sites have to be displayed, so I have to count how many animals in total are, that is what the subquery does.
I measured with profiling the times and get following results:
0.0047s for the total query,
0.0023s for the subquery
In the database are only 5 rows!
On that site I offer some filters, like age +/- 1 year and is the animal already adopted, because of that I need the WHERE clause on both, which probably takes up the most performance, followed by the order by clause which is necessary to display the new ones first.
P.S. I need all columns from the table, I did some testings and SELECT * had same runtimes then selecting all 10 columns manually like some people recommend.
EDIT:
Would it be worth to exclude the smalltext (varchar 250), imagpath (varchar 50) columns in a own table and inner join them, the other columns I could probably need for later filter. But type, gender, chipped are tinyints.
Any improvement tips for me?
Should I do the subquery in a own query outside of the main one?
Edit: 31.07
SELECT a.* , c.cnt AS countanimals
FROM animal a
JOIN (
Select a1.date AS date1, a1.tmstmpadopt AS tmstmpadopt1, a1.type AS type1, COUNT(a1.id) as cnt
FROM animal a1
GROUP BY date1, tmstmpadopt1, type1
) c on (a.date = c.date1 AND a.tmstmpadopt = c.tmstmpadopt1 AND a.type = c.type1)
WHERE a.type = 1 AND tmstmpadopt = 0 AND (date BETWEEN DATE_FORMAT(CURDATE() , '%Y-%m-%d') - INTERVAL 100 YEAR AND DATE_FORMAT(CURDATE(),'%Y-%m-%d')- INTERVAL 1 YEAR)
ORDER BY a.timestamp DESC
LIMIT 1, 20;
Inline view may help you. So try this
SELECT a.*,c.cnt AS countanimals
FROM animal a
join (Select a1.dateborn, a1.timestampadopt, count(a1.id) as cnt
from animals a1
Where a1.timestampadopt > 0
and a1.type = :type
group by a1.dateborn, a1.timestampadopt) c on (a.dateborn = c.dateborn and a.timestampadopt = c.timestampadopt)
WHERE a.type = :type
AND a.timestampadopt > 0
AND a.dateborn BETWEEN DATE_FORMAT(CURDATE(),'%Y-%m-%d')-INTERVAL 1 YEAR AND DATE_FORMAT(CURDATE(),'%Y-%m-%d'))
ORDER BY a.timestamp DESC
LIMIT 1, 20;
Why don't you do the count on the script, as you process the rows, you can count them.

Fetch only 2 results, one for each different value of a concrete field

In a MYSQL table with those 5 fields: id, user_id, date, type, uid where type can be 1 or 2, I'm looking for a single query where I can fetch 2 results, one for type=1 and another one for type=2 based on date field.
Right now i have the following query which only gives me the last uid without taking care of the type field.
SELECT t.uid
FROM table AS t
WHERE t.user_id = 666
ORDER BY t.date
DESC LIMIT 1
Does anyone know how should modify this query so i can get the last uid for type=1 and the last one for type=2 based on date field? I would like to keep a a single query
Union all is probably the simplest method:
(select t.*
from t
where t.user_id = 666 and t.type = 1
order by date desc
limit 1
) union all
(select t.*
from t
where t.user_id = 666 and t.type = 2
order by date desc
limit 1
)
Finally i updated the query following this "paradigm":
http://dev.mysql.com/doc/refman/5.7/en/example-maximum-column-group-row.html
http://jan.kneschke.de/projects/mysql/groupwise-max/
This is how the query ended up:
SELECT s1.type, s1.uid
FROM t AS s1
LEFT JOIN t AS s2 ON s1.type = s2.type AND s1.date < s2.date
WHERE s2.date IS NULL;
Here's a visual example: http://hastebin.com/ibinidasuw.vhdl
Credits are for snoyes from #sql on Freenode. :)

Mysql: Order by max N values from subquery

I'm about to throw in the towel with this.
Preface: I want to make this work with any N, but for the sake of simplicity, I'll set N to be 3.
I've got a query (MySQL, specifically) that needs to pull in data from a table and sort based on top 3 values from that table and after that fallback to other sort criteria.
So basically I've got something like this:
SELECT tbl.id
FROM
tbl1 AS maintable
LEFT JOIN
tbl2 AS othertable
ON
maintable.id = othertable.id
ORDER BY
othertable.timestamp DESC,
maintable.timestamp DESC
Which is all basic textbook stuff. But the issue is I need the first ORDER BY clause to only get the three biggest values in othertable.timestamp and then fallback on maintable.timestamp.
Also, doing a LIMIT 3 subquery to othertable and join it is a no go as this needs to work with an arbitrary number of WHERE conditions applied to maintable.
I was almost able to make it work with a user variable based approach like this, but it fails since it doesn't take into account ordering, so it'll take the FIRST three othertable values it finds:
ORDER BY
(
IF(othertable.timestamp IS NULL, 0,
IF(
(#rank:=#rank+1) > 3, null, othertable.timestamp
)
)
) DESC
(with a #rank:=0 preceding the statement)
So... any tips on this? I'm losing my mind with the problem. Another parameter I have for this is that since I'm only altering an existing (vastly complicated) query, I can't do a wrapping outer query. Also, as noted, I'm on MySQL so any solutions using the ROW_NUMBER function are unfortunately out of reach.
Thanks to all in advance.
EDIT. Here's some sample data with timestamps dumbed down to simpler integers to illustrate what I need:
maintable
id timestamp
1 100
2 200
3 300
4 400
5 500
6 600
othertable
id timestamp
4 250
5 350
3 550
1 700
=>
1
3
5
6
4
2
And if for whatever reason we add WHERE NOT maintable.id = 5 to the query, here's what we should get:
1
3
4
6
2
...because now 4 is among the top 3 values in othertable referring to this set.
So as you see, the row with id 4 from othertable is not included in the ordering as it's the fourth in descending order of timestamp values, thus it falls back into getting ordered by the basic timestamp.
The real world need for this is this: I've got content in "maintable" and "othertable" is basically a marker for featured content with a timestamp of "featured date". I've got a view where I'm supposed to float the last 3 featured items to the top and the rest of the list just be a reverse chronologic list.
Maybe something like this.
SELECT
id
FROM
(SELECT
tbl.id,
CASE WHEN othertable.timestamp IS NULL THEN
0
ELSE
#i := #i + 1
END AS num,
othertable.timestamp as othertimestamp,
maintable.timestamp as maintimestamp
FROM
tbl1 AS maintable
CROSS JOIN (select #i := 0) i
LEFT JOIN tbl2 AS othertable
ON maintable.id = othertable.id
ORDER BY
othertable.timestamp DESC) t
ORDER BY
CASE WHEN num > 0 AND num <= 3 THEN
othertimestamp
ELSE
maintimestamp
END DESC
Modified answer:
select ilv.* from
(select sq.*, #i:=#i+1 rn from
(select #i := 0) i
CROSS JOIN
(select m.*, o.id o_id, o.timestamp o_t
from maintable m
left join othertable o
on m.id = o.id
where 1=1
order by o.timestamp desc) sq
) ilv
order by case when o_t is not null and rn <=3 then rn else 4 end,
timestamp desc
SQLFiddle here.
Amend where 1=1 condition inside subquery sq to match required complex selection conditions, and add appropriate limit criteria after the final order by for paging requirements.
Can you use a union query as below?
(SELECT id,timestamp,1 AS isFeatured FROM tbl2 ORDER BY timestamp DESC LIMIT 3)
UNION ALL
(SELECT id,timestamp,2 AS isFeatured FROM tbl1 WHERE NOT id in (SELECT id from tbl2 ORDER BY timestamp DESC LIMIT 3))
ORDER BY isFeatured,timestamp DESC
This might be somewhat redundant, but it is semantically closer to the question you are asking. This would also allow you to parameterize the number of featured results you want to return.

mySQL UNION fault

Im having trouble with a sql join involving a union. I'm trying to pull a COUNT and a field from 2 tables but getting an error.
The query:
$sql_result7 = mysql_query("(SELECT COUNT (*) as alertcount, date as alertdate FROM alerts WHERE to_id='$id' AND date > '$lastcheck') UNION (SELECT COUNT (*) as mailcount, date maildate FROM mobmail WHERE to_id='$id' AND to_del=0 AND seen = '0')", $db);
$rs7 = mysql_fetch_array($sql_result7);
$alerts = $rs7[alertcount];
$mails = $rs7[mailcount];
$last_alert = $rs7[alertdate];
$last_mail = $rs7[maildate];
Is it something to do with the date as alertdate part?
The error im getting is:
Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource
Besides the space between COUNT and (*), there is another issue. You can't use $rs7[mailcount] nor $rs7[maildate] in your PHP code because your query is equivalent to:
SELECT
COUNT(*) as alertcount
, date as alertdate
FROM alerts
WHERE to_id = '$id'
AND date > '$lastcheck'
UNION
SELECT
COUNT(*) --- No "as mailcount" here
, date --- No "as maildate" either
FROM mobmail
WHERE to_id = '$id'
AND to_del = 0
AND seen = '0'
and will return two rows and only 2 columns:
alertcount | alertdate
-----------|------------
24 | 2012-01-04
73 | 2011-11-11
Two ways to solve this problem:
Either keep the query (changing the UNION to UNION ALL to ensure that you always get 2 rows) and chnage the PHP to use the 2 rows.
Or change the query to:
SELECT alertcount, alertdate, mailcount, maildate
FROM
( SELECT
COUNT(*) AS alertcount
, date AS alertdate
FROM alerts
WHERE to_id = '$id'
AND date > '$lastcheck'
) AS a
CROSS JOIN
( SELECT
COUNT(*) AS mailcount
, date AS maildate
FROM mobmail
WHERE to_id = '$id'
AND to_del = 0
AND seen = '0'
) AS b