Combining data from multiple rows into one - mysql

First question on here, so i will try my best to be clear.
I have 2 tables:
"TABLE1" which contains a record for each stock code and a list of attributes.
In TABLE 1 there is just one record for each stock_code
"TABLE2" which contains a log of changes to attributes of products, over time.
"TABLE2" contains the following fields:.
stock_code
stock_attribute
old_value
new_value
change_date
change_time
TABLE 2 has multiple entries ofr each stock_code.
Every time a stock item is change, another entry is made in Table2, with the attribute that has changed, the change date, time, old value and new value.
I want to create a query which will result in a table that has one record for each stock_code (from TABLE 1), and a column for each week over past year, with the value in each field being the last recorded "new_val" for that week (From TABLE 2)
I have tried
SELECT a.`stcode`, b.`week1`, b.`week2`, b.`week3`, b.`week4` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) THEN newval END)week1,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 356 DAY) AND DATE_SUB(CURDATE(),INTERVAL 350 DAY) THEN newval END)week2,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 349 DAY) AND DATE_SUB(CURDATE(),INTERVAL 343 DAY) THEN newval END)week3,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 342 DAY) AND DATE_SUB(CURDATE(),INTERVAL 336 DAY) THEN newval END)week4,
(etc
etc
etc
FROM (SELECT * from TABLE 2 ORDER BY "chngdate" DESC, "chngtime" DESC )as sub) as b ON b.stcode = s.stcode
ORDER BY stcode ASC
The trouble is with this, i get multiple lines for a stock_code which has mutliple entries....
for example, for stock_code abc123 the result i get is
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 null null 4 null null null
abc123 2 null null null null null
abc123 null null null null 3 null
what i WANT is this:
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 2 null 4 null 3 null
I have also tried the following, but teh query took so long, it never finished (there were 52 derived tables!)
SELECT a.`stcode`, w1.`new_value`, w2.`new_value`, w3.`new_value`, w4.`new_value` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
LEFT JOIN (SELECT stcode, depot, fieldname, chngdate, chngtime, newval from STDepotAmendmentsLog WHERE chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) ORDER BY "chngdate" DESC, "chngtime" DESC)as w1 on s.stcode = w1.stcode
etc for week 2, 3, 4 etc etc

You could do the following:
Find the greatest date for each "week"
Find the rows corresponding to those dates
Use conditional aggregation to convert rows into columns
Here is a rough outline of the code. It assumes that e.g. if today is 2020-03-03 then week 52 is from 2020-02-26 to 2020-03-03. Adjust if necessary:
SELECT t.stock_code
, MAX(CASE WHEN weeknum = 51 THEN new_value END) AS week01
, MAX(CASE WHEN weeknum = 50 THEN new_value END) AS week02
, MAX(CASE WHEN weeknum = 1 THEN new_value END) AS week51
, MAX(CASE WHEN weeknum = 0 THEN new_value END) AS week52
FROM table2 AS t
JOIN (
SELECT stock_code
, DATEDIFF(CURRENT_DATE, change_date) div 7 AS weeknum -- count multiples of 7
, MAX(change_date) AS greatest_date
GROUP BY stock_code, weeknum
FROM table2
) AS a ON t.stock_code = a.stock_code AND t.change_date = a.greatest_date
GROUP BY t.stock_code

Related

SQL Query to find rows that didn't occur this month

