Merge multiple queries into single one - mysql

I wish to have total users of each role who make differents type_role of actions.
This is my tables:
t_users : id_user, username, userpass, status, ...
t_action: id_action, id_user, id_role, id_type_role, ...
t_role: id_role, libelle, status
t_type_role: id_type_role, libelle, status
This is my datas:
INSERT INTO `t_users` (`id_user`, `username`, `password`, `status`) VALUES
(1, 'foo', 'pass1', '1'),
(2, 'bar', 'pass2', '0'),
(3, 'fusion', 'pass3', '2');
INSERT INTO `t_role` (`id_role`, `libelle`, `statut`) VALUES
(1, 'Operator', 1),
(2, 'User', 1),
(5, 'Administrator', 1);
INSERT INTO `t_type_role` (`id_type_role`, `libelle`, `statut`) VALUES
(1, 'Executif', 1),
(2, 'home', 1),
(3, 'System', 1);
INSERT INTO `t_action` (`id_action`, `id_user`, `id_role`, `id_type_role`) VALUES
(1, 1, 5, 3),
(2, 2, 5, 3);
Expected result:
ROLE
Executif
home
System
Administrator
0
0
2
Operator
0
0
0
User
0
0
0
For each role, COUNT Users who have make actions WITH EACH column Type_of_role :
this is my queries written on each cell:
ROLE
Type_role_1
Type_role_2
DISTINCT(t_role.libelle)role_1
SELECT count(U.id) Type_role_1FROM t_users UJOIN t_action AON A.id_user=U.idJOIN t_type_role TRON TR.id=A.type_roleJOIN t_role RON R.id=A.id_roleWHERE R.libelle= role_1 AND TR.libelle= Type_role_2
SELECT count(U.id) Type_role_1FROM t_users UJOIN t_action AON A.id_user=U.idJOIN t_type_role TRON TR.id=A.type_roleJOIN t_role RON R.id=A.id_roleWHERE R.libelle= role_1 AND TR.libelle= Type_role_2
DISTINCT(t_role.libelle)role_2
SELECT count(U.id) Type_role_1FROM t_users UJOIN t_action AON A.id_user=U.idJOIN t_type_role TRON TR.id=A.type_roleJOIN t_role RON R.id=A.id_roleWHERE R.libelle= role_2 AND TR.libelle= Type_role_1
SELECT count(U.id) Type_role_2FROM t_users UJOIN t_action AON A.id_user=U.idJOIN t_type_role TRON TR.id=A.type_roleJOIN t_role RON R.id=A.id_roleWHERE R.libelle= role_2 AND TR.libelle= Type_role_2
Please, help

First solution:
As suggested by another user on comment, i've write the single query like this one:
SELECT DISTINCT
R.libelle ROLE,
COUNT( IF( T.libelle= 'Executif', 1, NULL )) AS Executif,
COUNT( IF( T.libelle= 'Home', 1, NULL )) AS Home,
COUNT( IF( T.libelle= 'System', 1, NULL )) AS System
FROM t_users U
JOIN t_action A ON A.id_user = U.id_user
JOIN t_role R ON R.id_role = A.id_role
JOIN t_type_role T ON T.id_type_role = A.id_type_role
GROUP BY ROLE
it works fine.But how about add another COUNT conditions dynamically?
Update:
this conditions should have to be built dynamically. Because, they are based on the table T.libelle wich could grow on time. example: If we add a new value to T.libelle, the query should have to include this new value on query accoding to the syntax of CONDITIONS...
Below,
CONDITIONS = COUNT( IF( T.libelle= 'Executif', 1, NULL )) AS Executif,
COUNT( IF( T.libelle= 'Home', 1, NULL )) AS Home,
COUNT( IF( T.libelle= 'System', 1, NULL )) AS System
So, if we add an entry new_libelle to T.libelle, CONDITIONS should become
CONDITIONS = COUNT( IF( T.libelle= 'Executif', 1, NULL )) AS Executif,
COUNT( IF( T.libelle= 'Home', 1, NULL )) AS Home,
COUNT( IF( T.libelle= 'System', 1, NULL )) AS System
COUNT( IF( T.libelle= 'New_libelle', 1, NULL )) AS New_libelle

