Mysql, Dynamic query for rows to column - mysql

I've three tables with name employee, employee_products, product_type
reference : http://sqlfiddle.com/#!9/00436/4
I'm trying to get data as
Let this table is Table_1
using this query:
select emp.name,
(select count(*) from employee_products where product_type_id = 1 and employee_id = emp.id) as Service,
(select count(*) from employee_products where product_type_id = 2 and employee_id = emp.id) as Product,
(select count(*) from employee_products where product_type_id = 3 and employee_id = emp.id) as Other
from employee as emp;
but, i think it is not efficient and from every new product_type_id i've to alter this query, can I do this dynamically.
+------------+---------+---------+-------+--+
| Name | Service | Product | Other | |
+------------+---------+---------+-------+--+
| Bezos | 1 | 0 | 0 | |
+------------+---------+---------+-------+--+
| Steve | 0 | 3 | 0 | |
+------------+---------+---------+-------+--+
| Bill gates | 1 | 0 | 0 | |
+------------+---------+---------+-------+--+
| Tim Cook | 0 | 0 | 1 | |
+------------+---------+---------+-------+--+
and
Let this table is Table_2
In this I can't figure out how this is even possible in mysql as there is no pivot feature in mysql.
+------------+---------+---------+---------+---------+-----------+-------+
| Name | Amazon | iPhone | iPad | iPod | Microsoft | IDK |
+------------+---------+---------+---------+---------+-----------+-------+
| Bezos | Service | NULL | NULL | NULL | NULL | NULL |
+------------+---------+---------+---------+---------+-----------+-------+
| Steve | NULL | Product | Product | Product | NULL | NULL |
+------------+---------+---------+---------+---------+-----------+-------+
| Bill gates | NULL | NULL | NULL | NULL | PRODUCT | NULL |
+------------+---------+---------+---------+---------+-----------+-------+
| Tim Cook | NULL | NULL | NULL | NULL | NULL | OTHER |
+------------+---------+---------+---------+---------+-----------+-------+
Please help.
Note : There can be more than 100 items in product_type, employee_products table.

Try this for Table_1
Select name, Max(Service) as Service, Max(Product) as Product, Max(Other) as Other
From (
select e.name,
count(case when ep.product_type_id = 1 then 1 else null end) as Service,
count(case when ep.product_type_id = 2 then 1 else null end) as Product,
count(case when ep.product_type_id = 3 then 1 else null end) as Other,
from employee e
inner join employee_products ep on (e.id = ep.employee_id)
)
Group by name;
Note: same way you can try for Table_2

There are several bits of code out there for dynamically building and running the SELECT with the columns dynamically deduced. Here is mine.

Related

Join data table with latest value, if not found show null mysql?

I have 3 tables, which every table has it's own foreign key and primary key.
Table scheme looks like this :
tbl_cake
/---------------------------------------------------------------------------\
| CakeId (Primary key) | CakeName | UserId (Foreign Key from tbl_user) |
|----------------------------------------------------------------------------|
| 1 | BlackForest | 12345 |
|----------------------------------------------------------------------------|
| 2 | Fruit Pie | 98475 |
|----------------------------------------------------------------------------|
| 3 | Birthday Cake | 12345 |
|----------------------------------------------------------------------------|
| 4 | Raspberry Pie | 28475 |
\----------------------------------------------------------------------------/
tbl_user
/--------------------------------------\
| UserId (Primary key) | UserName |
|--------------------------------------|
| 12345 | Angelia |
|--------------------------------------|
| 98475 | Rudi |
|--------------------------------------|
| 56782 | Andika |
\--------------------------------------/
tbl_transaction
/--------------------------------------------------------------------------------------------------\
| TransactionId(Primary) | CakeId(Foreign) | UserId| Qty | Date | OrderType |
|--------------------------------------------------------------------------------------------------|
| 1 | 1 | 12345 | 1000 | 2020-04-01 10:05:01 | Drive Thru |
|--------------------------------------------------------------------------------------------------|
| 2 | 2 | 98475 | 200 | 2020-04-03 09:15:01 | On The Spot |
|--------------------------------------------------------------------------------------------------|
| 3 | 2 | 98475 | 500 | 2020-04-03 11:05:01 | On The Spot |
|--------------------------------------------------------------------------------------------------|
| 4 | 1 | 12345 | 150 | 2020-04-05 08:05:01 | On The Spot |
\--------------------------------------------------------------------------------------------------/
So the goals are :
Show UserId,TransactionId,Qty,Date and OrderType which :
Show all UserId, either it has any order or not.
If multiple order occurs, then show the latest data
If UserId don't have any order, fill the missing values with null
Finally, show the data with OrderType 'On The Spot'.
The expected result will be :
FINAL RESULT
/----------------------------------------------------------------------------------\
| UserId | TransactionId | Qty | Date | OrderType |
|----------------------------------------------------------------------------------|
| 12345 | 4 | 150 | 2020-04-05 08:05:01 | On The Spot|
|----------------------------------------------------------------------------------|
| 98475 | 3 | 500 | 2020-04-03 11:05:01 | On The Spot|
|----------------------------------------------------------------------------------|
| 56782 | null | null | null | null |
\----------------------------------------------------------------------------------/
I think using join(s) will accomplish this, but i'm not sure how, especially when determine if UserId has any order or not, and pick the latest order to be shown.
Many thanks!
In MySQL 8+, we would use ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) rn
FROM tbl_transaction
WHERE OrderType = 'On The Spot'
)
SELECT
u.UserId,
t.TransactionId,
t.Qty,
t.Date,
t.OrderType
FROM tbl_user u
LEFT JOIN cte t
ON u.UserId = t.UserId AND rn = 1;
Demo
I managed to solve it (using left join and max function) :
select tbl_user.UserId,tbl_transaction.TransactionId,tbl_transaction.Qty,tbl_transaction.Date,
tbl_transaction.OrderType
from tbl_cake
left join tbl_user on tbl_user.UserId=tbl_cake.UserId
left join tbl_transaction on tbl_user.UserId=tbl_transaction.UserId
and
tbl_transaction.Date=(select max(Date) from tbl_transaction where UserId=tbl_User.UserId
and tbl_transaction.OrderType="On The Spot")
group by UserId