I am trying to find the number of sellers that made a sale last month but didn't make a sale this month.
I have a query that works but I don't think its efficient and I haven't figured out how to do this for all months.
SELECT count(distinct user_id) as users
FROM transactions
WHERE MONTH(date) = 12
AND YEAR(date) = 2015
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
AND transactions.user_id NOT IN
(
SELECT distinct user_id
FROM transactions
WHERE MONTH(date) = 1
AND YEAR(date) = 2016
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
)
The structure of the table is:
+---------+------------+-------------+--------+
| user_id | date | status | amount |
+---------+------------+-------------+--------+
| 1 | 2016-01-01 | 'COMPLETED' | 1.00 |
| 2 | 2015-12-01 | 'COMPLETED' | 1.00 |
| 3 | 2015-12-01 | 'COMPLETED' | 2.00 |
| 1 | 2015-12-01 | 'COMPLETED' | 3.00 |
+---------+------------+-------------+--------+
So in this case, users with ID 2 and 3, didn't make a sale this month.
Use conditional aggregation:
SELECT count(*) as users
FROM
(
SELECT user_id
FROM transactions
-- 1st of previous month
WHERE date BETWEEN SUBDATE(SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1), interval 1 month)
-- end of current month
AND LAST_DAY(CURRENT_DATE)
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
GROUP BY user_id
-- any row from previous month
HAVING MAX(CASE WHEN date < SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1)
THEN date
END) IS NOT NULL
-- no row in current month
AND MAX(CASE WHEN date >= SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1)
THEN date
END) IS NULL
) AS dt
SUBDATE(CURRENT_DATE, DAYOFMONTH(CURRENT_DATE)-1) = first day of current month
SUBDATE(first day of current month, interval 1 month) = first day of previous month
LAST_DAY(CURRENT_DATE) = end of current month
if you want to generify it, you can use curdate() to get current month, and DATE_SUB(curdate(), INTERVAL 1 MONTH) to get last month (you will need to do some if clause for January/December though):
SELECT count(distinct user_id) as users
FROM transactions
WHERE MONTH(date) = MONTH(DATE_SUB(curdate(), INTERVAL 1 MONTH))
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
AND transactions.user_id NOT IN
(
SELECT distinct user_id
FROM transactions
WHERE MONTH(date) = MONTH(curdate())
AND transactions.status = 'COMPLETED'
AND transactions.amount > 0
)
as far as efficiency goes, I don't see a problem with this one
The following should be pretty efficient. In order to make it even more so, you would need to provide the table definition and and the EXPLAIN.
SELECT COUNT(DISTINCT user_id) users
FROM transactions t
LEFT
JOIN transactions x
ON x.user_id = t.user_id
AND x.date BETWEEN '2016-01-01' AND '2016-01-31'
AND x.status = 'COMPLETED'
AND x.amount > 0
WHERE t.date BETWEEN '2015-12-01' AND '2015-12-31'
AND t.status = 'COMPLETED'
AND t.amount > 0
AND x.user_id IS NULL;
Just some input for thought:
You could create aggregated lists of user-IDs per month, representing all the unique buyers in that month. In your application, you would then simply have to subtract the two months in question in order to get all user-IDs that have only made a sale in one of the two months.
See below for query- and post-processing-examples.
In order to make your query efficient, I would recommend at least a 2-column index for table transactions on [status, amount]. However, in order to prevent the query from having to look up data in the actual table, you could even create a 4-column index [status, amount, date, user_id], which should further improve the performance of your query.
Postgres (v9.0+, tested)
SELECT (DATE_PART('year', t.date) || '-' || DATE_PART('month', t.date)) AS d,
STRING_AGG( DISTINCT t.user_id::TEXT, ',' ) AS buyers
FROM transactions t
WHERE t.status = 'COMPLETED'
AND t.amount > 0
GROUP BY DATE_PART('year', t.date),
DATE_PART('month', t.date)
ORDER BY DATE_PART('year', t.date),
DATE_PART('month', t.date)
;
MySQL (not tested)
SELECT (YEAR(t.date) || '-' || MONTH(t.date)) AS d,
GROUP_CONCAT( DISTINCT t.user_id ) AS buyers
FROM transactions t
WHERE t.status = 'COMPLETED'
AND t.amount > 0
GROUP BY YEAR(t.date), MONTH(t.date)
ORDER BY YEAR(t.date), MONTH(t.date)
;
Ruby (example for post-processing)
db_result = ActiveRecord::Base.connection_pool.with_connection { |con| con.execute( db_query ) }
unique_buyers = db_result.map{|e|[e['d'],e['buyers'].split(',')]}.to_h
buyers_dec15_but_not_jan16 = unique_buyers['2015-12'] - unique_buyers['2016-1']
buyers_nov15_but_not_dec16 = unique_buyers['2015-11']||[] - unique_buyers['2015-12']
...(and so on)...

mysql subquery for charts