Related

Find the longest chain of records not containing 1 record

I have a table that contains 4 columns - UserID, FromLocation, ToLocation, and Date. I need to pull the "length" of the longest chain (UserID going from FromLocation to ToLocation, as long as the chain does not contain "FAKE_LOCATION").
So, based on the following data set:
CREATE TABLE IF NOT EXISTS `tableA` (
`UserID` int(11) unsigned NOT NULL,
`FromLocation` varchar(20) NOT NULL,
`ToLocation` varchar(20) NOT NULL,
`Date` datetime NOT NULL
) DEFAULT CHARSET=utf8;
INSERT INTO `tableA` (`UserID`, `FromLocation`, `ToLocation`, `Date`) VALUES
(1, 'Loc 1', 'Loc 2', '2022-01-01'),
(1, 'Loc 2', 'Loc 3', '2022-01-02'),
(1, 'Loc 3', 'Loc 5', '2022-01-03'),
(1, 'Loc 5', 'Loc 18', '2022-01-04'),
(1, 'Loc 18', 'Loc 2', '2022-01-05'),
(1, 'Loc 2', 'Loc 4', '2022-01-06'),
(1, 'Loc 4', 'FAKE_LOCATION', '2022-01-07'),
(1, 'FAKE_LOCATION', 'Loc 7', '2022-01-08'),
(1, 'Loc 7', 'Loc 17', '2022-01-09'),
(2, 'Loc 3', 'Loc 4', '2022-01-05'),
(2, 'Loc 4', 'Loc 5', '2022-01-06'),
(2, 'Loc 5', 'FAKE_LOCATIOIN', '2022-01-07'),
(3, 'Loc 3', 'Loc 4', '2022-01-05'),
(3, 'Loc 4', 'FAKE_LOCATIOIN', '2022-01-07'),
(3, 'FAKE_LOCATIOIN', 'Loc 3', '2022-02-07'),
(3, 'Loc 3', 'Loc 5', '2022-02-08');
I'm trying to generate the following data set:
UserID
Longest Chain
1
7
2
3
3
2
For UserID 1, the longest chain is: Loc 1 -> Loc 2 -> Loc 3 -> Loc 5 -> Loc 18 -> Loc 2 -> Loc 4.
For User ID 2, the longest chain is: Loc 3 -> Loc 4 -> Loc 5
For User ID 3, the longest chain is: Loc 3 -> Loc 4. As well as, Loc 3 -> Loc 5
I've created an SQLFiddle for it. Any help shall be appreciated!
Based on the data you provided, I'm assuming that a record that follows another for an ID will always have the same from location as the prior records to location (sorting by UserID and date).
If that's the case, this should solve your problem -
#get max chain count by UserID
SELECT
UserID,
MAX(chain_count) AS LongestChain
FROM(
#count the number of records in each chain, add one for terminal ToLocation for chain
SELECT
UserID,
chained,
COUNT(chained) + 1 AS chain_count
FROM(
#start a new chain every time FAKE_LOCATION is present for a UserID
SELECT
UserID,
FromLocation,
ToLocation,
Date,
(#row_number4:=CASE
WHEN (ToLocation = 'FAKE_LOCATION' OR FromLocation = 'FAKE_LOCATION')
THEN #chain:= #chain + 1
ELSE #chain
END) AS chained
FROM tableA as a, (SELECT #chain:=0,#row_number4:=0) as chain
ORDER BY UserID, Date) chainvalues
WHERE ToLocation != 'FAKE_LOCATION' AND FromLocation != 'FAKE_LOCATION'
GROUP BY UserID, chained) chaincounts
GROUP BY UserID
ORDER BY UserID
;
It was a bit more challenging using MySQL 5.6 than it otherwise could have been since it doesn't allow for window functions.
Here is the fiddle with the query
Here is another example that addresses instances where movement is not necessarily restricted on the next record to the from location for the prior record:
#get max chain count by UserID
SELECT
UserID,
MAX(chain_count) AS LongestChain
FROM(
#count the number of records in each chain, add one for terminal ToLocation for chain
SELECT
UserID,
chained,
COUNT(chained) + 1 AS chain_count
FROM(
#start a new chain every time FAKE_LOCATION is present for a UserID, or b.UserID is null where not the first user record
SELECT
a.UserID,
a.FromLocation,
a.ToLocation,
a.Date,
b.UserID as bUserID,
(#row_number4:=CASE
WHEN (a.ToLocation = 'FAKE_LOCATION' OR a.FromLocation = 'FAKE_LOCATION' OR b.UserID IS NULL) AND userrow != 1
THEN #chain:= #chain + 1
ELSE #chain
END) AS chained,
userrow
FROM (
#get left side of table, userrow increments starting at one for each user instance, num shifts down one to join to b sides to location to ensure date order is followed
SELECT
(#row_number3:=CASE
WHEN #user = UserID
THEN #row_number3 + 1
ELSE 1
END) AS userrow,
#user:=UserID UserID,
FromLocation,
ToLocation,
Date,
(#row_number:=#row_number + 1) - 1 AS num
FROM tableA, (SELECT #row_number:=0) AS t, (SELECT #user:=0,#row_number3:=0) as z
ORDER BY UserID, Date) a
LEFT JOIN (
#right side, effectively shifts tableA up one
SELECT
UserID,
FromLocation,
ToLocation,
Date,
(#row_number2:=#row_number2 + 1) AS num
FROM tableA, (SELECT #row_number2:=0) AS t
ORDER BY UserID, Date) b
ON a.FromLocation = b.ToLocation and a.UserID = b.UserID and a.num = b.num, (SELECT #chain:=0,#row_number4:=0) as chain
ORDER BY a.num) chainvalues
WHERE ToLocation != 'FAKE_LOCATION' AND FromLocation != 'FAKE_LOCATION'
GROUP BY UserID, chained) final
GROUP BY UserID
ORDER BY UserID
;
Here is the fiddle for that example (I added in another record for testing purposes)

MySQL query to create a report

Hello Can any one help me with this query
INSERT INTO userrole (Id, Name, Description, IsEnabled, Created, CreatedBy, Updated, UpdatedBy) VALUES
(1, 'Role_1', '', 1, '2020-04-14 18:30:00', 'Admin', NULL, NULL),
(2, 'Role_2', 'Description', 1, '2020-04-15 18:30:00', 'ADMIN', '2020-04-16 18:30:00', 'John Smith'),
(3, 'Role_3', 'Description', 0, '2020-04-15 18:30:00', 'John SMITH', '2020-04-16 18:30:00', 'Ben SMITH'),
(4, 'Role_4', 'Description', 1, '2020-04-18 18:30:00', 'bEn SmiTh', '2020-04-20 18:30:00', 'BEN SMITH');
SELECT
UPPER(CreatedBy) AS UserName,
COUNT(UPPER(CreatedBy)) AS NoOfCreatedRoles,
SUM(CASE
WHEN IsEnabled = 1 THEN 1
ELSE - 1
END) AS NoOfCreatedAndEnabledRoles,
CASE
WHEN UpdatedBy IS NOT NULL THEN COUNT(UPPER(UpdatedBy)) ELSE -1
END AS NoOfUpdatedRoles
FROM UserRole
GROUP BY CreatedBy
ORDER BY CreatedBy desc
Give a try with this:
SELECT Result.UserName, Result.NoOfCreatedRoles, Result.NoOfCreatedAndEnabledRoles, Result.NoOfUpdatedRoles
FROM (
Select UPPER(TRIM(CreatedBy)) AS 'UserName',
COUNT(UPPER(CreatedBy)) AS NoOfCreatedRoles,
SUM(CASE WHEN IsEnabled = 1 THEN 1 ELSE - 1 END) AS NoOfCreatedAndEnabledRoles,
(SELECT CASE when COUNT(*) = 0 THEN -1 ELSE Count(*) END
FROM UserRole as URQ
WHERE URQ.UpdatedBy = UR.CreatedBy) AS NoOfUpdatedRoles
FROM UserRole UR
GROUP BY CreatedBy
ORDER BY UserName
) as Result
ORDER BY NoOfCreatedRoles

SQL: count and sum based on conditions from the related table

I have two tables;
countries(id, name, region);
1, 'UK', '1';
2, 'USA', '1';
3, 'AUSTRALIA', '1';
4, 'CHINA', '0';
5, 'INDIA', '0';
6, 'SRI LANKA', '0' ;
and
tickets(id, country_id, issued_date, holder, gender, fee, canceled);
100, 2, 2017-08-15, 'Person 1', 'M', 200, '1';
101, 2, 2017-08-15, 'Person 2', 'M', 200, '0';
103, 3, 2017-08-15, 'Person 3', 'M', 200, '0';
104, 5, 2017-08-16, 'Person 1', 'M', 200, '0';
105, 6, 2017-08-16, 'Person 1', 'M', 200, '0';
106, 1, 2017-08-17, 'Person 1', 'M', 200, '0';
107, 3, 2017-08-18, 'Person 1', 'M', 200, '1';
108, 4, 2017-08-18, 'Person 1', 'M', 200, '0';
I want to group all the tickets based on issued_date with some aggregates fields to generate the summary. Here is my query:-
SELECT
issued_date,
COUNT(*) as total_tickets,
COUNT(CASE WHEN canceled = '0' THEN 1 ELSE NULL END) as issued_tickets,
SUM(CASE WHEN canceled = '0' THEN fee ELSE NULL END) as total_amount
FROM tickets
GROUP BY issued_date;
But, how to use COUNT and SUM for related table countries? For example, I want to show how many tickets were sold on a date (2017-08-15) from a country having region = '1'.
I tried the following, but the results are not correct for region_1 field
SELECT
issued_date,
COUNT(*) as total_tickets,
COUNT(CASE WHEN canceled = '0' THEN 1 ELSE NULL END) as issued_tickets,SUM(CASE WHEN canceled = '0' THEN fee ELSE NULL END) as total_amount,
(SELECT COUNT(countries.id) FROM countries WHERE countries.id = tickets.country_id && countries.region = '1') as region_1
FROM tickets
GROUP BY issued_date;
I would use a derived table that is grouped on issue_date, country_id and region and use that derived table in an inner join.
SELECT issued_date
,COUNT(*) AS total_tickets
,COUNT(CASE WHEN canceled = '0' THEN 1 ELSE NULL END) AS issued_tickets
,SUM(CASE WHEN canceled = '0' THEN fee ELSE NULL END) AS total_amount
,tickets_by_region.total_region_1_tickets
FROM tickets
INNER JOIN (
SELECT issued_date
,country_id
,countries.region
,COUNT(*) AS total_region_1_tickets
FROM tickets
INNER JOIN countries ON (countries.id = tickets.country_id)
GROUP BY issued_date
,countries.country_id
,countries.region
) tickets_by_region ON (
tickets_by_region.issued_date = tickets.issued_date
AND tickets_by_region.country_id = tickets.country_id
AND tickets_by_region.region = '1'
) AS region_1
GROUP BY issued_date;
HTH.
You probably need to use INNER JOIN and GROUP BY with HAVING which allow append next table on related keys and add to additional summary or counts you need to use them like sub-query because they need to work with no filtered data.
Approach is prepare data in sub-query and then JOIN the filtered data
to your main table which can do final filtering for final result.
SQL can looks like bellow (tested on local)
SELECT t1.issued_date, COUNT(t1.id) as sum_tickets, t2.region, t3.total_tickets
FROM tickets t1
LEFT JOIN (SELECT id, COUNT(id) as total_tickets FROM tickets) t3 ON t3.id = t1.id
INNER JOIN countries t2 ON t2.id = t1.country_id
GROUP BY t1.issued_date
HAVING (t2.region = '1')
Output is
issued_date, sum_tickets, region, total_tickets
2017-08-15, 3, 1, 8
2017-08-17, 1, 1, null
2017-08-18, 2, 1, null
You can add more conditions to HAVING in query.
I would just use conditional aggregation with a JOIN:
SELECT t.issued_date, COUNT(*) as total_tickets,
SUM(t.canceled = 0) as issued_tickets,
SUM(CASE WHEN t.canceled = 0 THEN t.fee END) as total_amount,
SUM(c.region = 1) as num_region_1
FROM tickets t JOIN
countries c
ON t.country_id = c.id
GROUP BY t.issued_date;

Two Limit Clauses in one statement?

I am trying to build a product review page where users can comment any submitted reviews like Amazon. I want the page to display 3 user reviews as well as 2 responses to each of the reviews if they exist.
Here's the table
CREATE TABLE product_review
(`ID` int, `username` varchar(21), `review_title` varchar(30), `review_or_reply` int)
;
INSERT INTO product_review
(`ID`, `username`, `review_title`, `review_or_reply`)
VALUES
(1, 'Tom', 'Rip-off', 0),
(2, 'Peter', 'Rip-off', 1),
(3, 'May', 'Rip-off', 1),
(4, 'June', 'Rip-off', 1),
(5, 'Tommy', 'Worth the Price', 0),
(6, 'Sammy', 'Worth the Price', 1),
(7, 'Sam', 'Worth the Price',1),
(8, 'Bryan','Worth the Price',1),
(9, 'Sally', 'Average Product', 0)
;
The review_or_reply field is effectively a Yes or No field, where 0 means it's a review and 1 is the review's comments by other users.
Is there a single select statement that can limit 3 reviews and bring up two of their comments? For example:
Select `username`,`review_title`,`reply` from product_review where review_or_reply ='0' Limit 3
Select `username`,`review_title`,`reply` from product_review where review_or_reply = '1' and title = 'Rip-off' Limit 2
Select `username`,`review_title`,`reply` from product_review where review_or_reply = '1' and title = 'Worth the Price' Limit 2
Select `username`,`review_title`,`reply` from product_review where review_or_reply = '1' and title = 'Average Product' Limit 2
I want the output to be like this:
username review_title review_or_reply
Tom Rip-off 0
Peter Rip-off 1
May Rip-off 1
Tommy Worth the Price 0
Sammy Worth the Price 1
Sam Worth the Price 1
Sally Average Product 0
this will return 3 review_titles and then pull out two responses to that
SELECT
pr.*,
IF( #A = t.review_title,
IF(#B = 3, #B := 1, #B := #B +1)
, #B
) AS group_col,
#A := t.review_title
FROM (
SELECT
id,
username,
review_title
FROM product_review
WHERE reply ='0' LIMIT 3
) t
JOIN product_review pr ON pr.review_title=t.review_title
CROSS JOIN (SELECT #A := "", #B := 1) AS temp
GROUP BY group_col, review_title
ORDER BY id;
EDIT:
if there are more than one reply that is 0 in the database like so then this query will check for that. (since you did specify in the other queries that the reply had to be 1).
INSERT INTO product_review
(`ID`, `username`, `review_title`, `reply`)
VALUES
(1, 'Tom', 'Rip-off', 0),
(2, 'Peter', 'Rip-off', 1),
(3, 'May', 'Rip-off', 0),
(4, 'June', 'Rip-off', 1),
(5, 'Tommy', 'Worth the Price', 0),
(6, 'Sammy', 'Worth the Price', 1),
(7, 'Sam', 'Worth the Price',1),
(8, 'Bryan','Worth the Price',1),
(9, 'Sally', 'Average Product', 0),
(10, 'Timothy', 'Rip-off', 1)
notice that at id 3 there is a reply of 0 with id 10 a reply of 1. this query will correctly skip the reply = 0.
SELECT
pr.*,
IF( #A = t.review_title,
IF(pr.reply = 0, 1,
IF(#B = 3, #B := 1, #B := #B +1)
), #B
) AS group_col,
#A := t.review_title
FROM (
SELECT
DISTINCT
id,
username,
review_title
FROM product_review
WHERE reply ='0'
GROUP BY review_title
LIMIT 3
) t
JOIN product_review pr ON pr.review_title=t.review_title
CROSS JOIN (SELECT #A := "", #B := 1) AS temp
GROUP BY group_col, review_title
ORDER BY id;
DEMO
...or slower but simpler...
SELECT x.*
FROM product_review x
JOIN product_review y
ON y.review_title = x.review_title
AND y.id <= x.id
GROUP
BY x.id
HAVING COUNT(*) <= 3
ORDER
BY MIN(y.id)
LIMIT 3;

MySQL JOIN returning unrelated rows when combined with LEFT JOIN, WHERE and OR

I have the following table structure. The idea is that users have permissions to a forum either by their class or specific user overrides. ('action' in both cases is an enum with values 'read' & 'write')
user (id, class)
forum (id, name)
forum_permissions (forum_id, class_id, action)
forum_user_permissions (forum_id, user_id, action)
With the following query, I'm getting extra results based on rows in forum_permissions that I don't expect. By this I mean that every row on forum_permissions with forum_id = 3 is returned even though the class_id does not match.
SELECT forum.id AS forum_id, forum.name
FROM forum
JOIN forum_permissions ON forum_permissions.forum_id = forum.id
LEFT JOIN forum_user_permissions ON (
forum_user_permissions.forum_id = forum.id AND forum_user_permissions.user_id = 3 )
WHERE (( forum_permissions.class_id = 1 AND forum_permissions.action = 'read' )
OR
( forum_user_permissions.action = 'read' ))
e.g. I get this:
FORUM_ID NAME
1 chat
2 support
3 secret
3 secret
3 secret
3 secret
but expected this:
FORUM_ID NAME
1 chat
2 support
3 secret
I have made an SQL Fiddle with the specific example including data http://sqlfiddle.com/#!2/75c3a/5/0
Your left join is adding those extra lines. mybe if you change the WHERE
WHERE forum_user_permissions.user_id is not null and (
(forum_permissions.class_id = 1 AND forum_permissions.action = 'read')
OR
(forum_user_permissions.action = 'read')
)
Or
SELECT
forum.id AS forum_id, forum.name
FROM
forum
JOIN
forum_permissions
ON
forum_permissions.forum_id = forum.id
LEFT JOIN
forum_user_permissions
ON (
forum_user_permissions.forum_id = forum.id
)
WHERE forum_user_permissions.user_id = 3 and (
(forum_permissions.class_id = 1 AND forum_permissions.action = 'read')
OR
(forum_user_permissions.action = 'read')
)
But it depends on the results your are trying to get
Okay, I solved it myself by using LEFT JOIN's for both permission tables, rather than put the logic in a WHERE clause. I'm not very sure if this is a better approach than my first attempt and will gladly upvote if someone can explain.
SELECT forum.id AS forum_id, forum.name
FROM forum
LEFT JOIN forum_permissions
ON ( forum_permissions.forum_id = forum.id
AND forum_permissions.class_id = 1
AND forum_permissions.action = 'read' )
LEFT JOIN forum_user_permissions
ON ( forum_user_permissions.forum_id = forum.id
AND forum_user_permissions.user_id = 3
AND forum_user_permissions.action = 'read' )
WHERE forum_permissions.forum_id IS NOT null OR forum_user_permissions.forum_id IS NOT null
The full dataset is included below as I guess the fiddle will expire at some point.
INSERT INTO user
(`id`, `class`)
VALUES
(1, 1), (2, 1), (3, 1), (4, 2);
INSERT INTO forum
(`id`, `name`)
VALUES
(1, 'chat'), (2, 'support'), (3, 'secret');
INSERT INTO forum_permissions
(`forum_id`, `class_id`, `action`)
VALUES
(1, 1, 'read'), (1, 1, 'write'),
(1, 2, 'read'), (1, 2, 'write'),
(2, 1, 'read'), (2, 1, 'write'),
(2, 2, 'read'), (2, 2, 'write'),
(3, 2, 'read'), (3, 2, 'write'),
(3, 3, 'read'), (3, 3, 'write');
INSERT INTO forum_user_permissions
(`forum_id`, `user_id`, `action`)
VALUES
(3, 3, 'read'), (3, 3, 'write');