Counting rows when groupBy by several fields - mysql

I have follow query
Select parent.*,
( Select ?????????
from event e
where e.company_id = parent.company_id
AND e.event_type_id in (10, 11, 12)
AND
e.email in
(Select DISTINCT u.email
from users u where u.parent_id = parent.id )
and e.subject_id in
(Select DISTINCT s.subject_id from subjects s where s.parent_id = parent.id )
GROUP by e.email, e.subject_id ) as done
from parent_table parent
I need put something instead ???????? to count subquery rows
I tried to wrap it with another subquery
Select count(*) from (.........)
but in this case my inner query not see parent table
Unknown column 'parent.company_id' in 'where clause'
subquery itself returns table like
----------------------------------------
| email | subject | count |
----------------------------------------
| email1 | 1 | 1 |
| email2 | 5 | 3 |
| email3 | 20 | 22 |
so its events count by email-subject pairs
on top level I need just number of such pairs
UPDT:
seems
COUNT(DISTINCT e.email, e.subject_id)
instead
GROUP by e.email, e.subject_id )
works fine for me

( Select COUNT(DISTINCT e.email, e.subject_id)
from event e
where e.company_id = parent.company_id
AND e.event_type_id in (10, 11, 12)
AND
e.email in
(Select DISTINCT u.email
from users u where u.parent_id = parent.id )
and e.subject_id in
(Select DISTINCT s.subject_id from subjects s where s.parent_id = parent.id ) ) as done
from parent_table parent

Related

Optimize and reduce size of Union Select Query

My question is about how to optimize and reduce size of a sql query. I want to join more than 20 multiple queries using UNION, it is giving me the correct result as per the below logic, but I am looking for two things here
something more efficient
I already have 20 UNIONS in my query, and every month I have to add 2-4 UNIONS more which make this query very long so is there any way this query can be rephrased with less code
Select
'343' As 'Manual ID',
'24/07/2020' As 'Date',
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
where A.ID IN (1)
UNION
Select
'323' As 'Manual ID',
'24/08/2020' As 'Date',
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
where A.ID IN(2,3,4)
and so on ...
Result
Manual ID | Date | Shipper | Order Name | Customer Name | Qty
343 | 24/07/2020 | 1 | order1 | A | 5
323 | 24/08/2020 | 2 | order2 | B | 2
323 | 24/08/2020 | 3 | order3 | C | 1
323 | 24/08/2020 | 4 | order4 | D | 12
You can try this:
Select
CASE
WHEN A.ID IN(1) THEN '343'
WHEN A.ID IN(2,3,4) THEN '323'
END As 'Manual ID',
CASE
WHEN A.ID IN(1) THEN '24/07/2020'
WHEN A.ID IN(2,3,4) THEN '24/08/2020'
END As 'Date',
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
Where A.ID IN(1,2,3,4)
First suggestion is to move the parameters in to another table, then join on it. You can even make that an inline view if you don't want to use a real table...
Second suggestion is to use UNION ALL to avoid the costs of deduplication incurred by UNION.
SELECT
params.*,
O.Order_Name,
C.Customer_Name,
Q.Quantity
FROM
(
SELECT '343' As 'Manual ID', '24/07/2020' As 'Date', 1 AS A_ID
UNION ALL SELECT '323' As 'Manual ID', '24/08/2020' As 'Date', 2 AS A_ID
UNION ALL SELECT '323' As 'Manual ID', '24/08/2020' As 'Date', 3 AS A_ID
UNION ALL SELECT '323' As 'Manual ID', '24/08/2020' As 'Date', 4 AS A_ID
)
AS params
INNER JOIN Shipper A ON A.ID = params.A_ID
Left Join Order O ON A.ID = O.ID
Left Join Customer C ON C A.ID = C.ID
Left Join Quantity Q ON Q.ID = C.ID
Alternatively, don't recompute this every month. Write a new query each month, and insert the results into another table?
If you just want to go for query the better way would be to use the case when statement but every now and then you need to keep updating the query adding new cases.
Another, optimized solution will be to create a new table to store
Manual ID, Date, (Common) ID present in Shipper (Table). Then create a view to join all above tables with new Table.
New Table
Manual ID | Date | ID |
343 | 24/07/2020 | 1 |
323 | 24/08/2020 | 2 |
323 | 24/08/2020 | 3 |
323 | 24/08/2020 | 4 |
Then Create a View Joining all Tables including new new table with ID.
In this you just need add new value to new table and you will complete result in view it self.
CREATE VIEW MY_VIEW
AS
SELECT * FROM
(
Select
T.[Manual ID],
T.[Date],
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
Left Join NewTable T T.ID = A.ID
)
Now just insert value in new table and fetch complete data from MY_VIEW. It will give the same result as you are excepting.