Combining two mysql tables into one row

I'm trying to combine a few tables into a row.
Team Table:
+----+-------+
| id | team |
+----+-------+
| 10 | Team1 |
| 11 | Team2 |
| 12 | Team3 |
+----+-------+
Location Table:
+----+-----------+
| id | location |
+----+-----------+
| 1 | location1 |
| 2 | location2 |
| 3 | location3 |
+----+-----------+
Stops Table:
+----+---------+-------------+---------------------+
| id | team_id | location_id | timestamp |
+----+---------+-------------+---------------------+
| 1 | 10 | 2 | 2019-11-07 15:27:42 |
| 2 | 10 | 3 | 2019-11-07 16:37:52 |
| 3 | 10 | 4 | 2019-11-07 17:47:62 |
+----+---------+-------------+---------------------+
Looking to create the desired table:
+----+---------+---------------------+---------------------+---------------------+
| id | team_id | (loc id=2) | (loc id=3) | (loc id=4) |
+----+---------+---------------------+---------------------+---------------------+
| 1 | 10 | 2019-11-07 15:27:42 | 2019-11-07 16:37:52 | 2019-11-07 17:47:62 |
| 2 | 11 | | | |
| 3 | 12 | | | |
+----+---------+---------------------+---------------------+---------------------+
There will always be a finite number of locations.
Any guidance would be greatly appreciated! I've tried a handful of LEFT JOINS, but am not getting far.
You can do conditional aggregation:
select
t.id team_id
max(case when s.location_id = 2 then timestamp end) loc_id_2,
max(case when s.location_id = 3 then timestamp end) loc_id_3,
max(case when s.location_id = 4 then timestamp end) loc_id_4
from
team t
left join stops s on s.team_id = t.id
group by t.id
If you want to generate an id column on the fly for the generated results (which makes little sense since you get one record per team_id already), then you can use row_number() (availble in MySQL 8.0 onwards):
select
row_number() over(order by t.id) id,
t.*
from (
select
t.id team_id,
max(case when s.location_id = 2 then timestamp end) loc_id_2,
max(case when s.location_id = 3 then timestamp end) loc_id_3,
max(case when s.location_id = 4 then timestamp end) loc_id_4
from
team t
left join stops s on s.team_id = t.id
group by t.id
) t

WHERE Clause for 1 field

