MySQL: Query UNION, JOIN with conditional WHERE - mysql

Say we have the following tables, employee and payroll_slip:
Table: employee
id_employee | name_employee |
============|===============|
1 | john |
2 | doe |
3 | alex |
Table: payroll_slip
id_slip | id_employee | month_slip | year_slip |
========|=============|============|===========|
1 | 1 | 01 | 2016 |
2 | 2 | 01 | 2016 |
3 | 1 | 02 | 2016 |
4 | 2 | 02 | 2016 |
5 | 1 | 03 | 2016 |
6 | 3 | 03 | 2016 |
And we want to get the following result where month_slip = '03' AND year_slip = '2016'
id_employee | month_slip | year_slip | status_slip
============|============|===========|============
1 | 03 | 2016 | paid
2 | 03 | 2016 | unpaid
3 | 03 | 2016 | paid
I tried this query:
SELECT
a.id_employee,
payroll_slip.month_slip,
payroll_slip.year_slip,
IF(a.id_employee=payroll_slip.id_employee, 'paid', 'unpaid') AS status_slip
FROM (
SELECT id_employee FROM employee
UNION
SELECT id_employee FROM payroll_slip
) a
LEFT OUTER JOIN payroll_slip ON a.id_employee = payroll_slip.id_employee
LEFT OUTER JOIN employee ON a.id_employee = employee.id_employee
WHERE payroll_slip.month_slip = '03' AND payroll_slip.year_slip = '2016'
Any suggestion for this?

Try having a cross join:
SELECT X.id_employee, X.month_slip, X.year_slip
, CASE WHEN Y.id_employee IS NULL THEN 'Unpaid' ELSE 'Paid' END AS status_slip
FROM (
SELECT A.id_employee, B.month_slip, B.year_slip
from employee A
cross join (
select '03' AS month_slip, '2016' AS year_slip
) B
) X
LEFT JOIN payroll_slip Y
ON X.id_employee = Y.id_employee
AND X.month_slip = Y.month_slip
AND X.year_slip = Y.year_slip

Try this query:
SELECT e.id_employee, '03' AS month_slip, '2016' AS year_slip,
CASE WHEN p.id_employee IS NOT NULL THEN 'paid' ELSE 'unpaid' END AS status_slip
FROM employee e LEFT JOIN payroll_slip p
ON e.id_employee = p.id_employee AND p.month_slip = '03' AND p.year_slip = '2016'
The trick here is to move the restrictions on the month_slip and year_slip inside the JOIN condition, rather than in the WHERE clause.
Follow the link below for a running demo:
SQLFiddle

Related

MySQL select N latest rows for each product from 3 relational tables