I have 2 tables with the following data
inviteTable
inviteDate | contractAmount | status
2014-01-01 1500 awarded
2015-01-01 2000 awarded
2015-01-02 4000 closed
2015-02-01 6000 awarded
2015-02-02 8000 awarded
2015-03-01 8500 awarded
quoteTable
quoteDate | quoteAmount | status
2014-03-01 1500 awarded
2015-01-02 2000 awarded
2015-01-03 4000 sent
2015-01-04 6000 awarded
2015-02-03 8000 awarded
2015-02-10 8500 sent
2015-02-11 9000 awarded
2015-03-01 9500 awarded
I want to make ONE query that gives me the following data structure
month | quotedTotal | quotedCount | awardedTotal | awardedCount
January 8000 2 2000 1
February 17000 2 14000 2
March 9500 1 8500 1
currently i have written this code...but it doesn't work
SELECT
MONTHNAME(t1.due_date) as month,
(select sum(`amount`) from quoteTable where awarded= 1 ) as estimatedAmount,
sum(t1.contactAmount) AS sumContactAmount,
COUNT(*) as `noOfAwarded`
FROM inviteTable t1
where
t1.status = "Awarded" and
t1.inviteDate between '2014-11-01' and '2015-10-31'
GROUP BY MONTH(due_date)
;
basically I want to have ONE query that gives me the following:
volume that got awarded - sum of contractAmount and status awarded
count of how many awarded - count of inviteTable.status=awarded
volume that got quoted - sum of quoteAmount and status awarded
count of how many quoted - count of quotedTable.status=awarded
all grouped by month and in a date range
my query doesn't work. Your help would be highly appreciated as I am stuck!
Your schema is unfortunate; it would be easier if you have just one table with an extra column to record what type of record you have. However, if you first collect the two tables into one table via union all, you can use a fairly simple query to produce the results to want:
select
monthname(_date) month,
sum(quoteAwarded * amount) quotedTotal,
sum(quoteAwarded) quotedCount,
sum(contractAwarded * amount) awardedTotal,
sum(contractAwarded) awardedCount
from (select inviteDate _date, contractAmount amount, status = 'awarded' contractAwarded, 0 quoteAwarded from inviteTable
union all
select quoteDate, quoteAmount, 0, status = 'awarded' from quoteTable) x
where _date between '2014-11-01' and '2015-10-31'
group by 1
order by month(_date)
See SQLFiddle using your sample data, and producing you expected output.
At the core of this query is the convenient fact that (in mysql) "true" is 1 and "false" is 0 - allowing the use of sum() instead of count(), and more importantly avoiding the use of case by using sum() over some simple math.
Here is a way you can do it, note that you have dates in both tables and you need to use join preferably left join , so this will ensure that the dates present in the left table is always returned in this case the month name.
select
t1.month,
t1.awardedTotal,
t1.awardedCount,
t2.quotedTotal,
t2.quotedCount
from(
select
monthname(t1.inviteDate) as month,
month(t1.inviteDate) as m,
sum(
case
when t1.status = 'awarded' then t1.contractAmount else 0
end
) as awardedTotal,
sum(
case
when t1.status = 'awarded' then 1 else 0
end
) as awardedCount
from inviteTable t1
where t1.inviteDate between '2014-11-01' and '2015-10-31'
group by month
)t1
left join(
select
monthname(t2.quoteDate) as month,
sum(
case
when t2.status = 'awarded' then t2.quoteAmount else 0
end
) as quotedTotal,
sum(
case
when t2.status = 'awarded' then 1 else 0
end
) as quotedCount
from quoteTable t2
where t2.quoteDate between '2014-11-01' and '2015-10-31'
group by month
)t2 on
t1.month = t2.month
order by t1.m
http://sqlfiddle.com/#!9/a863a/10
UPDATE:
Yes since you want dates present in both tables it can not be done with the above process since it always consider one table as left table and will get all the dates from that table. For getting data for all the dates across both tables you first need to get the dates from both tables and then using left join get the calculation part something as
select
t1.month,
coalesce(t2.awardedTotal,0) as awardedTotal,
coalesce(t2.awardedCount,0) as awardedCount,
coalesce(t3.quotedTotal,0) as quotedTotal,
coalesce(t3.quotedCount,0) as quotedCount
from(
select month(inviteDate) as m,monthname(inviteDate) as month
from inviteTable where inviteDate between '2014-11-01' and '2015-10-31'
union
select month(quoteDate) as m,monthname(quoteDate) as month
from quoteTable where quoteDate between '2014-11-01' and '2015-10-31'
)t1
left join(
select
monthname(inviteDate) as month,
sum(
case
when status = 'awarded' then contractAmount else 0
end
) as awardedTotal,
sum(
case
when status = 'awarded' then 1 else 0
end
) as awardedCount
from inviteTable
group by month
)t2 on t1.month = t2.month
left join(
select
monthname(quoteDate) as month,
sum(
case
when status = 'awarded' then quoteAmount else 0
end
) as quotedTotal,
sum(
case
when status = 'awarded' then 1 else 0
end
) as quotedCount
from quoteTable
group by month
)t3 on t1.month=t3.month
order by t1.m
http://sqlfiddle.com/#!9/ddaac/14

