Recursive Dynamic Query - mysql

I have this recursive SQL query where I obtain the hierarchy of IDs of every office (an office belongs to higher rank office and such) - inspired by #leftclickben's answer in How to do the Recursive SELECT query in MySQL?:
select #pv := o.office_id, o.display_name, (
select concat(concat(group_concat(#pv := t.parent_office_id order by t.parent_office_id asc SEPARATOR '.' ), '.'), t.office_id) pivot
from (select * from office order by (CASE WHEN parent_office_id < office_id THEN parent_office_id END) DESC,
(CASE WHEN parent_office_id > office_id THEN parent_office_id END) ASC) t
where t.office_id = #pv
) 'hierarchy'
from office o
group by o.office_id
order by o.office_id asc, o.parent_office_id desc
;
For this query to work, it needs perferct descendance or ascendance to be verified. This property is not verified in my data, and thus I had to take advantage of selective order by in the subquery.
I obtained very encouraging results (80% accuracy), and I was wondering if anyone could suggest more techniques to obtain better results?
Examples:
- for some office (ID = 97), its hierarchy is 1.2.4.14.97 (accurate value);
- for another case (ID = 101), I get: 111.101 (broken hierarchy);
In a nutshell, all results must start with 1.
A good sample:
http://sqlfiddle.com/#!9/82f13/1

Based on #Hart CO's suggestion, I solved this with three queries with two unions:
(select #pv := o.office_id, o.display_name, ifnull((
select concat(concat(group_concat(#pv := t.parent_office_id order by t.parent_office_id asc SEPARATOR '.' ), '.'), t.office_id) pivot
from (select * from office order by parent_office_id desc) t
where t.office_id = #pv
), '1.') 'hierarchy'
from office o
where office_id not in (26, 27, 28, 29, 30, 32, 33, 34, 41, 57, 58, 59, 60, 61, 62, 63, 64, 73, 74, 75, 76, 77, 79, 82, 91, 96, 101, 102, 103, 104)
group by o.office_id
order by o.parent_office_id desc)
union
(
select #pv := o.office_id, o.display_name, (
select concat(concat(group_concat(#pv := t.parent_office_id order by t.parent_office_id asc SEPARATOR '.' ), '.'), t.office_id) pivot
from (select * from office order by parent_office_id DESC) t
where t.office_id = #pv
) 'hierarchy'
from office o
where office_id in (26, 27, 28, 29, 30, 32, 33, 34, 41, 57, 58, 59, 60, 61, 62, 63, 64, 91, 96, 101, 102, 103, 104)
group by o.office_id
order by o.parent_office_id desc
)
union
(select #pv := o.office_id, o.display_name, (
select concat(concat(group_concat(#pv := t.parent_office_id order by (select parent_office_id from office where office_id = t.parent_office_id) asc SEPARATOR '.' ), '.'), t.office_id) pivot
from (select * from office oo order by (select parent_office_id from office where office_id = oo.parent_office_id) deSC) t
where t.office_id = #pv
) 'hierarchy'
from office o
where office_id in (73, 74, 75, 76, 77, 79, 82)
group by o.office_id
order by o.parent_office_id desc
);
Indeed, the first query was straight forward: parent_id is smaller.
For the second query, the parent_id was larger at the bottom level.
For the third query, the parent_id of the parent is larger, that's why I opted for a sub-query in the order by block for both group_concat and the sub-query with the alias t.
I can finally move forward with my ETL.

Related

GROUP_CONCAT not working in Sub-Query

I have tried this query to get the following data
SELECT
GROUP_CONCAT( sb.p_id ) ,
TRUNCATE( SUM( sb.total_amount ) , 2 ) grand_total,
TRUNCATE( SUM( sb.amount_wot ) , 2 ) sale_amount,
(SELECT TRUNCATE( SUM( (item_qty * item_price) * tax_price /100 ) , 2 )
FROM ci_bill_items
WHERE bill_id IN ( GROUP_CONCAT( sb.p_id ) ) ) tax_amount
FROM ci_suppliers_bills sb
WHERE sb.p_id >0
I got the expected result but not the tax_amount its return null but if i run the seperate query like :
SELECT TRUNCATE( SUM( (item_qty * item_price) * tax_price /100 ) , 2 )
FROM ci_bill_items
WHERE bill_id IN ( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 71 )
Then i get the correct result. But all i want in one query like the first one, is group_concat not working like i tried bill_id IN ( GROUP_CONCAT( sb.p_id ) )? Help is much appriciated.
You can use FIND_IN_SET like this:
SELECT
GROUP_CONCAT( sb.p_id ) ,
TRUNCATE( SUM( sb.total_amount ) , 2 ) grand_total,
TRUNCATE( SUM( sb.amount_wot ) , 2 ) sale_amount,
(SELECT TRUNCATE( SUM( (item_qty * item_price) * tax_price /100 ) , 2 )
FROM ci_bill_items
WHERE FIND_IN_SET(bill_id, GROUP_CONCAT( sb.p_id ) ) > 0 ) tax_amount
FROM ci_suppliers_bills sb
WHERE sb.p_id >0
GROUP_CONCAT doesn't return a list, it returns a string. To get a list that you can use with IN, you need to run a subquery:
WHERE bill_id IN (SELECT p_id FROM ci_suppliers_bills
WHERE p_id > 0)

Return all rows from sub-query SQL?

Now I wrote query SQL for getting rows limited by first query:
SELECT * FROM commenttoarticle a
WHERE a.idCommentToArticle = (SELECT CommentToArticlePID FROM commenttoarticle b)
ORDER BY a.idCommentToArticle DESC LIMIT 3
When I try to execute this query, I get:
#1242 - Subquery returns more than 1 row
How to resolve this issue? So, I need get all rows from sub-query.
If I want return one row - I need use GROUP BY, but it is not solution
Modified query:
SELECT a.idCommentToArticle FROM
commenttoarticle a WHERE a.CommentToArticlePID IN
(SELECT idCommentToArticle FROM commenttoarticle b) ORDER BY a.idCommentToArticle DESC LIMIT 3
Dump table commenttoarticle:
CREATE TABLE IF NOT EXISTS `commenttoarticle` (
`idCommentToArticle` int(11) NOT NULL AUTO_INCREMENT,
`CommentToArticleTime` int(11) NOT NULL,
`CommentToArticleIdArticle` int(11) NOT NULL,
`CommentToArticleComment` text NOT NULL,
`CommentToArticleIdUser` int(11) NOT NULL,
`CommentToArticlePID` int(11) NOT NULL,
PRIMARY KEY (`idCommentToArticle`),
UNIQUE KEY `idCommentToArticle_UNIQUE` (`idCommentToArticle`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=59 ;
--
-- Дамп данных таблицы `commenttoarticle`
--
INSERT INTO `commenttoarticle` (`idCommentToArticle`, `CommentToArticleTime`, `CommentToArticleIdArticle`, `CommentToArticleComment`, `CommentToArticleIdUser`, `CommentToArticlePID`) VALUES
(29, 0, 11, 'продажам?\nИнтересует не мега-звезда, а именно предметный, руками умеющий продавать сам и помогающий выстраивать это бизнесам.', 459, 0),
(30, 0, 11, '2', 459, 0),
(31, 0, 11, '3', 459, 0),
(36, 0, 11, '3.1', 459, 31),
(37, 1413822798, 11, 'also facing that prob. on the plteform of win 7', 459, 29),
(38, 0, 11, ' here i dont have internet connection.. #Samint Sinha thanks ill check it out maybe tomorrow.', 459, 29),
(39, 0, 11, ' Select max id and you will have dhe last row returned', 459, 29),
(32, 0, 11, '4', 459, 0),
(44, 1414354324, 11, 'How to do', 456, 29),
(45, 1414354469, 11, 'sfsfsf', 456, 29),
(46, 1414354708, 11, 'dddd', 456, 29),
(47, 1414357761, 11, 'sfsfs', 456, 0),
(57, 1414370833, 39, 'kkkppppppp', 456, 0),
(49, 1414358233, 11, 'VSF\nSFSF', 456, 0),
(50, 1414359589, 11, 'How to do', 456, 0),
(51, 1414359660, 11, 'sfsfsdf', 456, 0),
(52, 1414361057, 11, 'SDFSF', 456, 0),
(53, 1414364023, 11, 'dsfdsjfsifmsi', 456, 0),
(54, 1414364031, 11, 'sdfdskjfnskf', 456, 52),
(55, 1414364034, 11, 'sdfdskjfnskf', 456, 52),
(56, 1414364044, 11, 'fndsdfnsofosfi', 456, 52),
(58, 1414370841, 39, 'dfgdfgdgdgdgdgdfgdgdfg', 456, 0);
Result what I need:
Here is example at sqlfiddle: sqlfiddle.com/#!2/dbd82a/1 I need get last 3 rows with a unlimited COMMENTTOARTICLEPID for each first query, if exists. In example, I need get rows with IDCOMMENTTOARTICLE: 58, 57, 56, 52
SELECT a.*
FROM commenttoarticle a
JOIN commenttoarticle b
ON b.CommentToArticlePID = a.idCommentToArticle
ORDER
BY a.idCommentToArticle DESC
LIMIT 3
use IN instead of '=' before sub-select.
USe in instead of ==
SELECT * FROM commenttoarticle a WHERE a.idCommentToArticle in (SELECT CommentToArticlePID FROM commenttoarticle b) ORDER BY a.idCommentToArticle DESC LIMIT 3
Use IN instead of = because your query returns lot of values:
SELECT * FROM commenttoarticle a
WHERE a.idCommentToArticle IN (
SELECT CommentToArticlePID
FROM commenttoarticle b)
ORDER BY a.idCommentToArticle DESC LIMIT 3
You can use limit in both sub query or main query or both.
If you need only 3 result then add limit at main query
SELECT * FROM commenttoarticle a WHERE a.idCommentToArticle IN (SELECT CommentToArticlePID FROM commenttoarticle b) ORDER BY a.idCommentToArticle DESC LIMIT 3
If you want 3 result at sub query then it will be like this
SELECT * FROM commenttoarticle a WHERE a.idCommentToArticle IN (SELECT CommentToArticlePID FROM commenttoarticle b LIMIT 3) ORDER BY a.idCommentToArticle DESC
If you want both
SELECT * FROM commenttoarticle a WHERE a.idCommentToArticle IN (SELECT CommentToArticlePID FROM commenttoarticle b LIMIT 3) ORDER BY a.idCommentToArticle DESC LIMIT 3
I am thinking that your subquery returns more than 1 row hence replace your "=" with "IN". Like this...
SELECT * FROM commenttoarticle a
WHERE a.idCommentToArticle IN (SELECT CommentToArticlePID FROM commenttoarticle b)
ORDER BY a.idCommentToArticle DESC LIMIT 3

MySQL with Where IN and Distinct and Limit

SELECT id, server_id, start_time, end_time
FROM errors
WHERE server_id in (3, 12, 24, 25, 26, 27, 28, 29, 30)
ORDER BY id DESC
LIMIT 9
This is the query I'm trying to run to give me results where the server_id = 3, 12, 24, 25, 26, 27, 28, 29, 30. Instead, what I receive is server_id = 25, 25, 12, 25, 27, 27, 28, 28, 27. Note the repeating server_ids. The query gives me unique id but duplicate server_id.
Is there a way I can get results that would show me the last id for each server_id?
I've tried doing ORDER BY server_id but that gives me the same issue.
I tried running DISTINCT but that also does not work.
you'll have to use some aggregation functions.
Something like
select
server_id,
max(id),
avg(start_time),--for example
avg(end_time)--for example
from errors
where server_id in (3, 12, 24, 25, 26, 27, 28, 29, 30)
group by server_id
order by id desc
if you need tht start_time and end_time corresponding to the max id by server_id, you may do
select e.id, e.server_id, e.start_time, e.end_time
from errors e
join (select server_id, max(id) maxid
from errors
group by server_id) t
on t.maxid = e.id and e.server_id = t.server_id
where e.server_id in (3, 12, 24, 25, 26, 27, 28, 29, 30)
order by e.id DESC
The issue you have is that you need only one record from each server with the max ID.. and relative information. You need to limit the results to just that max ID... Here's one way...
SELECT id, server_id, start_time, end_time
FROM errors
WHERE server_id in (3, 12, 24, 25, 26, 27, 28, 29, 30)
and ID = (Select max(ID) from errors E2 where E2.server_ID=errors.server_ID)
ORDER BY id DESC
LIMIT 9

MySQL: How to select data from a column in another table

I have the following select statement:
SELECT
items.id,
items.name AS item_name,
items.tobuy,
items.list_id,
items.note,
items.unit,
MIN(NULLIF(packages.ppu, 0)) AS mppu,
packages.price AS mprice,
items._deleted
FROM
items
INNER JOIN
lists ON lists.id = items.list_id
LEFT JOIN
packages ON items.id = packages.item_id
WHERE
lists.user_id = 1 AND
items._deleted = '0'
GROUP BY
items.id
ORDER BY
tobuy DESC,
item_name
But what I really want is the price to come from the package that has the minimum ppu (which is not necessarily the package with the minimum price).
Any ideas?
Sample Records:
Table: items:
id, name, tobuy, list_id, note, unit, _deleted
95, test1, 1, 1, null, null, 0
69, test2, 1, 1, null, null, 0
194, test3, 1, 1, null, null, 0
162, test4, 1, 1, null, null, 0
Table: lists:
id, name, user_id
1, list1, 1
Table: packages:
id, item_id, price, ppu
392, 95, 0, 0
117, 95, 13.49, 0.078
391, 95, 0, 0
386, 69, 0, 0
387, 69, 0, 0
388, 69, 0, 0
368, 194, 4.58, 0.138
18, 194, 3.38, 0.177
17, 194, 3.88, 0.144
The results should be four items with the following information:
id, item_name, tobuy, list_id, note, unit, mppu, mprice, _deleted
95, test1, 1, 1, null, null, 0.078, 13.49, 0
69, test2, 1, 1, null, null, 0, 0, 0
194, test3, 1, 1, null, null, 0.138, 4.58, 0
162, test4, 1, 1, null, null, 0, 0, 0
Notice that item 162 doesn't have any corresponding packages, but it still shows up in the list. This is the reason for the "LEFT JOIN"
BTW, "mppu" stands for "minimum price per unit"
can you try for the 2nd left:
packages ON ( packages.item_id = items.id and select min(packages.ppu) from packages where item_id=item.id)
also make sure to change group by to items.id
not tested
With mysql this is surprisingly simplex.
Order by ppn and move the group by to an outer query
SELECT * FROM (
SELECT
items.id,
items.name AS item_name,
items.tobuy,
items.list_id,
items.note,
items.unit,
MIN(NULLIF(packages.ppu, 0)) AS mppu,
packages.price AS mprice,
items._deleted
FROM items
INNER JOIN lists
ON lists.id = items.list_id
AND lists.user_id = 1
LEFT JOIN packages
ON items.id = packages.item_id
WHERE items._deleted = '0'
ORDER BY
ppm,
tobuy DESC,
item_name) x
GROUP BY
id
The following works to get the results that I was looking for.
SELECT * FROM (
SELECT
items.id,
items.name AS item_name,
items.tobuy,
items.list_id,
items.note,
items.unit,
NULLIF(packages.ppu, 0) AS mppu,
packages.price AS mprice,
items._deleted
FROM
items
INNER JOIN
lists ON lists.id = items.list_id
AND lists.user_id = 1
LEFT JOIN
packages ON packages.item_id = items.id
WHERE
items._deleted = '0'
ORDER BY
tobuy DESC,
item_name,
IFNULL(mppu, 999999)) x
GROUP BY
x.id
ORDER BY
tobuy DESC,
item_name
Thanks to everyone for the input to get me through this.

MySQL include zero rows when using COUNT with LEFT OUTER JOIN and GROUP BY

How can I avoid eliminating the users with zero meetings? I'm aware there are similar questions, but this code is quite a bit more complex.
SELECT user.userID, user.contactName, user.email, COUNT( * ) AS meetingsCount
FROM user
LEFT OUTER JOIN meeting ON user.userID = meeting.userID
WHERE user.userID NOT
IN ( 1, 2, 3, 4, 5, 59, 62, 63, 64, 66, 69, 71, 72, 73, 78, 107 )
AND SUBSTRING( meeting.meetingCode, 5, 2 )
BETWEEN 12
AND 22
AND SUBSTRING( meeting.meetingCode, 7, 2 )
BETWEEN 01
AND 12
AND SUBSTRING( meeting.meetingCode, 9, 2 )
BETWEEN 01
AND 31
GROUP BY user.userID, contactName, email
ORDER BY meetingsCount DESC
You need to put the logic for the meeting code table in your join. Otherwise users matching the records you are filtering out from the meeting table will be filtered out of your results. Making your JOIN essentially an INNER join. I think you also should put single quotes around the values in your BETWEEN clauses.
SELECT user.userID, user.contactName, user.email, COUNT( meeting.userID ) AS meetingsCount
FROM user
LEFT OUTER JOIN meeting ON user.userID = meeting.userID
AND SUBSTRING( meeting.meetingCode, 5, 2 ) BETWEEN '12' AND '22'
AND SUBSTRING( meeting.meetingCode, 7, 2 ) BETWEEN '01' AND '12'
AND SUBSTRING( meeting.meetingCode, 9, 2 ) BETWEEN '01' AND '31'
WHERE user.userID NOT IN ( 1, 2, 3, 4, 5, 59, 62, 63, 64, 66, 69, 71, 72, 73, 78, 107 )
GROUP BY user.userID, contactName, email
ORDER BY meetingsCount DESC