mysql count() with two tables and don't repeat more than 2 row

I have two tables.
Table: employees
Table: employee_roles
I'd like to show a list of all employees where they don't have more than 2 or equal employee_id.
I mean something like this I need to show:
How can I do the query? I used this query but it doesn't work. Also with the conditional WHERE name like '%'.
SELECT COUNT(id_employee), name
FROM employees
WHERE name LIKE '%' AND id_employee NOT IN
(SELECT employee_id
FROM employee_roles);
If you want employees with one role, then you can use aggregation:
select id_employee
from employee_roles
group by id_employee
having count(*) = 1;
If you want employees with 0 or 1 role, then you need to bring in the employees with no roles, using a left join:
select e.id_employee, e.name
from employees e left join
employee_roles er
on e.id_employee = er.id_employee
group by e.id_employee, e.name
having count(er.id_employee) <= 1;
If you want to count them, use a subquery:
select count(*)
from (select e.id_employee, e.name
from employees e left join
employee_roles er
on e.id_employee = er.id_employee
group by e.id_employee, e.name
having count(er.id_employee) <= 1
) e
Try the following
first option using left join
select
name
from
(
select
id_employee,
name
from employees e
left join employee_roles er
on e.id_employee = er.employee_id
group by
id_employee,
name
having count(employee_id) <= 1
) val
Second option usin union all
select
name
from employees e
where not exists (
select
employee_id
from employee_roles er
where e.id_employee = er.employee_id
)
union all
select
name
from employee_roles er
join employees e
on e.id_employee = er.employee_id
group by
name
having count(employee_id) = 1
output:
| name |
| ----- |
| Poul |
| Erick |
| Joy |
| Smith |
You need to join the tables, then use COUNT(*) and GROUP BY. Then you can check if the count is 1 to filter out employees with multiple roles.
SELECT name
FROM employees AS e
JOIN employee_roles AS r ON e.id_employee = r.id_employee
GROUP BY e.id_employee
HAVING COUNT(*) = 1

Fetch only matching records from two entities

Please help me with the MYSQL query.
Entity Name: OrderItem
Attributes: orderId(PK), orderItemSeqId(PK), productId
Entity Name: ProductFacility
Attributes: facilityId(PK), productId(PK), inventoryCount (Integer)
**OrderId | orderItemSeqId | productId**
OID1 | 0001 | 10000
OID1 | 0002 | 10001
OID1 | 0003 | 10002
**FacilityId | ProductId | InventoryCount**
FC_1 | 10000 | 12
FC_1 | 10001 | 5
FC_1 | 10002 | 7
FC_2 | 10001 | 1
FC_2 | 10002 | 6
FC_3 | 10002 | 7
Here I want to fetch the facility (FC_1) which has all the products available for order.
I don't want the facility records which has partial products (like facility FC_3 has only one (10002) product from order OID1)
I only want the facilityId records which have all the products from the order (ex OID1)
IMPORTANT POINT: This SQL will be executed on millions of records.
Try this:
select *
from ProductFacility f1
where (select count(distinct InventoryCount) cnt_fac
from ProductFacility f2
where f2.FacilityId = f1.FacilityId
and InventoryCount > 0
group by FacilityId ) = (select count(distinct productId)
from OrderItem );
Here is a DEMO
This will get you the facilities you are looking for
with cte as
(
select OrderId, count(*) as c
from orderT a
group by OrderId
)
select FacilityId, b.OrderId, count(*) as c from Facility a
inner join orderT b on a.ProductId = b.ProductId
group by FacilityId, b.OrderId
having count(*) = (select c from cte c where c.OrderId = b.OrderId)
To get a list of which facilities can satisfy an order, you need to check which facility has InventoryCount > 0 for each product in the order, which you can do with this query:
SELECT O.OrderId, F.FacilityID
FROM (SELECT DISTINCT productId
FROM OrderItem) P
CROSS JOIN (SELECT DISTINCT OrderId
FROM OrderItem) O
CROSS JOIN (SELECT DISTINCT FacilityId
FROM ProductFacility) F
LEFT JOIN OrderItem OI ON OI.OrderId = O.OrderID AND OI.productId = P.productId
LEFT JOIN ProductFacility PF ON PF.FacilityId = F.FacilityId AND PF.productId = OI.productId AND PF.InventoryCount > 0
GROUP BY O.OrderId, F.FacilityID
HAVING COUNT(OI.ProductId) = COUNT(PF.ProductId)
I have added an additional order to my demo which requires products 10001 and 10002; for that the output is
OrderId FacilityID
OID1 FC_1
OID2 FC_1
OID2 FC_2
Demo on SQLFiddle
This should work:
select oc.orderId, ofc.facilityId
from
(
select orderId, count(*) productCount
from OrderItem
group by orderId) oc
inner join
(
select pf.facilityId, oi.orderId, count(*) productsAvailableCount
from ProductFacility pf
inner join OrderItem oi on oi.productId = pf.productId and pf.inventoryCount > 0
group by pf.facilityId, oi.orderId
) ofc on ofc.orderId = oc.orderId and oc.productCount = ofc.productsAvailableCount
Demo on DB Fiddle

