Appointments per month, December missing? - mysql

SELECT month(dateofappointment), COUNT(*) 'NumberOfAppointments'
FROM appointment
WHERE YEAR(dateofappointment) = '2016'
GROUP BY MONTH(dateofappointment)
this shows me all months but December is not there because there weren't any appointments made in that year. how do i show December as being 0?

To solve these types of queries it often helps to express them as a series of requirements, this can make it easier to resolve.
When the results don't come out as expected, update your requirements statements with new requirements as you identify them, then try again:
As I see it now you have 2 requirements:
Return a single row for each month of the year of 2016
For each row show a count of the appointments for the corresponding month
Ok so that was verbose, but you see what you are missing from your query is a statement that defines the '1 row for each month of the year 2016' So you need to build that recordset first, either manually or through recursion.
MySQL does not currently support recursive Common Table Expressions, this is a trivial concept in many other RDBMSs
But if MySQL doesn't support recursion, what are our options? Here are some other attempts on SO:
МуSQL Get a list of dates in month, year
How to generate a dynamic sequence table in MySQL?
generate an integer sequence in MySQL
This might sound a bit of a hack, but you can use any table in your database that has more than 12 rows and has an auto-incrementing field, oh and was seeded to start at 1 (or below). Forget about whether this is right or wrong, it will work:
SELECT Id
FROM LogEvent -- An arbitrary table that I know has records starting from 1
WHERE Id BETWEEN 1 AND 12
So that is hacky, but we can implement a row count function so that we can use any table with 12 or more rows, regardless of ids or seeding, stole this from: MySQL get row number on select - Answer by Mike Cialowicz
SET #rank=0;
SELECT #rank:=#rank+1 AS rank
FROM orders
WHERE rank <= 12
Now we can either union the missing rows from this result set to the original query or use a join operator. First solution using union.
It is common to use UNION ALL to inject missing rows to a recordset because it separates the expected result query from the exceptional or default results. Sometimes this syntax makes it easier to interpret the expected operation
SET #rank = 0;
SELECT month(dateofappointment) as Month, COUNT(*) 'NumberOfAppointments'
FROM appointment
WHERE YEAR(dateofappointment) = '2016'
GROUP BY MONTH(dateofappointment)
UNION ALL
SELECT rank, 0
FROM (
SELECT #rank:=#rank+1 AS rank
FROM rows
WHERE #rank < 12
) months
WHERE NOT EXISTS (SELECT dateofappointment
FROM appointment
WHERE YEAR(dateofappointment) = '2016' AND MONTH(dateofappointment) = months.rank)
ORDER BY Month
But it makes for an ugly query. You could also join on the months query with a left join on the count of appointments, but here the intention is harder to identify.
SET #rank = 0;
SELECT months.rank, COUNT(appointment.dateofappointment)
FROM (
SELECT #rank:=#rank+1 AS rank
FROM rows
WHERE #rank < 12
) months
LEFT OUTER JOIN appointment ON months.rank = Month(appointment.dateofappointment) AND YEAR(dateofappointment) = '2016'
GROUP BY months.rank
I have saved these queries into a SqlFiddle so you can see the results:
http://sqlfiddle.com/#!9/99d485/4
As I pointed out above, this is trivial in MS SQL and Oracle RDBMS, where we can generate sequences of values dynamically through recursive Common Table Expressions (CTEs) For the players at home here is an implementation in MS SQL Server 2014. The example is a little more evolved, using a from and to date to filter the results dynamically
-- Dynamic MS SQL Example using recursive CTE
DECLARE #FromDate Date = '2016-01-01'
DECLARE #ToDate Date = '2016-12-31'
;
WITH Months(Year, Month, Date) AS
(
SELECT Year(#FromDate), Month(#FromDate), #FromDate
UNION ALL
SELECT Year(NextMonth.Date), Month(NextMonth.Date), NextMonth.Date
FROM Months
CROSS APPLY (SELECT DateAdd(m, 1, Date) Date) NextMonth
WHERE NextMonth.Date < #ToDate
)
SELECT Months.Year, Months.Month, COUNT(*) as 'NumberOfAppointments'
FROM Months
LEFT OUTER JOIN appointment ON Year(dateofappointment) = Months.Year AND Month(dateofappointment) = Months.Month
GROUP BY Months.Year, Months.Month

Related

SQL - Show all dates in a given range and count how many posts there are on that date using a timestamp from database

I have created a query that will count the number of posts and group them by the date, however the result doesn't show the dates when there were no posts.
QUERY:
SELECT DATE(Date_Uploaded) AS ForDate
, COUNT(*) AS NumPosts
FROM Articles
WHERE 'Status'='4'
GROUP
BY DATE(Date_Uploaded)
ORDER
BY ForDate
for info 'Status' = 4 means the post is published on the site and 'Date_Uploaded' is the timestamp of publication.
This will for example return;
2020-09-10: 2
2020-09-14: 1
2020-09-25: 4
However I want;
2020-09-10: 2
2020-09-11: 0
2020-09-12: 0
2020-09-13: 0
2020-09-14: 1
etc.
The reason I need my data like this is so I can use it with google charts to create a column chart that shows the number of posts over time. by not including the dates with no posts the chart will not format the missing dates.
If there is a way to still use the same query but have google charts space the data appropriately this would also be a great solution.
Thanks.
EDIT: The date range would be defined on the same page I intend to place the chart using the data
If you have data for all dates, but just not for status = 4, then you can use conditional aggregation
SELECT DATE(Date_Uploaded) AS ForDate,
SUM(CASE WHEN Status = 4 THEN 1 ELSE 0 END) AS NumPosts
FROM Articles
GROUP BY DATE(Date_Uploaded)
ORDER BY ForDate;
Otherwise, you need to use a table that has dates (or numbers) or generate them and use LEFT JOIN. However, the exact syntax depends on the database.
EDIT:
In MySQL, you can use a recursive CTE to generate the dates:
with recursive dates as (
select date('2020-09-10') as date
union all
select date + interval 1 day
from dates
where date < '2020-09-25'
)
select d.date, count(a.date_uploaded)
from dates d left join
articles a
on a.date_uploaded >= d.date and
a.date_uploaded < d.date + interval 1 day and
a.status = 4
group by d.date;

How to calculate the difference between two dates in stored procedure for multiple sets in rows

I have a data table like this
id typeid date
12 exited 01-06-2017
1 approved 05-06-2017
7 attended 08-06-2017
9 admitted 10-06-2017
45 approved 12-06-2017
67 admitted 16-06-2017
The answer I want would be something like this:
difference(days)
5
4
I want to calculate the date difference between approved and admitted (wherever they are, so I think we have to use looping statement). I want to write a stored procedure in MySql (version: 5.6) which returns the result in any form (maybe a table having these results).
This is actually the sort of problem for which window functions are very well suited, but since you are using version 5.6, this isn't a possibility. Here is one way to do this:
SELECT
DATEDIFF(
(SELECT t2.date FROM yourTable t2
WHERE t2.typeid = 'admitted' AND t2.date > t1.date
ORDER BY t2.date LIMIT 1),
t1.date) AS difference
FROM yourTable t1
WHERE
typeid = 'approved'
ORDER BY
date;
The logic in the above query is that we restrict only records which are approved type. For each such records, using a correlated subquery, we then seek ahead and time and find the nearest record which is admitted type. Then, we take the difference between those two dates.
Check the working demo link below.
Demo
If you are concerned about performance, you can assign a value to each row which is the cumulative number of "admitted". Then use this for aggregation:
select max(case when typeid = 'approved' then date end) as approved_date,
max(case when typeid = 'admitted' then date end) as admitted_date,
datediff(max(case when typeid = 'admitted' then date end),
max(case when typeid = 'approved' then date end)
) as diff
from (select t.*,
(#cnt := #cnt + (typeid = 'approved')) as grp
from (select t.* from t order by date) t cross join
(select #cnt := 0) params
) t
group by grp;
This can take advantage of an index on (date) for assigning grp. Then it just needs to do a group by.
Using a correlated subquery can become quite expensive as the size of the data grows. So for larger data, this should be much more efficient.
In either case, using window functions (available in MySQL 8+) is much, much the preferred solution.

SELECT a column not in GROUP BY clause

I'm trying to write a query that would select only the rows that have events which where the only events in that year.
Eg:
Year Event
2011 A
2011 B
2012 C
2013 B
2013 D
2014 D
So, I would like to get the rows 2012 C and 2014 D in the results.
I tried doing a GROUP BY on Year, but that wouldn't let me select the Event column.
2011 and 2013 have 2 events, so these shouldn't be in the results.
Please help.
EDIT: I could write a nested query to get the only the rows having count(Year) = 1 with GROUP BY Year, but I'm unable to get the Event column selected in the outer query
SELECT Year, Event from table where Year in (SELECT Year from table GROUP BY Year Having count(*) = 1) as count;
There is no need for using a subquery or nested query. You can simply GROUP By Year field and use HAVING COUNT(Year)=1 to find the required rows. So, the applicable query will be:
SELECT Year, Event
FROM table_name
GROUP BY Year
HAVING COUNT(Year)=1
You can find the executable solution sample at:
http://sqlfiddle.com/#!9/b47044/11
Logic:
When you group by Yearit aggregates all rows with same year. So, count will be 2 for 2011.
You can check this by running:
SELECT Year, Event, COUNT(Year) as event_count
FROM table_name
GROUP BY Year
You can see this intermediate step in execution, at: http://sqlfiddle.com/#!9/b47044/10
This above solution will only work for MySQL version < 5.7. For higher versions find the solution below.
For 5.7 and greater the ONLY_FULL_GROUP_BY SQL mode is enabled by default so this will fail. Either you can update this mode( Refer answers under SELECT list is not in GROUP BY clause and contains nonaggregated column .... incompatible with sql_mode=only_full_group_by ) or alternatively you can use ANY_VALUE() function to refer to the non-aggregated column, so update query that will work in MySQL 5.7 and greater is:
SELECT Year, ANY_VALUE(Event)
FROM table_name
GROUP BY Year
HAVING COUNT(Year)=1;
You can find executable example at: https://paiza.io/projects/e/tU-7cUoy3hQUk2A7tFfVJg
Reference: https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/miscellaneous-functions.html#function_any-value
You have a minor mistake in the query, the count(*) which is used in having clause should also be in the select clause
SELECT Year, Event from table where Year in (
SELECT Year from (
SELECT Year,count(*) from table GROUP BY Year Having count(*) = 1)temp
);
Only those Year and events need to be filtered which contains single event that Year
Inner Query would give you only years which have one event
Outer query would select the events of those years
SELECT Year, Event from table where Year in
(SELECT Year from table GROUP BY Year Having count(*) = 1);
Good Question,
You don't even need a subquery to get the desired output. Concatenate all the event names into one string, then search for comma , in the string, If comma , is found, this year has more than one events, otherwise only one.
SELECT Year, GROUP_CONCAT(Event) AS Event FROM Events GROUP BY (year) having
INSTR(Event, ",") = 0;
SELECT Year, Event
FROM table
WHERE Year in (SELECT Year
FROM table
GROUP BY Year
HAVING count(*) = 1);

how to count number of lines with jointure in Talend on Oracle

i have 3 tables
supplier(id_supp, name, adress, ...)
Customer(id_cust, name, adress, ...)
Order(id_order, ref_cust, ref_supp, date_order...)
I want to make a job that counts the number of orders by Supplier, for last_week, last_two_weeks with Talend
select
supp.name,
(
select
count(*)
from
order
where
date_order between sysdate-7 and sysdate
nd ref_supp=id_supp
) as week_1,
(
select
count(*)
from
order
where
date_order between sysdate-14 and sysdate-7
nd ref_supp=id_supp
) as week_2
from supplier supp
the resaon for what i'm doing this, is that my query took to much time
You need a join between supplier and order to get supplier names. I show an inner join, but if you need ALL suppliers (even those with no orders in the order table) you may change it to a left outer join.
Other than that, you should only have to read the order table once and get all the info you need. Your query does more than one pass (read EXPLAIN PLAN for your query), which may be why it is taking too long.
NOTE: sysdate has a time-of-day component (and perhaps the date_order value does too); the way you wrote the query may or may not do exactly what you want it to do. You may have to surround sysdate by trunc().
select s.name,
count(case when o.date_order between sysdate - 7 and sysdate then 1 end)
as week_1,
count(case when o.date_order between sysdate - 14 and sysdate - 7 then 1 end)
as week_2
from supplier s inner join order o
on s.id_supp = o.ref_supp
;

How to GROUP BY consecutive data (date in this case)

I have a products table and a sales table that keeps record of how many items a given product sold during each date. Of course, not all products have sales everyday.
I need to generate a report that tells me how many consecutive days a product has had sales (from the latest date to the past) and how many items it sold during those days only.
I'd like to tell you how many things I've tried so far, but the only succesful (and slow, recursive) ones are solutions inside my application and not inside SQL, which is what I want.
I also have browsed several similar questions on SO but I haven't found one that lets me have a clear idea of what I really need.
I've setup a SQLFiddle here to show you what I'm talking about. There you will see the only query I can think of, which doesn't give me the result I need. I also added comments there showing what the result of the query should be.
I hope someone here knows how to accomplish that. Thanks in advance for any comments!
Francisco
http://sqlfiddle.com/#!2/20108/1
Here is a store procedure that do the job
CREATE PROCEDURE myProc()
BEGIN
-- Drop and create the temp table
DROP TABLE IF EXISTS reached;
CREATE TABLE reached (
sku CHAR(32) PRIMARY KEY,
record_date date,
nb int,
total int)
ENGINE=HEAP;
-- Initial insert, the starting point is the MAX sales record_date of each product
INSERT INTO reached
SELECT products.sku, max(sales.record_date), 0, 0
FROM products
join sales on sales.sku = products.sku
group by products.sku;
-- loop until there is no more updated rows
iterloop: LOOP
-- Update the temptable with the values of the date - 1 row if found
update reached
join sales on sales.sku=reached.sku and sales.record_date=reached.record_date
set reached.record_date = reached.record_date - INTERVAL 1 day,
reached.nb=reached.nb+1,
reached.total=reached.total + sales.items;
-- If no more rows are updated it means we hit the most longest days_sold
IF ROW_COUNT() = 0 THEN
LEAVE iterloop;
END IF;
END LOOP iterloop;
-- select the results of the temp table
SELECT products.sku, products.title, products.price, reached.total as sales, reached.nb as days_sold
from reached
join products on products.sku=reached.sku;
END//
Then you just have to do
call myProc()
A solution in pure SQL without store procedure : Fiddle
SELECT sku
, COUNT(1) AS consecutive_days
, SUM(items) AS items
FROM
(
SELECT sku
, items
-- generate a new guid for each group of consecutive date
-- ie : starting with day_before is null
, #guid := IF(#sku = sku and day_before IS NULL, UUID(), #guid) AS uuid
, #sku := sku AS dummy_sku
FROM
(
SELECT currents.sku
, befores.record_date as day_before
, currents.items
FROM sales currents
LEFT JOIN sales befores
ON currents.sku = befores.sku
AND currents.record_date = befores.record_date + INTERVAL 1 DAY
ORDER BY currents.sku, currents.record_date
) AS main_join
CROSS JOIN (SELECT #sku:=0) foo_sku
CROSS JOIN (SELECT #guid:=UUID()) foo_guid
) AS result_to_group
GROUP BY uuid, sku
The query is really not that hard. Declare variables via cross join (SELECT #type:=0) type. Then in the selects, you can set variables value row by row. It is necessary for simulating Rank function.
select
p.*,
sum(s.items) sales,
count(s.record_date) days_sold
from
products p
join
sales s
on
s.sku = p.sku
where record_date between '2013-04-18 00:00:00' and '2013-04-26 00:00:00'
group by sku;