Now i have this code which return latest record for each product. But i don't know how to modify this to get for example 3 latest rows for each product.
I want to compare latest product prices and i need few latest rows of each.
shops
id | shopId
-----------
1 | 2345
2 | 6573
products
id | shopId | title | active | pDateAdded | pDateUpdate
---------------------------------------------------------------------------
18 | 1 | Honda | 1 | 2021-03-07 01:56:34 | 2021-03-07 04:36:34
19 | 2 | Subaru | 1 | 2021-03-07 03:43:34 | 2021-03-08 04:36:34
20 | 1 | VW | 1 | 2021-03-07 07:21:34 | 2021-03-09 04:36:34
21 | 2 | Ford | 0 | 2021-03-07 11:37:34 | 2021-03-10 04:36:34
prices
id | shopId | productId | price | dDateAdded
-----------------------------------------------------
224 | 1 | 18 | 2385 | 2021-03-09 12:39:57
225 | 2 | 19 | 1523 | 2021-03-09 13:14:44
226 | 1 | 20 | 5489 | 2021-03-09 17:32:18
227 | 1 | 18 | 2256 | 2021-03-10 18:22:13
228 | 2 | 19 | 1600 | 2021-03-10 21:33:21
229 | 1 | 20 | 5321 | 2021-03-10 14:15:56
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
231 | 2 | 19 | 1666 | 2021-03-11 17:31:49
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
This command return only 1 latest record from prices table for every product from products table for specific shopId
SELECT s.*, c.*, d.*
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN (
SELECT productId, MAX(dDateAdded) MaxDate
FROM prices
GROUP BY productId
) MaxDates
ON MaxDates.productId = c.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id AND MaxDates.MaxDate = d.dDateAdded
WHERE s.id = ".$shopId."
For example if shopId=1 this command get only that records (I omitted here the data from the other tables that are retrieved):
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
But i want to get for example 2 latest records for every product where shopId=1, so the records which i want to get:
(shops)id | (shops)shopId | title | active | price | dDateAdded
1 | 2345 | Honda | 1 | 2256 | 2021-03-10 18:22:13
1 | 2345 | Honda | 1 | 2137 | 2021-03-10 14:15:56
1 | 2345 | VW | 1 | 5321 | 2021-03-11 05:55:25
1 | 2345 | VW | 1 | 5001 | 2021-03-11 20:18:01
To select N latest rows needs to allocate row number and to filter by N rows. However, the ROW_NUMBER function is not supported in MySQL 5.7.
So that you need to simulate the ROW_NUMBER function like the follwing:
You can get the desired result by adding subquery with row number to your query like the below:
DB Fiddle
SELECT
s.id,
s.shopId,
c.title,
c.active,
d.price,
d.dDateAdded
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id
--
LEFT JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY p1.id, p1.shopId, p1.productId, p1.dDateAdded
) AS w
ON d.id=w.id
--
WHERE
s.id = 1 AND
w.row_num <= 2
DB Fiddle
SELECT
id,
shopId,
productId,
price,
dDateAdded
FROM (
SELECT p1.*,
(
SELECT COUNT(*)+1 FROM prices p2
WHERE
p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
) row_num
FROM prices p1
) p
WHERE
shopId = 1 AND
row_num <= 2
ORDER BY id
DB Fiddle
SELECT p.* FROM prices p
INNER JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY
p1.id,
p1.shopId,
p1.productId,
p1.dDateAdded
) w
ON p.id=w.id
WHERE
p.shopId = 1 AND
w.row_num <= 2
ORDER BY p.id
Other way using a variable

Left Join not filtering results

Using MySQL.
Below is my table structure.
batch_admissions
------------------------+
batchId | studentId |
----------------------- +
1 | 1 |
1 | 2 |
1 | 3 |
2 | 1 |
2 | 2 |
------------------------+
attendance_master
----------------------------+
attendance | studentId |
----------------------------+
P | 1 |
P | 2 |
P | 3 |
----------------------------+
desire result if batchId=2 as below as attendance_master only contain record of batchId=1
----------------------------+
attendance | studentId |
----------------------------+
| 1 |
| 2 |
----------------------------+
But currently I am getting all record back from attendance_master irrespective of change in batchId.
What wrong in my query? I think left join should do the job. but not working
SELECT
a.attendanceId,
a.attendanceDate,
a.attendance,
a.Remarks,
CONCAT(b.studentFirstName, ' ', COALESCE(b.studentMiddleName,'') , ' ', b.studentLastName) as studentName ,
c.classRollNum,
SUBSTRING_INDEX(SUBSTRING_INDEX( a.attendanceDate , '-', 3 ),'-',-1) AS attDay,
CASE WHEN DAYNAME(a.attendanceDate) = 'Monday' THEN 'Mon'
WHEN DAYNAME(a.attendanceDate) = 'Tuesday' THEN 'Tue'
WHEN DAYNAME(a.attendanceDate) = 'Wednesday' THEN 'Wed'
WHEN DAYNAME(a.attendanceDate) = 'Thursday' THEN 'Thu'
WHEN DAYNAME(a.attendanceDate) = 'Friday' THEN 'Fri'
WHEN DAYNAME(a.attendanceDate) = 'Saturday' THEN 'Sat'
WHEN DAYNAME(a.attendanceDate) = 'Sunday' THEN 'Sun'
END as attDayName
,CONCAT(SUBSTRING_INDEX(SUBSTRING_INDEX( a.attendanceDate , '-', 3 ),'-',-1),'.',c.classRollNum) as Idx
FROM attendance_master a
LEFT JOIN student_master b ON a.studentId = b.studentId
LEFT JOIN batch_admissions c ON c.studentId = a.studentId AND c.batchId=1
WHERE a.attendanceDate BETWEEN '2016-03-01' AND '2016-03-31'
ORDER BY c.classRollNum ASC
-------------
Basically I trying to avoid triggering two queries and want result in single query.
batch_admissions table holds series of batch with N numbers student in it.
attendance_master table holds attendance of students for all batch.
On web page I am displaying table grid report, per batch wise.
What I am trying to achieve,
case 1 : when attendance_master NOT contain attendance for batchId for specific period. Still want list of student for that batch
-------------------------------------------------------
BatchId |studentId | Mon | Tue | Wed | Thus |
------------------------------------------------------
1 | 11 | | | | |
1 | 12 | | | | |
.. | .. | | | | |
Case 2: when attendance_master contain attendance for batchId for specific period.
-------------------------------------------------------
BatchId |studentId | Mon | Tue | Wed | Thus |
------------------------------------------------------
2 | 1 | P | P | P | P |
2 | 2 | P | A | P | P |
.. | .. | P | P | P | P |
Alternate I can trigger two queries to achieve this logically. One for get of student for batch, and then getting attendance detail for all those student.
ok... so return all records from batch admissions and the related student_master data (which there will always be records) and the associated attendance master data...
FROM batch_admissions c
INNER JOIN student_master b
ON a.studentId = c.studentId
LEFT JOIN attendance_master a
ON c.studentId = a.studentId
and a.attendanceDate BETWEEN '2016-03-01' AND '2016-03-31'
WHERE c.batchId=1
ORDER BY c.classRollNum ASC