Im a beginner in sql query and I would like to ask 1 question.
I have 2 table which is dbo.Group_Master and dbo.Group_Assign.
TABLE : GROUP MASTER
| GROUP_ID | GROUP_DESC |
|------------ |:---------------: |
| PMD | POWER MOTOR |
| ASSY | ASSEMBLY |
| FINANCE | FINANCE DEPT |
| COSTING | COSTING DEPT |
| IE | IMPORT EXPORT |
| BBC GROUP | BBC GROUP |
| PRODUCTION | PRODUCTION DEPT |
| PURCHASING | PURCHASING |
| SALE | SALE DEPT |
| MMASTER | MATERIAL MASTER |
TABLE : GROUP_ASSIGN
| JOB_ID | STEP_ID | LOG_NAME | GROUP_ID |
|-------- |:-------: |---------- |---------- |
| J1 | 1 | ACCOUNT | PMD |
| J1 | 2 | ACCOUNT | ASSY |
| J1 | 3 | ACCOUNT | SALE |
| J1 | 1 | FREIGHT | IE |
When I joined both table and put where clause, and run query like below
SELECT
a.GROUP_ID,
a.GROUP_DESC,
CASE WHEN b.GROUP_ID IS NULL THEN '0' ELSE '1' end as status
FROM dbo.GROUP_MASTER A
LEFT JOIN dbo.GROUP_ASSIGN B ON (B.GROUP_ID = a.GROUP_ID)
WHERE B.LOG_NAME LIKE 'ACCOUNT'
It display result like this :
What I want is the result manage to display all the first 2 column and only third column is affected by where clause like below image. Is there any way i can do it ?
Expected Output
Try below query ...
SELECT
A.GROUP_ID,
A.GROUP_DESC,
CASE WHEN B.GROUP_ID IS NULL THEN '0' ELSE '1' end as status
FROM dbo.GROUP_MASTER A
LEFT JOIN (SELECT * FROM dbo.GROUP_ASSIGN WHERE LOG_NAME LIKE 'ACCOUNT') B ON (A.GROUP_ID = B.GROUP_ID)
You could try using a subquery (to fetch what you want from group_assign first) , like this :
SELECT
a.GROUP_ID,
a.GROUP_DESC,
CASE WHEN b.GROUP_ID IS NULL THEN '0' ELSE '1' end as status
FROM dbo.GROUP_MASTER A
LEFT JOIN (select GROUP_ID, LOG_NAME from dbo.GROUP_ASSIGN where LOG_NAME LIKE 'ACCOUNT') B ON (B.GROUP_ID = a.GROUP_ID)

WHERE AND clause for the same column but different rows

Prior Information Notice
I have 3 tables:
types
+----+-------------+-----------------------+------------+------------+
| id | category_id | name | created_at | updated_at |
+----+-------------+-----------------------+------------+------------+
| 1 | 1 | T-Shirts | NULL | NULL |
+----+-------------+-----------------------+------------+------------+
prototypes
+----+-----------------------------------------+------------+------------+
| id | name | created_at | updated_at |
+----+-----------------------------------------+------------+------------+
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL |
+----+-----------------------------------------+------------+------------+
filters
+----+-------------+---------------------+-------+------------+------------+
| id | name | value | extra | created_at | updated_at |
+----+-------------+---------------------+-------+------------+------------+
| 1 | gender | male | NULL | NULL | NULL |
| 2 | gender | female | NULL | NULL | NULL |
| 3 | age_group | adult | NULL | NULL | NULL |
| 4 | age_group | child | NULL | NULL | NULL |
| 5 | age_group | baby | NULL | NULL | NULL |
+----+-------------+---------------------+-------+------------+------------+
They are related one another through n-m relationship, so there are respective junction tables types_prototypes, types_filters, prototypes_filters as well. For more details please check also out my dump file.
Problem itself
I'm trying to set up filtering system (with Laravel), so I need to query all Prototypes that are related to all given Filters (logical AND). Until now I have managed to get the, as long as use chose only one Filter:
select * from `prototypes`
inner join `types_prototypes` on `prototypes`.`id` = `types_prototypes`.`prototype_id`
inner join `prototypes_filters` on `prototypes`.`id` = `prototypes_filters`.`prototype_id`
inner join `filters` on `prototypes_filters`.`filter_id` = `filters`.`id`
where `types_prototypes`.`type_id` = ? and `filter_id` = ? group by `prototypes`.`id`
The problem itself consists in the fact that this query is inapplicable, as soon as we have several filters that should be valid simultaneously:
...
where `types_prototypes`.`type_id` = ? and `filter_id` = ? and `filter_id` = ? group by `prototypes`.`id`
I know, where ... and doesn't work, because I have due to join only one column filter_id that can contain only one single value at the same time (what actually groupBy() takes care of). So in this sense I have a new one row for the relation of the same Prototype with another Filter, e.g.:
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| id | name | created_at | updated_at | type_id | prototype_id | prototype_id | filter_id | id | name | value | extra | created_at | updated_at |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL | 1 | 1 | 1 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL | 1 | 1 | 1 | 3 | 3 | age_group | adult | NULL | NULL | NULL |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
I have already tried several different methods, including where 'filter_id' in(?,?), where FIND_IN_SET('filter_id', '?,?') and even restructured my database in accord with EAV-pattern (when the filters is divided into filter_names and filter_values). But every time I obtain only entries that fulfill one requirement of the whole set (equals logical OR), for instance (here we have prototypes for adults and men, but not only for adult men):
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| id | name | created_at | updated_at | type_id | prototype_id | prototype_id | filter_id | id | name | value | extra | created_at | updated_at |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
| 1 | Gildan Softstyle Adult Ringspun T-shirt | NULL | NULL | 1 | 1 | 1 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 2 | American Apparel Womans T-Shirt | NULL | NULL | 1 | 2 | 2 | 3 | 3 | age_group | adult | NULL | NULL | NULL |
| 3 | Gildan Adult Cotton T-shirt | NULL | NULL | 1 | 3 | 3 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 4 | American Apparel Mens T-Shirt | NULL | NULL | 1 | 4 | 4 | 1 | 1 | gender | male | NULL | NULL | NULL |
| 5 | American Apparel Kids T-Shirt | NULL | NULL | 1 | 5 | 5 | 1 | 1 | gender | male | NULL | NULL | NULL |
+----+-----------------------------------------+------------+------------+---------+--------------+--------------+-----------+----+-----------+-------+-------+------------+------------+
I'm almost desperate, does anybody have a clue?
Thanks you in advice for your help and sorry for so much text, I just wanted to describe all circumstances.
You have to join with the filters table repeatedly for each criterion.
select * from prototypes AS p
inner join types_prototypes AS tp1 on p.id = tp1.prototype_id
inner join prototypes_filters AS pf1 on p.id = pf1.prototype_id
inner join filters AS f1 on pf1.filter_id = f1.id
inner join types_prototypes AS tp2 on p.id = tp2.prototype_id
inner join prototypes_filters AS pf2 on p.id = pf2.prototype_id
inner join filters AS f2 on pf2.filter_id = f2.id
where tp1.type_id = ? and f1.filter_id = ?
AND tp2.type_id = ? and f2.filter_id = ?
group by prototypes.id