Mysql GROUPing with ORDER based on auto increment column

I have to group my anime index according to their AniDB ID and show the values in a DESCENDING order according to file auto increment id.
Here's what I did currently:
SELECT
f.id, f.category, f.anidb, f.mal_id, COUNT( * ) AS dupes, f.filename,
a.titles, a.synopsis, a.episodes, a.image, a.rating,
c.name as cat_name, c.id as categoryid
FROM table_files f
LEFT JOIN table_anidb a ON a.id = f.anidb
LEFT JOIN table_categories c ON c.id = f.category
GROUP BY a.id ORDER BY f.id DESC
PROBLEM:
I have Naruto 8 episodes. episode 8's ID is 204. And ep.1 has ID 160. The query return like this:
id | anidb | filename | dupes | cat_name
--------------------------------------------------------
201 | 8692 | SAO | 1 | Series
200 | 9251 | RYO | 1 | Movie
.....
.......
160 | 239 | Naruto ep.1 | 8 | Series
But I want Naruto Episode 8 to be showed in the top of the results instead of episode 1 in the last.
How do I group by anidb and mal_id at the same time with an OR logic? So that the grouping can be done even if there is not any anidb ID provided.
Ad. 1.
Since id, anidb and filename are all in one table i'm afraid you can't get away from doing a subquery join:
SQLFiddle
SELECT f.id, f.anidb, f.filename
FROM files f
JOIN
(SELECT MAX(id) as id FROM files GROUP BY anidb) AS f2
ON f2.id = f.id
ORDER BY f.id DESC
(data flattened for the sake of readibility but you can get the general idea)
Ad. 2.
As for the second problem, you really just have to add second grouping column to the above joined subquery:
SQLFiddle
SELECT f.id, f.anidb, f.mal_id, f.filename
FROM files f
JOIN
(SELECT MAX(id) as id FROM files GROUP BY anidb, mal_id) AS f2 on f2.id = f.id
ORDER BY f.id DESC
The NULL's are distinct from each other (e.g. NULL != NULL) so there's no fear that grouping would melt all the nulled anidb rows into one.
For the first problem you can use ORDER BY dupes
SELECT
f.id, f.category, f.anidb, f.mal_id, COUNT( * ) AS dupes, f.filename,
a.titles, a.synopsis, a.episodes, a.image, a.rating,
c.name as cat_name, c.id as categoryid
FROM table_files f
LEFT JOIN table_anidb a ON a.id = f.anidb
LEFT JOIN table_categories c ON c.id = f.category
GROUP BY a.id ORDER BY dupes DESC
For the second problem you can use CASE to check if f.anidb is null
SELECT
f.id, f.category, f.anidb, f.mal_id, COUNT( * ) AS dupes, f.filename,
a.titles, a.synopsis, a.episodes, a.image, a.rating,
c.name as cat_name, c.id as categoryid
FROM table_files f
LEFT JOIN table_anidb a ON a.id = f.anidb
LEFT JOIN table_categories c ON c.id = f.category
GROUP BY
(CASE WHEN f.anidb IS NULL THEN f.mal_id ELSE f.anidb END )
ORDER BY dupes DESC

SELECT distinct values for multiple rows of same ID