How to identify entities which have repeated values in sequence using MySQL?

I have a table:
UNIT_ID | YEAR | MONTH | VAR
---------+------+-------+------
1 | 2015 | 1 | 0
1 | 2015 | 2 | 0
1 | 2015 | 3 | 0
2 | 2015 | 1 | 10
2 | 2015 | 2 | 10
2 | 2015 | 3 | 10
1 | 2015 | 4 | 5
1 | 2015 | 5 | 5
1 | 2015 | 6 | 5
2 | 2015 | 4 | 10
2 | 2015 | 5 | 3
2 | 2015 | 6 | 3
3 | 2016 | 1 | 3
3 | 2016 | 2 | 3
3 | 2016 | 3 | 3
3 | 2016 | 4 | 3
2 | 2016 | 6 | 0
2 | 2016 | 7 | 0
2 | 2016 | 8 | 0
I want to know which units have a sequence bigger than 3 zeros or bigger than 4 values repeated. Grouped by year. So, my result table would be like this:
1 | 2015 | true
2 | 2015 | true
2 | 2016 | true
I have found this solution but unfortunately I could not adapt to my case. I need also that the query is in MySQL.
You could just join them 4 times. Last join is a left join to allow the case for 3 0's.
select a.unit_id, a.year, 'true'
from tbl a
join tbl b on a.unit_id = b.unit_id and a.year = b.year and a.month+1 = b.month and a.var = b.var
join tbl c on b.unit_id = c.unit_id and b.year = c.year and b.month+1 = c.month and b.var = c.var
left join tbl d on c.unit_id = d.unit_id and c.year = d.year and c.month+1 = d.month and c.var = d.var
where a.var = 0 or d.var is not null;
Faster and more generic solution. It scans the table only once, and uses user defined variables (#pu for previous unit_id, #py for previous year, etc) to remember the previous row:
select distinct unit_id, year
from (
select unit_id, `year`, `month`, `var`,
if(unit_id=#pu and `year`=#py and `month`=#pm+1 and `var`=#pv, #i:=#i+1, #i:=1)*
if(#pu:=unit_id,1,1)*if(#py:=`year`,1,1)*if(#pm:=`month`,1,1)*if(#pv:=`var`,1,1) as c
from table1 a
join (select #pu:=null, #py:=null, #pm:=null, #pv:=null, #i:=1) b
order by unit_id, `year`, `month`, `var`) a
group by unit_id, `year`, `var`
having (`var` = 0 and max(c) >= 3) or (`var` != 0 and max(c) >= 4);
fiddle

Joining tables and making count operation - MySQL

I have those tables:
Members
---------------------------
MemberID | Name |.....
1
2
3
4
---------------------------
RentedMovies
---------------------------
MemberID | MovieID | DateOfLease | ReturnDate | .....
1 | 1 | 2012-12-20 | 2013-01-05
1 | 2 | 2012-12-15 | 2012-12-30
1 | 3 | 2012-12-16 | 2013-01-06
2 | 1 | 2012-12-17 | 2012-12-18
2 | 4 | 2012-12-18 | 2013-01-05
3 | 1 | 2012-12-19 | 2013-01-04
I need to get this:
--------------------------------------------------------
MemberID | NumberOfRentedMovies | ReturnData < curdate())
1 | 3 | 1
2 | 2 | 1
3 | 1 | 0
4 | 0 | 0
---------------------------------------------------------
And i used next code:
SELECT Members.MemberID,
COUNT(rented.MemberID) AS NumberOfRentedMovies,
COUNT(notTakenBackOnTime.idClana) AS NumberOfMoviesLate
FROM Members
left JOIN RentedMovies as rented ON rented.MemberID = Members.MemberID
left JOIN RentedMovies as notTakenBackOnTime ON notTakenBackOnTime.MemberID
= Members.MemberID AND notTakenBackOnTime.ReturnDate< CURDATE()
group by Members.MemberID
But it doesnt work corrextly!
And I also tried with this:
SELECT MemberID,my,my2
FROM Members as mem
JOIN (SELECT COUNT(* )AS my FROM RentedMovies) b
ON b.MemberID = mem.MemberID
JOIN (SELECT COUNT(* )AS my2 FROM RentedMovies WHERE ReturnDate< CURDATE()) c
ON c.MemberID = mem.MemberID
But i got some errors!
So the question is how to accomplish right solution?
You were close. Try this:
SELECT M.MemberID,
COUNT(RM.MemberID) NumberOfRentedMovies,
SUM(CASE WHEN RM.ReturnDate < CURDATE() THEN 1 ELSE 0 END) ReturnData
FROM Members M
LEFT JOIN RentedMovies RM
ON M.MemberID = RM.MemberID
GROUP BY M.MemberID
The desired result you showed can be accomplished by:
SELECT MemberID,
COALESCE(COUNT(MovieID), 0) AS NumberOfRentedMovies,
COALESCE(SUM(ReturnDate < CURDATE()), 0) AS NotYetReturned
FROM Members
LEFT JOIN RentedMovies USING (MemberID)
GROUP BY MemberID
See it in action: http://sqlfiddle.com/#!2/a192c/1

MYSQL Query : How to UNION two table with ORDER BY

can anyone help me solve this problem? i've been trying to query this but i keep getting error.
MySQL code:
SELECT * FROM
(
SELECT BU.*, BD.BOOK_TITLE AS BOOK_TITLE, BD.BOOK_COMPANY AS COMPANY,
BD.RETURN_DATE AS RETURN
FROM BOOK_USER BU
INNER JOIN BOOKING_DETAIL BD ON (BU.USR_ID = BD.USR_ID)
UNION
SELECT BU.*, "NEW REGISTERED" AS BOOK TITLE, 'RENT-A-BOOK' AS COMPANY,
BU.REGISTER_DATE AS RETURN
FROM BOOK_USER BU
) AS BU
GROUP BY BU.USR_ID
The Tables:
BOOK_USER
+---------+----------+---------------+
| USR_ID | USR_NAME | REGISTER_DATE |
+---------+----------+---------------+
| 1 | john | 2011-09-20 |
+---------+----------+--------------+
| 2 | jane | 2011-12-05 |
+---------+----------+--------------+
| 3 | doe | 2012-02-16 |
+---------+----------+--------------+
| 4 | mary | 2012-02-02 |
+---------+----------+--------------+
BOOKING_DETAIL
+---------+----------+------------+-----------+--------------+
| BOOK_ID | USR_ID | BOOK_TITLE | COMPANY | RETURN_DATE |
+----------+--------+-------------+-----------+--------------+
| 1 | 1 | DEAR JOHN |ABC PVT LMT| 2011-11-01 |
+---------+---------+-------------+-----------+--------------|
| 2 | 1 | LUCKY | DEF | 2012-03-18 |
+---------+---------+-------------+-----------+--------------|
| 3 | 1 | THE RISE | GHI | 2012-06-12 |
+---------+---------+-------------+-----------+--------------|
| 4 | 2 | HELLO | TIMES | 2012-01-11 |
+---------+---------+-------------+-----------+--------------|
| 5 | 2 | SHOPAHOLIC | | 2012-08-31 |
+---------+---------+-------------+-----------+--------------|
| 6 | 3 | LOST | | 2012-06-20 |
+---------+---------+-------------+-----------+--------------|
The result should return the latest RETURN_DATE and SORTED by USR_ID.
eg:
John, THE RISE, GHI,2012-06-12
Jane,SHOPAHOLIC,RENT-A-BOOK,2012-08-31
doe, LOST,RENT-A-BOOK,2012-06-20
mary, NEW REGISTERED,RENT-A-BOOK , 2012-02-02
The query constains a subquery which gets the latest RETURN_DATE for each user.
SELECT a.*, c.BOOK_TITLE, c.RETURN_DATE
FROM book_user a
INNER JOIN
(
SELECT usr_ID, MAX(return_DATE) maxDate
FROM booking_detail
GROUP BY usr_ID
) b ON a.usr_ID = b.usr_ID
INNER JOIN booking_detail c
ON b.usr_ID = c.usr_ID AND
b.maxDate = c.return_DATE
SQLFiddle Demo
UPDATE
use LEFT JOIN and COALESCE
SELECT a.USR_ID,
a.USR_NAME,
COALESCE(c.BOOK_TITLE,'RENT-A-BOOK') BOOK_TITLE,
COALESCE(c.RETURN_DATE, a.REGISTER_DATE) RETURN_DATE
FROM book_user a
LEFT JOIN
(
SELECT usr_ID, MAX(return_DATE) maxDate
FROM booking_detail
GROUP BY usr_ID
) b ON a.usr_ID = b.usr_ID
LEFT JOIN booking_detail c
ON b.usr_ID = c.usr_ID AND
b.maxDate = c.return_DATE
SQLFiddle Demo
Try that :
SELECT *
FROM (
(
SELECT BU.*, BD.BOOK_TITLE, BD.BOOK_COMPANY AS COMPANY,BD.RETURN_DATE AS RETURN FROM BOOK_USER BU INNER JOIN BOOKING_DETAIL BD ON (BU.USR_ID = BD.USR_ID)
) UNION ALL (
SELECT BU.*, BD.BOOK_TITLE, 'RENT-A-BOOK' AS COMPANY,BD.RETURN_DATE AS RETURN FROM BOOK_USER BU INNER JOIN BOOKING_DETAIL BD ON (BU.USR_ID = BD.USR_ID)
)
) BU
GROUP BY BU.USR_ID
You can directly get the output using following query check the query:
SELECT BU.*, IFNULL(BD.BOOK_TITLE, 'NEW REGISTERED') AS BOOK_TITLE, IFNULL(BD.BOOK_COMPANY, 'RENT-A-BOOK') AS COMPANY,BD.RETURN_DATE AS RETURN
FROM BOOK_USER BU
LEFT JOIN (SELECT * FROM (SELECT USR_ID, BOOK_TITLE, BOOK_COMPANY, RETURN_DATE FROM BOOKING_DETAIL ORDER BY RETURN_DATE DESC) AS A GROUP BY USR_ID) AS BD ON (BU.USR_ID = BD.USR_ID)
ORDER BY BU.USR_ID;