Aggregating a column by multiple date ranges in a single query

I have a MySQL table with 5 columns:
Device | Name | Date | Source | Income
Neither Device nor Name nor Source are unique.
I am trying to write the SQL for getting the following:
Device | Name | Source | Income (for last 3 days) | Income (for last 9 days) | Income (for last 12 days)
What is the best way to do this?
You can get a conditional SUM() via CASE statements:
SELECT Device
,Name
,Source
,SUM(CASE WHEN date BETWEEN UNIX_TIMESTAMP(NOW() - INTERVAL 3 day) AND UNIX_TIMESTAMP(NOW()) THEN Income END) AS Last_3_Days
,SUM(CASE WHEN date BETWEEN UNIX_TIMESTAMP(NOW() - INTERVAL 9 day) AND UNIX_TIMESTAMP(NOW()) THEN Income END) AS Last_9_Days
FROM YourTable
GROUP BY Device
,Name
,Source
SELECT COUNT(*) as total_l3d FROM table WHERE DATEDIFF(Date, NOW()) <= 3
UNION
SELECT COUNT(*) as total_l9d FROM table WHERE DATEDIFF(Date, NOW()) <= 9
UNION
SELECT COUNT(*) as total_l12d FROM table WHERE DATEDIFF(Date, NOW()) <= 12

Employee Whole Attendance Query

