Problem MYSQL Query join with output want - mysql

I has problem with MySql query. I has try many time for query, but still not get what I want. Maybe anyone can help my problem.
This is structure table and what output I want :
This is whats i try, but when #IDPERIODS=2, thats not show i want :
SET #IDPERIODS:=2;
SELECT billing.*
FROM _t_data_user
LEFT JOIN (
SELECT user_id as iduser,
IF(a.id_bill_type=b.id_bill_type,a.id_setting_bill,ifnull(b.id_setting_bill,a.id_setting_bill)) as idsettingbill,
id_user_group as group_user,
IF(a.id_bill_type=b.id_bill_type,a.id_bill_type, ifnull(b.id_bill_type,a.id_bill_type)) as idbilltype,
IF(a.id_bill_type=b.id_bill_type,a.id_period, ifnull(b.id_period,a.id_period)) as period,
IF(a.id_bill_type=b.id_bill_type,a.amount_bill, ifnull(b.amount_bill,a.amount_bill)) as amount_billing
FROM _t_data_user
LEFT JOIN _t_setting_bill_user b ON b.id_group_user=id_user_group and b.id_period=#IDPERIODS
LEFT JOIN _t_setting_bill_user a ON a.id_user=user_id and a.id_period=#IDPERIODS
WHERE IFNULL(a.id_period, b.id_period) = #IDPERIODS
) billing ON iduser = user_id
WHERE period = #IDPERIODS
GROUP BY user_id, idbilltype
This MySql table scheme :
Table structure and sample data:
CREATE TABLE `_t_data_user` (
`user_id` int(4) unsigned NOT NULL AUTO_INCREMENT,
`id_user_group` int(4) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `_t_data_user` (`user_id`, `id_user_group`)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2);
CREATE TABLE `_t_setting_bill_user` (
`id_setting_bill` int(11) unsigned NOT NULL AUTO_INCREMENT,
`id_group_user` int(4) DEFAULT NULL,
`id_user` int(4) DEFAULT NULL,
`id_period` int(4) DEFAULT NULL,
`id_bill_type` int(4) DEFAULT NULL,
`amount_bill` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id_setting_bill`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `_t_setting_bill_user`
(`id_setting_bill`, `id_group_user`, `id_user`,
`id_period`, `id_bill_type`, `amount_bill`)
VALUES
(1, 1, 0, 1, 1, 1000),
(2, 1, 0, 1, 2, 500),
(3, 0, 1, 1, 1, 900),
(4, 0, 1, 2, 1, 1000),
(5, 1, 0, 2, 2, 500),
(6, 2, 0, 1, 1, 1100);

This gives you the raw data you want:
SELECT *
FROM
setting_bill_user s
JOIN data_user d
ON
s.id_group_user = d.id_user_group OR
s.id_user = d.user_id
Look:
You just have to choose and alias the columns appropriately
I can't work out why your desired output is missing the 1000 row for user id 1/setting bill 1, but I'm sure you can add some WHERE clause to cover that, whatever the reason may be

It seems that removing a few parts from the subquery will return the result that you're looking for:
SELECT billing.*
FROM _t_data_user
JOIN (
SELECT user_id AS iduser,
IF(a.id_bill_type=b.id_bill_type,a.id_setting_bill,IFNULL(b.id_setting_bill,a.id_setting_bill)) AS idsettingbill,
id_user_group AS group_user,
IF(a.id_bill_type=b.id_bill_type,a.id_bill_type, IFNULL(b.id_bill_type,a.id_bill_type)) AS idbilltype,
IF(a.id_bill_type=b.id_bill_type,a.id_period, IFNULL(b.id_period,a.id_period)) AS period,
IF(a.id_bill_type=b.id_bill_type,a.amount_bill, IFNULL(b.amount_bill,a.amount_bill)) AS amount_billing
FROM _t_data_user t
LEFT JOIN _t_setting_bill_user b ON b.id_group_user=t.id_user_group
LEFT JOIN _t_setting_bill_user a ON a.id_user=t.user_id
WHERE IFNULL(a.id_period, b.id_period) = #IDPERIODS
) billing ON iduser = user_id
GROUP BY user_id, idbilltype;
I've removed AND id_period=#IDPERIODS of the ON condition in both of LEFT JOIN in the subquery.
I've changed LEFT JOIN to JOIN in the outer query because you were doing LEFT JOIN with a WHERE condition on the data from the right reference (the subquery). Which, in practice is just a normal JOIN so LEFT JOIN is unnecessary. Therefore, I also removed WHERE period = #IDPERIODS from the outer query.
And that's it. Other than that, most of your original query structures are still intact.
Demo fiddle

Halo bro desugha,
I think you should make it on programatic way, you should re-arrange and create object to combine data from data_user INTO setting_bill_user
SELECT compacted_usr_bill.* FROM (
SELECT billusr.*,
usr.user_id AS usr_id_usr, usr.id_user_group AS usr_id_group
FROM (
SELECT bill.id_setting_bill,
CASE
WHEN bill.id_user > 0 AND bill.id_group_user = 0 THEN bill.id_user
WHEN bill.id_user = 0 AND bill.id_group_user > 0 THEN bill.id_group_user
ELSE bill.id_user
END AS grouped_id_usr,
bill.id_period, bill.id_bill_type, bill.amount_bill
FROM _t_setting_bill_user AS bill)
AS billusr
LEFT JOIN _t_data_user AS usr
ON billusr.grouped_id_usr IN(usr.user_id, usr.id_user_group)
) AS compacted_usr_bill
This query will combine them, you can filter again with Grouping or Programmatic way

Related

MySQL: ORDER BY and GROUP BY together

I recently upgraded to MySQL 5.7.22 and my query stopped working. I have two tables "items" and "packages" where I'm trying to output a row for each item including a column for the package with the minimum price per unit, but ignore packages that have a price per unit set to 0.
Here's a minimal sample of tables and data:
CREATE TABLE `items` (
`id` int(11) NOT NULL
);
CREATE TABLE `packages` (
`item_id` int(11) NOT NULL,
`price_per_unit` float(16,6) DEFAULT 0
);
INSERT INTO `items` (`id`) VALUES
(1),
(2),
(3);
INSERT INTO `packages` (`item_id`, `price_per_unit`) VALUES
(1, 0.45),
(1, 0),
(1, 0.56),
(1, 0.34);
Here's the query:
SELECT
*
FROM
(
SELECT
items.id,
NULLIF(pkgs.ppu, 0) AS mppu
FROM
items
LEFT JOIN
(
SELECT
item_id,
price_per_unit AS ppu
FROM
packages
) AS pkgs ON pkgs.item_id = items.id
ORDER BY
IFNULL(mppu, 9999)
) X
GROUP BY
X.id
I was setting the zero values to null and then bumping their values to be much higher during the ordering. There must be a better way (especially since this method doesn't work any longer).
The expected output for this data is:
id mppu
1 0.34
2 null
3 null
I think your query is a bit too complex. What about this?
SELECT i.id,IFNULL(Min(p.price_per_unit), 'NONE')
FROM items i
LEFT JOIN packages p
ON ( i.id = p.item_id )
WHERE p.price_per_unit > 0
OR p.price_per_unit IS NULL
GROUP BY i.id
See this fiddle. I used this data:
INSERT INTO `items` (`id`) VALUES
(1),(2),(3);
INSERT INTO `packages` (`item_id`, `price_per_unit`) VALUES
(1, 0.45),
(1, 0),
(1, 0.56),
(1, 0.34),
(2, 9.45),
(2, 0),
(2, 0.56),
(2, 0.14);
And got this result:
id IFNULL(min(p.price_per_unit),'None')
1 0.340000
2 0.140000
3 None
Agree with GL,
SELECT * FROM GROUP BY
is not predictable .
i will rewrite the query with :
SELECT a.*,b.min_price_per_unit
FROM items a
LEFT JOIN (
SELECT item_id
,min(CASE
WHEN price_per_unit = 0
THEN 9999
ELSE price_per_unit
END) AS min_price_per_unit
FROM packages
GROUP BY item_id
) b ON a.id = b.item_id;

mysql- sort list based on empty and null values as last in column

I have a table of users with basic details id, their names and profile photo (link to photo, actually)
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL,
`userName` varchar(60) NOT NULL,
`photo` varchar(50) NULL,
`status` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=latin1;
INSERT INTO `users` (`id`, `userName`,`photo`, `status`) VALUES
(1, 'John', 'john.png',1),
(2, 'Jane', 'jane.png',1),
(3, 'Ali', '',1),
(6, 'Bruce', 'bruce.png',1),
(7, 'Martha', '',1),
(8, 'Sidney', '',1),
(10, 'Charlie', 'charlie.png',1),
(12, 'Elisa', '',1),
(14, 'Samantha', 'samantha.png',1),
(15, 'Hannah', 'hannah.png',1),
(16, 'Hannah', '',1),
(17, 'Kevin', 'kevin1.png',1),
(18, 'Kevin', 'kevin2.png',1),
(19, 'Ruth', '',1);
Not all users have profile picture. I would like to list these users in alphabetic order and also show users who have profile picture in front.
This is the query that I wrote:
select * from users ORDER BY photo DESC ;
It sorts users based on photo value but they are not being shown in alphabetic order.
Is it possible to list these users such that all users who have photos will appear top on the list with [userName] in alphabetic order?
SQL FIDDLE
Once you've changed '' to NULL...
SELECT * FROM users ORDER BY photo IS NULL,username;
You have empty vlaues here. So you can use this way.
SELECT * FROM users ORDER BY if(photo = '' or photo is null,1,0), userName
But in mysql has a method to order null values with (-).
SELECT * FROM users ORDER BY -photo DESC, userName ASC
Here photo column data order as null values last because of DESC order
OR
SELECT * FROM users ORDER BY
CASE WHEN photo IS NULL THEN 1 ELSE 0 END ASC, userName ASC
If you want 1st people without picture and then other (while sorting these 2 groups alphabetically):
select * from users ORDER BY photo,userName ASC ;
Waiting your feed back if it is not exactly what you want.

MySQL wrong result using MIN() function

Question:
In this example, we have the grades of 5 students from school 1. We want to know which student had the lowest grade.
We were expecting to get student number 4, but SQL returns student 1
Can someone help me?
Thanks in advance
Table 1:
CREATE TABLE `table1` (
`school_id` int(11) unsigned NOT NULL,
`student_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`grade` int(11) unsigned NOT NULL,
PRIMARY KEY (`student_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
Data:
INSERT INTO `table1` (`school_id`, `student_id`, `grade`)
VALUES
(1, 1, 20),
(1, 2, 15),
(1, 3, 18),
(1, 4, 12),
(1, 5, 15);
SQL Query:
SELECT t1.`school_id`, t1.`student_id`, MIN(t1.grade)
FROM table1 as t1
WHERE t1.`school_id`=1
GROUP BY t1.`school_id`;
Printscreen:
SELECT * FROM table1 ORDER BY grade LIMIT 1
If you want the worst performing student in each school, then that's...
SELECT x.*
FROM table1 x
JOIN
( SELECT school_id
, MIN(grade) grade
FROM table1
GROUP
BY school_id
) y
ON y.school_id = x.school_id
AND y.grade = x.grade;
http://sqlfiddle.com/#!9/f44cb2/1
With #tadman's tip, we came up with a solution:
You can find it bellow in case you came across with this same issue.
We didn't understand why we have to use the limit. if we take out the limit line, we will get a wrong result
SELECT t2.`school_id`, t2.`student_id`, t2.grade
FROM
(
SELECT t1.`school_id`, t1.`student_id`, t1.grade
FROM table1 as t1
WHERE t1.`school_id`=1
ORDER BY t1.`grade` ASC
limit 4294967295
)
as t2
GROUP BY t2.`school_id`;
Unless there are some more requirements for your problem, I guess you would be good with just:
select t1.school_id, t1.student_id, t1.grade
from table1 as t1,
(select school_id, min(grade) as grade from table1 group by school_id) as t2
where t1.school_id=t2.school_id
and t1.grade=t2.grade;

Get maximum id if there are parent id otherwise get all results

I have a table and having the following data
CREATE TABLE IF NOT EXISTS `tbl_ticket` (
`id` int(9) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL,
`ticket_title` varchar(250) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=16 ;
--
-- Dumping data for table `tbl_ticket`
--
INSERT INTO `tbl_ticket` (`id`, `parent_id`, `ticket_title`) VALUES
(1, 0, 'tyty'),
(2, 0, 'testing'),
(3, 0, 'test from ticket'),
(4, 0, 'test ticket'),
(5, 0, 'test ticket'),
(6, 0, 'test ticket'),
(7, 0, 'test ticket'),
(8, 5, 'test ticket'),
(9, 0, '1 Ticket'),
(10, 0, '2Ticket'),
(11, 2, 'ticket2'),
(12, 2, 'ticket1'),
(13, 0, 'title 1234'),
(14, 0, 'titles 1234'),
(15, 14, 'sample 1234');
I need to return all rows where id is not present in parent id from the table.
Also if id is present in the parent_id column, I want to get the row having the highest id which matches the parent_id.
i.e. I need to return rows with id 1, 3,4,6,7,8,9,10, 12,13, 15.
I tried this sql
SELECT `id` , `parent_id`
FROM `tbl_ticket`
WHERE id NOT
IN (
SELECT parent_id
FROM tbl_ticket
)
but it returns value 11 also, instead it should return 12 which is the row having highest id with parent_id =2
Assuming the 5 in your expected output is a typo, as 5 appears in the parent_id field for id=8, you can get your result by the union of two simple queries.
select t1.id
from tbl_ticket t1
where not exists (
select 1 from tbl_ticket
where parent_id = t1.id
)
and parent_id = 0
union all
select max(id)
from tbl_ticket
where parent_id <> 0
group by parent_id
order by id asc
Fiddle here
The query is in two parts. the first part gets all the tickets that are not present in another tickets parent_id field, and which themselves do not have a parent (parent_id = 0).
The second part of the query looks at those tickets that DO have a parent (parent_id <> 0), and for each group of tickets that share the same parent_id, selects the one with the max id.
The results are then combined with a union to give a single result set. Since the two result sets are mutually exclusive, we can use union all to skip over the duplicate check.
If I understand correctly, you can do this with not exists rather than combining two separate queries. The advantage is that no duplicate elimination is needed (as is needed when you use union):
select t.*
from tbl_ticket t
where not exists (select 1
from tbl_ticket t2
where t2.parent_id = t.id
) or
not exists (select 1
from tbl_ticket t2
where t2.parent_id = t.id and t2.id > t.id
);
The first gets all rows that have no parents. The second gets all rows with the maximum id for a parent.
For best performance, you want an index on tbl_ticket(parent_id, id).

How to select row pairs that match certain criteria in mysql

I have a table of things. Here is a simplified structure:
CREATE TABLE `things` (
`thing_id` int(11) NOT NULL AUTO_INCREMENT,
`thing_group` int(11) NOT NULL,
`thing_status` int(1) NOT NULL DEFAULT '0'
);
There are 2 types of things. Primary, which would have thing_id = thing_group and secondary, which would having a unqiue thing_id but the same thing_group as the primary item.
INSERT INTO `things` (`thing_id`, `thing_group`, `thing_status`) VALUES
(1, 1, 0),
(2, 1, 1),
(3, 3, 1),
(4, 3, 0),
(5, 5, 1),
(6, 5, 1),
(7, 7, 0),
(8, 7, 0),
(9, 9, 1),
(10, 9, 1),
I have thousands of these pairs.
thing_status can be 0 for either the primary or the secondary (or both), but I want to select ONLY a pair (at random) that has thing_status = 1 both for primary and secondary thing.
So from the sample data I provided, it should only return pairs which are either thing_id 5 and 6, or 9 and 10 (at random)
Hard part:
Some things can just have the primary thing only, and no secondary. The query should still return those and treat them equally to things that come in pairs.
Am i better off doing 2 queries or a convoluted single query?
Group your rows by thing_group and select those where the number of rows is the same as the sum of thing_status. Join the resulting set back to the original table on thing_group to obtain the actual rows corresponding to the groups. So:
SELECT
t.thing_id,
t.thing_group
FROM things t
INNER JOIN (
SELECT thing_group
FROM things
GROUP BY thing_group
HAVING COUNT(*) = SUM(thing_status)
ORDER BY RAND()
LIMIT 1
) g ON t.thing_group = g.thing_group
Not so hard, maybe the random part is a bit more tricky:
select *
from things
where
thing_group = (select thing_group
from things
where thing_status = 1
group by thing_group
having count(thing_id) = 2
limit 1)
limit 1
My intuition says that you should use 2 queries with a UNION ALL. But... with MySQL it's not always clear what works and what doesn't.
I believe that this query does what you want though:
SELECT t1.thing_id, t1.group_id
FROM things t1
LEFT JOIN things t2
ON t2.thing_id = t2.thing_group
AND t1.thing_id != t2.thing_id
WHERE (
t1.thing_id = t1.thing_group
AND t2.thing_id IS NULL
) OR (
t1.thing_group = t2.thing_id
AND t1.thing_id != t1.thing_group
)
GROUP BY t1.thing_id, t1.group_id