COUNT counting something on LEFT JOIN that doesn't exist. Why?

I'm having a problem.
I have this table called usersbycourse which shows this information:
+------------+-----------------+--------+-----------+-------+-----------------+-----------------+
| instanceid | shortname | userid | firstname | logid | lastaccessdelta | modulesfinished |
+------------+-----------------+--------+-----------+-------+-----------------+-----------------+
| 2 | PJU | 74 | Robin | 766 | 1662246 | 0 |
| 3 | Fundgest-GRHN1A | 75 | Batman | 867 | 1576725 | 0 |
| 3 | Fundgest-GRHN1A | 77 | Abigobeu | 1004 | 610480 | 0 |
+------------+-----------------+--------+-----------+-------+-----------------+-----------------+
and this SQL:
SELECT
mdl_course.id,
mdl_course.shortname,
COUNT(CASE WHEN usersbycourse.modulesfinished = 1 THEN NULL ELSE 1 END) AS studentcount
FROM mdl_course LEFT JOIN usersbycourse ON mdl_course.id = usersbycourse.instanceid
GROUP BY mdl_course.id;
The results from the SQL are:
+----+-----------------+--------------+
| id | shortname | studentcount |
+----+-----------------+--------------+
| 1 | Unity I | 1 |
| 2 | PJU | 1 |
| 3 | Fundgest-GRHN1A | 2 |
| 4 | asdzxc2 | 1 |
+----+-----------------+--------------+
But why? In inside SQL has no Unity I, and no asdzxc2. How do I produce a result like this:
+----+-----------------+--------------+
| id | shortname | studentcount |
+----+-----------------+--------------+
| 1 | Unity I | 0 |
| 2 | PJU | 1 |
| 3 | Fundgest-GRHN1A | 2 |
| 4 | asdzxc2 | 0 |
+----+-----------------+--------------+
?
EDIT:
I want to count only rows having modulesfinished = 0
What you're looking for is SUM rather than COUNT, that is,
SELECT
mdl_course.id,
mdl_course.shortname,
SUM(CASE WHEN usersbycourse.modulesfinished = 0 THEN 1 ELSE 0 END) AS studentcount
FROM mdl_course LEFT JOIN usersbycourse ON mdl_course.id = usersbycourse.instanceid
GROUP BY mdl_course.id;
The problem is because you are using LEFT JOIN some of the values for usersbycourse.modulesfinished are NULL
Something you need to learn is
NULL == something
Is always unknown, not true, not false, just unknown.
So when you try to compare with = 1 your nulls get the ELSE but not because they aren't 1, is because is all the rest.
So if instead you change the condition to
COUNT(CASE WHEN usersbycourse.modulesfinished = 0 THEN 1 ELSE NULL)
Only the TRUE match will get 1, the FALSE and the UNKNOW part ill get NULL and COUNT doesnt count nulls. And that is what you want.