I am stucked with this problem from long time. Though you will find this is big question, but it's not............
I have following tables, with dummy data, so you can also try it.
users Table with Columns
emp_id emp_Name Joining_Date state_id
1 Lauren 11-07-2011 123
2 John 01-08-2012 234
3 Smith 02-09-2011 234
____________________________________________________
CREATE TABLE users
( emp_id int
, emp_Name varchar(20)
, Joining_Date DATE
, state_id int
);
INSERT INTO users (emp_id, emp_Name, Joining_Date, state_id)
VALUES
(1, 'John', '2011-08-10', 123) ,
(2, 'Smith', '2011-09-11', 234) ;
__________________________________________________________________
cl_doctor_call Table
Subject Call_Date call_Done_By(emp_id)
Call 15-01-2012 1
CA 21-02-2012 2
___________________________________________________________________
CREATE TABLE cl_doctor_call
( Subject VARCHAR(10)
, Call_Date DATE
, call_Done_By INT
) ;
INSERT INTO cl_doctor_call (Subject, Call_Date,call_Done_By)
VALUES
('sub1', '2011-08-15', 1) ,
('sub2', '2011-09-16', 2) ;
___________________________________________________________________
cl_ chemist Table
Subject Call_Date call_Done_By(emp_id)
Chemist 1-02-2012 2
Texo 21-03-2012 1
____________________________________________________________________________________
CREATE TABLE cl_chemist ( Subject VARCHAR(10), Call_Date DATE, call_Done_By INT );
INSERT INTO cl_chemist (Subject, Call_Date,call_Done_By) VALUES ('sub3','2011-08-19',2),('sub5','2011-09-25',1);
___________________________________________________________
cl_Stock Table
Subject Call_Date call_Done_By(emp_id)
Sub1 1-02-2012 1
Sub2 21-03-2012 3
____________________________________________________________
CREATE TABLE cl_Stock
( Subject VARCHAR(10)
, Call_Date DATE
, call_Done_By INT
);
INSERT INTO cl_Stock (Subject, Call_Date,call_Done_By)
VALUES
('ABC', '2011-10-13', 1) ,
('sub5', '2011-11-17', 2) ;
______________________________________________________
Meetings Table
Subject Meeting_Date call_Done_By(emp_id)
Sub1 11-02-2012 1
Sub2 23-03-2012 2 _____________________________________________________________________________________
CREATE TABLE Meetings ( Subject VARCHAR(10), meet_Date DATE, meet_Done_By INT );
INSERT INTO Meetings (Subject, meet_Date,meet_Done_By)
VALUES
('Planning','2011-11-01',2),
('Deploy','2011-12-15',1);
______________________________________________________________________________________
Leave Table
Subject from_Date to_date Requested_By(emp_id) status
Sub1 01-02-2012 03-02-2012 2 Declined
Sub2 21-03-2012 22-03-2012 1 Approved
Holiday Table
Holiday_Name Holiday_Date States_Id
New Year 01-01-2012 123
Independence Day 15-08-2012 234
______________________________________________________________________________________
CREATE TABLE Holiday ( Holiday_Name VARCHAR(10),Holiday_Date DATE, State_Id_c INT );
INSERT INTO Holiday (Holiday_Name,Holiday_Date,State_Id_c) VALUES ('NEW_YEAR','2012-01-01',123), ('CHRISHMAS','2011-12-25',234);
______________________________________________________________________________________
Replace with date
for stock='Y'
for chemist='Y'
for doctor='Y'
for leave='L'
for Sunday='S'
for meeting ='M'
for Holiday='H'
Hope so still you are with me.
Now Admin will select 'month' and 'year' and according to that attendance report for
employee will displayed.
Output :
Employee Year Month 1 2 3 4 5 6 7.... for all 31 days
John 2011 Nov Y Y Y H Y L S....
Smith 2011 Nov Y Y Y H Y M S.....
. . . ................
. . . ................
&so on &so on &so on & so on
here in output 1,2,3.... are days from 0-31 for a month which we can write using 'case'
Consider if employee is present on day show its status as 'Y' ,if he is on leave show it's status as 'L; if there is Sunday show status 'S' for holiday show 'H' and so on.
So basically I want a write a query to retrieve all these details.
So pl z suggest me ways here, that How can I display this attendance Report by a query or procedure.
I have tried so far this .....
DELIMITER $$
USE `leosatyen_claris`$$
DROP PROCEDURE IF EXISTS `Test`$$
CREATE DEFINER=`leosatyen_claris`#`%` PROCEDURE `Test`(IN month_last_date DATE)
BEGIN
SELECT
users.id AS Id,
users.first_name AS Employee,
CASE MAX(CASE WHEN DATE_FORMAT(main.cdate,'%d')='01' THEN main.task
WHEN DATE_FORMAT(DATE_ADD(month_last_date, INTERVAL 1 DAY),'%W')='SUNDAY' THEN 0
WHEN main.cdate IS NULL THEN -1
ELSE -2 END)
WHEN -1 THEN 'NA'
WHEN 0 THEN 'S'
WHEN 1 THEN 'Y'
WHEN 2 THEN 'C'
WHEN 3 THEN 'S1'
WHEN 4 THEN 'P'
WHEN 5 THEN 'M'
END Day1,
CASE MAX(CASE WHEN DATE_FORMAT(main.cdate,'%d')='02' THEN main.task
WHEN DATE_FORMAT(DATE_ADD(month_last_date, INTERVAL 2 DAY),'%W') ='SUNDAY' THEN 0
WHEN main.cdate IS NULL THEN -1
ELSE -2 END)
WHEN -1 THEN 'NA'
WHEN 0 THEN 'S'
WHEN 1 THEN 'Y'
WHEN 2 THEN 'C'
WHEN 3 THEN 'S1'
WHEN 4 THEN 'P'
WHEN 5 THEN 'M'
END Day2,
.
.
. ## UP TO 31 DAYS
CASE MAX(CASE WHEN DATE_FORMAT(main.cdate,'%d')='31' THEN main.task
WHEN DATE_FORMAT(DATE_ADD(month_last_date, INTERVAL 31 DAY),'%W')='SUNDAY' THEN 0
WHEN main.cdate IS NULL THEN -1
ELSE -2 END)
WHEN -1 THEN 'NA'
WHEN 0 THEN 'S'
WHEN 1 THEN 'Y'
WHEN 2 THEN 'C'
WHEN 3 THEN 'S1'
WHEN 4 THEN 'P'
WHEN 5 THEN 'M'
END Day31
FROM
users
LEFT JOIN
(
SELECT DATE_FORMAT(cl_doctor_call.date_entered,'%Y-%m-%d')AS cdate,
cl_doctor_call.created_by AS emp,
1 task
FROM
cl_doctor_call
WHERE
DATE_FORMAT(cl_doctor_call.date_entered,'%Y-%m-%d')>month_last_date AND
DATE_FORMAT(cl_doctor_call.date_entered,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT
DATE_FORMAT(cl_chemist_call.date_entered,'%Y-%m-%d') AS cdate,
cl_chemist_call.created_by AS emp,
2 task
FROM
cl_chemist_call
WHERE
DATE_FORMAT(cl_chemist_call.date_entered,'%Y-%m-%d')>month_last_date AND
DATE_FORMAT(cl_chemist_call.date_entered,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT
DATE_FORMAT(cl_stockist_call_cstm.calldate_c,'%Y-%m-%d') AS cdate,
cl_stockist_call.created_by AS emp,
3 task
FROM
cl_stockist_call LEFT JOIN cl_stockist_call_cstm
ON cl_stockist_call.id=cl_stockist_call_cstm.id_c
WHERE
DATE_FORMAT(cl_stockist_call_cstm.calldate_c,'%Y-%m-%d')> month_last_date AND
DATE_FORMAT(cl_stockist_call_cstm.calldate_c,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT DATE_FORMAT(cl_product_pramotional_call.date_entered,'%Y-%m-%d') AS cdate,
cl_product_pramotional_call.created_by AS emp,
4 task
FROM cl_product_pramotional_call
WHERE
DATE_FORMAT(cl_product_pramotional_call.date_entered,'%Y-%m-%d')>month_last_date AND
DATE_FORMAT(cl_product_pramotional_call.date_entered,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT DATE_FORMAT(meetings.date_start,'%Y-%m-%d') AS cdate,
meetings.created_by AS emp,
5 task
FROM meetings
WHERE
DATE_FORMAT(meetings.date_start,'%Y-%m-%d')> month_last_date AND
DATE_FORMAT(meetings.date_start,'%Y-%m-%d') <= DATE_ADD(month_last_date, INTERVAL 30 DAY)
)AS main
ON main.emp = users.id
WHERE DATE_FORMAT(users.date_entered,'%Y-%m-%d')<= month_last_date
GROUP BY users.id ;
END$$
DELIMITER ;
Here I am getting all Sunday's of that month AND All customer calls like Doctor_call,
chemist call, stockist call and meetings entries correctly.
Now I just need to add 'Leave' and Holiday Entries. So anybody can assist me to achieve holidays and leave in my existing procedure?
any suggestions will be helpful for me....
Finally I achieved answer for Attendance Report.
We need to execute this lengthy stored procedure to get monthly attendance of all Employees.
To execute this procedure we need to pass IN parameter as last date of previous month like if we need to see December 2011 attendance Report pass '2011-11-30' AS IN parameter.
DELIMITER $$
USE `Sample`$$
DROP PROCEDURE IF EXISTS `Test`$$
CREATE DEFINER=`Sample`#`%` PROCEDURE `Test`(IN month_last_date DATE)
BEGIN
SELECT
users.id AS Id,DATE_FORMAT(month_last_date,'%Y')AS YEAR,DATE_FORMAT(month_last_date,'%M')AS MONTH,
CONCAT(users.first_name,users.last_name) AS Employee,cl_territories.name AS Territory,
CASE MAX(CASE WHEN DATE_FORMAT(main.cdate,'%d')='01' THEN main.task
WHEN DATE_FORMAT(DATE_ADD(month_last_date, INTERVAL 1 DAY),'%W')='SUNDAY' THEN 0
WHEN main.cdate IS NULL THEN -1
ELSE -2 END)
WHEN -1 THEN 'NA'
WHEN 0 THEN 'S'
WHEN 1 THEN 'Y'
WHEN 2 THEN 'C'
WHEN 3 THEN 'S1'
WHEN 4 THEN 'P'
WHEN 5 THEN 'M'
WHEN 6 THEN 'H'
WHEN 7 THEN 'L'
END Day1,
CASE MAX(CASE WHEN DATE_FORMAT(main.cdate,'%d')='02' THEN main.task
WHEN DATE_FORMAT(DATE_ADD(month_last_date, INTERVAL 2 DAY),'%W') ='SUNDAY' THEN 0
WHEN main.cdate IS NULL THEN -1
ELSE -2 END)
WHEN -1 THEN 'NA'
WHEN 0 THEN 'S'
WHEN 1 THEN 'Y'
WHEN 2 THEN 'C'
WHEN 3 THEN 'S1'
WHEN 4 THEN 'P'
WHEN 5 THEN 'M'
WHEN 6 THEN 'H'
WHEN 7 THEN 'L'
END Day2,
.
.
. # upto 31 days
CASE MAX(CASE WHEN DATE_FORMAT(main.cdate,'%d')='31' THEN main.task
WHEN DATE_FORMAT(DATE_ADD(month_last_date, INTERVAL 31 DAY),'%W')='SUNDAY' THEN 0
WHEN main.cdate IS NULL THEN -1
ELSE -2 END)
WHEN -1 THEN 'NA'
WHEN 0 THEN 'S'
WHEN 1 THEN 'Y'
WHEN 2 THEN 'C'
WHEN 3 THEN 'S1'
WHEN 4 THEN 'P'
WHEN 5 THEN 'M'
WHEN 6 THEN 'H'
WHEN 7 THEN 'L'
END Day31
FROM
users LEFT JOIN users_cstm
ON users.id=users_cstm.id_c
LEFT JOIN cl_territories
ON users_cstm.cl_territories_id1_c=cl_territories.id
LEFT JOIN
(
SELECT DATE_FORMAT(cl_doctor_call.date_entered,'%Y-%m-%d')AS cdate,
cl_doctor_call.created_by AS emp,
1 task
FROM
cl_doctor_call
WHERE
DATE_FORMAT(cl_doctor_call.date_entered,'%Y-%m-%d')>month_last_date AND
DATE_FORMAT(cl_doctor_call.date_entered,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT
DATE_FORMAT(cl_chemist_call.date_entered,'%Y-%m-%d') AS cdate,
cl_chemist_call.created_by AS emp,
2 task
FROM
cl_chemist_call
WHERE
DATE_FORMAT(cl_chemist_call.date_entered,'%Y-%m-%d')>month_last_date AND
DATE_FORMAT(cl_chemist_call.date_entered,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT
DATE_FORMAT(cl_stockist_call_cstm.calldate_c,'%Y-%m-%d') AS cdate,
cl_stockist_call.created_by AS emp,
3 task
FROM
cl_stockist_call LEFT JOIN cl_stockist_call_cstm
ON cl_stockist_call.id=cl_stockist_call_cstm.id_c
WHERE
DATE_FORMAT(cl_stockist_call_cstm.calldate_c,'%Y-%m-%d')> month_last_date AND
DATE_FORMAT(cl_stockist_call_cstm.calldate_c,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT DATE_FORMAT(cl_product_pramotional_call.date_entered,'%Y-%m-%d') AS cdate,
cl_product_pramotional_call.created_by AS emp,
4 task
FROM cl_product_pramotional_call
WHERE
DATE_FORMAT(cl_product_pramotional_call.date_entered,'%Y-%m-%d')>month_last_date AND
DATE_FORMAT(cl_product_pramotional_call.date_entered,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT DATE_FORMAT(meetings.date_start,'%Y-%m-%d') AS cdate,
meetings.created_by AS emp,
5 task
FROM meetings
WHERE
DATE_FORMAT(meetings.date_start,'%Y-%m-%d')> month_last_date AND
DATE_FORMAT(meetings.date_start,'%Y-%m-%d') <= DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT DATE_FORMAT(cl_holidays_structures.holiday_date,'%Y-%m-%d') AS cdate,
users.id AS emp,
6 task
FROM users LEFT JOIN users_cstm
ON users.id=users_cstm.id_c
LEFT JOIN cl_states
ON users_cstm.cl_states_id1_c=cl_states.id
LEFT JOIN cl_holidays_structures
ON cl_holidays_structures.cl_states_id_c=users_cstm.cl_states_id1_c
WHERE
DATE_FORMAT(cl_holidays_structures.holiday_date,'%Y-%m-%d')> month_last_date AND
DATE_FORMAT(cl_holidays_structures.holiday_date,'%Y-%m-%d') <= DATE_ADD(month_last_date, INTERVAL 30 DAY)
UNION ALL
SELECT cl_leave_management_cstm.from_date_c+ INTERVAL td.days DAY cdate,
users.id AS emp,
7 task
FROM
cl_leave_management LEFT JOIN cl_leave_management_cstm
ON cl_leave_management.id=cl_leave_management_cstm.id_c
LEFT JOIN users
ON cl_leave_management.created_by = users.id
JOIN temp_days td
ON DATEDIFF(cl_leave_management_cstm.to_date_c, cl_leave_management_cstm.from_date_c) >= td.days
WHERE DATE_FORMAT(cl_leave_management_cstm.from_date_c,'%Y-%m-%d')> month_last_date AND
DATE_FORMAT(cl_leave_management_cstm.from_date_c,'%Y-%m-%d')<=DATE_ADD(month_last_date, INTERVAL 30 DAY) AND
cl_leave_management_cstm.status_c='Approved'
)AS main
ON main.emp = users.id
WHERE DATE_FORMAT(users.date_entered,'%Y-%m-%d')<= month_last_date
GROUP BY users.id ;
END$$
DELIMITER ;
Thanks to all who guide me to get this result.

selecting between date range and forcing empty rows to be 0?

I have a table that looks like
expires | value
-------------------
2011-06-15 | 15
2011-06-15 | 15
2011-06-25 | 15
2011-07-15 | 15
2011-07-15 | 15
2011-07-25 | 15
2011-08-15 | 15
2011-08-15 | 15
2011-08-25 | 15
I want to run a query that will spit out
June | 45
July | 45
August | 45
So my query is
SELECT SUM(amount) AS `amount`,
DATE_FORMAT(expires , '%M') AS `month`
FROM dealDollars
WHERE DATE(expires) BETWEEN DATE(NOW())
AND LAST_DAY(DATE(NOW()+INTERVAL 3 MONTH))
GROUP BY MONTH(expires)
Which works fine. But with the result, if there were no rows in say July, July would not show up.
How can I force July to show up with 0 as its value?
There is no easy way to do this. One possible way is to have a table called months:
Which will have 12 rows: (January, February, ..., December)
You can left join the Months table with the query you have to get the desired output.
The general consensus is that you should just create a table of month names. What follows is a silly solution which can serve as a workaround.
You'll have to work on the specifics yourself, but have you looked at sub-queries in the from clause?
Basically, it would be something like this:
SELECT NVL(B.amount, 0) as `amount`, A.month as `month`
FROM (SELECT 'January' as `month`
UNION SELECT 'February' as `month`
UNION SELECT 'March' as `month`...
UNION SELECT 'DECEMBER' as `month`) as A
LEFT JOIN
(SELECT SUM(amount) AS `amount`,
DATE_FORMAT(expires , '%M') AS `month`
FROM dealDollars
WHERE
DATE(expires) BETWEEN
DATE(NOW()) AND
LAST_DAY(DATE(NOW()+INTERVAL 3 MONTH))
GROUP BY MONTH(expires)) as B
ON (A.MONTH = B.MONTH)
Crazy, no?
MySQL doesn't have recursive functionality, so you're left with using the NUMBERS table trick -
Create a table that only holds incrementing numbers - easy to do using an auto_increment:
DROP TABLE IF EXISTS `example`.`numbers`;
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Populate the table using:
INSERT INTO NUMBERS
(id)
VALUES
(NULL)
...for as many values as you need. In this case, the INSERT statement needs to be run at least 3 times.
Use DATE_ADD to construct a list of days, increasing based on the NUMBERS.id value:
SELECT x.dt
FROM (SELECT DATE(DATE_SUB(CURRENT_DATE(), INTERVAL (n.id - 1) MONTH)) AS dt
FROM numbers n
WHERE DATE(DATE_SUB(CURRENT_DATE(), INTERVAL (n.id - 1) MONTH)) BETWEEN CURRENT_DATE()
AND LAST_DAY(CURRENT_DATE() +INTERVAL 3 MONTH)) ) x
Use an OUTER JOIN to get your desired output:
SELECT x.dt,
COUNT(*) AS total
FROM (SELECT DATE(DATE_SUB(CURRENT_DATE(), INTERVAL (n.id - 1) MONTH)) AS dt
FROM numbers n
WHERE DATE(DATE_SUB(CURRENT_DATE(), INTERVAL (n.id - 1) MONTH)) BETWEEN CURRENT_DATE()
AND LAST_DAY(CURRENT_DATE() +INTERVAL 3 MONTH)) ) x
LEFT JOIN YOUR_TABLE y ON y.date = x.dt
GROUP BY x.dt
ORDER BY x.dt
Why Numbers, not Dates?
Simple - dates can be generated based on the number, like in the example I provided. It also means using a single table, vs say one per data type.
select MONTHNAME(expires) as month_name,sum(`value`) from Table1
group by month_name order by null;
fiddle