I have a table that looks like this:
ID | FIELD_NAME | VALUE
23 | sign_up | yes
23 | first_name | Fred
23 | street | Barber Lane
24 | sign_up | no
24 | first_name | Steve
24 | street | Camaro St.
25 | sign_up | yes
25 | first_name | Larry
25 | street | Huckleberry Ave
I want to run a query that will select unique ID's and the values as named columns so it would appear like so:
ID | SIGN_UP | FIRST_NAME | STREET |
23 | yes | Fred | Barber Lane |
24 | no | Steve | Camaro St. |
25 | yes | Larry | Huckleberry Ave. |
Any help would be much appreciated!!
You can use this simple solution:
SELECT DISTINCT
a.id,
b.value AS SIGN_UP,
c.value AS FIRST_NAME,
d.value AS STREET
FROM tbl a
LEFT JOIN tbl b ON a.id = b.id AND b.field_name = 'sign_up'
LEFT JOIN tbl c ON a.id = c.id AND c.field_name = 'first_name'
LEFT JOIN tbl d ON a.id = d.id AND d.field_name = 'street'
Just to be safe, I made the joins LEFT JOIN's because I do not know if an id can have missing fields, in which case they will show up as NULL in our derived columns.
SQL-Fiddle Demo
You could also try pivoting with the help of grouping and conditional aggregating:
SELECT
ID,
MAX(CASE FIELD_NAME WHEN 'sign_up' THEN VALUE END) AS SIGN_UP,
MAX(CASE FIELD_NAME WHEN 'first_name' THEN VALUE END) AS FIRST_NAME,
MAX(CASE FIELD_NAME WHEN 'street' THEN VALUE END) AS STREET
FROM atable
GROUP BY
ID
;
Adapted from another answer by me:
SELECT ids.ID AS ID,
sign_up.VALUE AS SIGN_UP,
first_name.VALUE AS FIRST_NAME,
street.VALUE AS STREET
FROM (SELECT DISTINCT ID FROM tableName) AS ids
LEFT JOIN tableName AS sign_up
ON (sign_up.ID = ids.ID AND
sign_up.FIELD_NAME = 'sign_up')
LEFT JOIN tableName AS first_name
ON (first_name.ID = ids.ID AND
first_name.FIELD_NAME = 'first_name')
LEFT JOIN tableName AS street
ON (street.ID = ids.ID AND
street.FIELD_NAME = 'street')
The left joins will ensure that missing values will result in NULL cells, instead of an omission of the whole row. Not sure whether that is important in your application. If it is not, you can use an inner join and in particular get rid of the subquery to select all unique IDs. See my original answer from which I derived this.
Am not sure there is a Pivot/Unpivot feature in MySQL.
Try this:
SELECT a.ID,
c.FIELD_NAME AS SIGN_UP,
a.FIELD_NAME AS FIRST_NAME,
b.FIELD_NAME AS STREET
FROM <YOUR-TABLE> a LEFT JOIN <YOUR-TABLE> b
ON a.ID = b.ID
AND a.FIELD_NAME = 'first_name'
AND b.FIELD_NAME = 'street' LEFT JOIN <YOUR-TABLE> c
ON c.ID = a.ID
AND c.FIELD_NAME = 'sign_up'
One approach is to use a correlated subquery to return each field value as a column,
SELECT t.id
, (SELECT f1.value FROM mytable f1
WHERE f1.id = t.id AND f1.field_name = 'sign_up'
ORDER BY f1.value LIMIT 1
) AS SIGN_UP
, (SELECT f2.value FROM mytable f2
WHERE f2.id = t.id AND f2.field_name = 'first_name'
ORDER BY f2.value LIMIT 1
) AS FIRST_NAME
, (SELECT f3.value FROM mytable f3
WHERE f3.id = t.id AND f3.field_name = 'street'
ORDER BY f3.value LIMIT 1
) AS STREET
FROM (SELECT s.id
FROM mytable s
GROUP BY s.id
ORDER BY s.id
) t
This isn't the only way, but it's a workable approach, especially if you are concerned that you will get exactly four columns returned, and that they will be returned in a specific sequence.
Note that this approach works when a particular field_name is "missing" for a particular ID (it will return a NULL in place of a value). It also works if there are multiple occurrences of the same field_name for a particular ID. (This query will return only one of them, and disregard the other.)
This same result set can also be obtained with a query written like this:
SELECT t.id AS ID
, f1.sign_up AS SIGN_UP
, f2.first_name AS FIRST_NAME
, f3.street AS STREET
FROM (SELECT s.id
FROM mytable s
GROUP BY s.id
ORDER BY s.id
) t
LEFT
JOIN (SELECT s1.id
, MIN(s1.value) AS sign_up
FROM mytable s1
WHERE s1.field_name = 'sign_up'
AND s1.value IS NOT NULL
GROUP BY s1.id
) f1
ON f1.id = t.id
LEFT
JOIN (SELECT s2.id
, MIN(s2.value) AS first_name
FROM mytable s2
WHERE s2.field_name = 'first_name'
AND s2.value IS NOT NULL
GROUP BY s2.id
) f2
ON f2.id = t.id
LEFT
JOIN (SELECT s3.id
, MIN(s3.value) AS street
FROM mytable s3
WHERE s3.field_name = 'street'
AND s3.value IS NOT NULL
GROUP BY s3.id
) f3
ON f3.id = t.id
With other queries, ensure you are getting the desired behavior when a field_name is "missing" for a given ID, or when there are duplicate field_name for a given ID, or when there are additional field_name values in the table which you are not concerned with.