I have a problem in postgresql.
I have one cohorte (gathering of people) and i would like counting the persons in this cohorte.
Begin date : "2014-09-01", End date : "2014-11-30".
I have 5 persons between 09/01 and 09/22
I have 5 persons between 09/20 and 09/25
I have 5 persons between 09/26 and 10/05
I have 5 persons between 10/01 ans 11/30
I want to have the max of accommodation for each month between the begin date and the end date in SQL (or PHP). Expected max person count:
September(09) => 10
October(10) => 10
November(11) => 5
Find the maximum of simultaneously present persons on a single day for every month in a given period.
I suggest generate_series() to produce the series of days in your period. Then aggregate twice:
First to get a count for each day. A single day can be dealt with plain BETWEEN. Your ranges are obviously meant to be with include borders.
Second to get the maximum per month.
SELECT date_trunc('month', day)::date AS month, max(ct) AS max_ct
FROM (
SELECT g.day, count(*) AS ct
FROM cohorte
,generate_series('2014-09-01'::date -- first of Sept.
,'2014-11-30'::date -- last of Nov.
,'1 day'::interval) g(day)
WHERE g.day BETWEEN t_begin AND t_end
GROUP BY 1
) sub
GROUP BY 1
ORDER BY 1;
Returns:
month | max_ct
-----------+--------
2014-09-01 | 10
2014-10-01 | 10
2014-11-01 | 5
Use to_char() to prettify the month output.
SQL Fiddle .. is down ATM. Here is my test case (that you should have provided):
CREATE TEMP TABLE cohorte (
cohorte_id serial PRIMARY KEY
,person_id int NOT NULL
,t_begin date NOT NULL -- inclusive
,t_end date NOT NULL -- inclusive
);
INSERT INTO cohorte(person_id, t_begin, t_end)
SELECT g, '2014-09-01'::date, '2014-09-22'::date
FROM generate_series (1,5) g
UNION ALL
SELECT g+5, '2014-09-20', '2014-09-25'
FROM generate_series (1,5) g
UNION ALL
SELECT g+10, '2014-09-26', '2014-10-05'
FROM generate_series (1,5) g
UNION ALL
SELECT g+15, '2014-10-01', '2014-11-30'
FROM generate_series (1,5) g;
For more complex checks I'd suggest the OVERLAPS operator:
Find overlapping date ranges in PostgreSQL
For more complex scenarios I'd also consider range types:
Preventing adjacent/overlapping entries with EXCLUDE in PostgreSQL
can't you use window function?
I'd try something like this (I've not tested this code, just exposed my thoughts)
SELECT max(count) FROM (
SELECT count(*) OVER (PARTITION BY ???) as count
FROM contract
WHERE daterange(dateStart, dateEnd, '[]') && daterange('2014-09-01', '2014-10-01', '[)')
) as max
Here, my problem remains that I can't find a way to partition for each day of the interval. Maybe this is a wrong approach, but I would be interested by a solution based on windows.
edit: with this request, you have the max of simultaneous present, but over all the time, not only a given month
with presence as (
SELECT id, generate_series(begin_date, end_date, '1 day'::interval) AS date
FROM test
),
presents as (
SELECT count(*) OVER (PARTITION BY date) AS count
FROM presence
)
SELECT max(count) from presents;
Here we come, I think
Imagine your person table has 3 columnsĀ :
id
entrance_date
leaving_date
the request would look like
WITH presents as (
SELECT id,
daterange(entrance_date, leaving_date, '[]') * daterange('2014-09-01', '2014-11-30', '[]') as range
FROM person
WHERE daterange(entrance_date, leaving_date, '[]') && daterange('2014-09-01', '2014-11-30', '[]')
),
present_per_day as (
SELECT id,
generate_series(lower(range), upper(range), '1 day'::interval) AS date
FROM presents
),
count_per_day as (
SELECT count(*) OVER (PARTITION BY date) AS count,
date
FROM present_per_day
),
SELECT max(count) OVER (PARTITION BY date_part('year', date), date_part('month', date)) as max,
date_part('year', date),
date_part('month', date)
FROM count_per_day;
(I have to leave, I hope I'll have time to test it later)
In fact, #erwin solution is much much more easy and efficient than this one.
Related
create table link : https://drive.google.com/file/d/1EEqpW2Y8UkplfQcp_fw0j2byXAxBQXOW/view?usp=sharing
I have a query
SELECT
DATE_FORMAT(seized_date,'%Y-%m') as 'Seized Date',
sum(case when seized_remarks = 'Temporary Seized' then 1 else 0 end) AS seized,
sum(case when (DATE_FORMAT(release_date, '%Y-%m') BETWEEN '2021-01' AND '2021-07') then 1 else 0 end) AS released,
sum(case when (DATE_FORMAT(stock_return_date, '%Y-%m') BETWEEN '2021-01' AND '2021-07') then 1 else 0 end) AS stock_return
FROM mahindra
where
(DATE_FORMAT(seized_date, '%Y-%m') BETWEEN '2021-01' AND '2021-07')
GROUP BY DATE_FORMAT(seized_date,'%Y-%m')
which gives result as
Date Seized Release Stock Return
2021-01 1 0 0
2021-03 1 0 0
2021-04 1 0 0
2021-05 5 1 0
2021-06 6 0 1
2021-07 2 0 0
here i didn't get the result of february 2021. I want to get the result of all months between this two dates even if the seized_date does not exist
Looks like you need in something like
SELECT
dates.`Seized Date`,
COALESCE(SUM(mahindra.seized_remarks = 'Temporary Seized'), 0) AS seized,
COALESCE(COUNT(mahindra.release_date), 0) AS released,
COALESCE(COUNT(mahindra.stock_return_date), 0) AS stock_return
FROM ( SELECT '2021-01' `Seized Date` UNION ALL
SELECT '2021-02' UNION ALL
SELECT '2021-03' UNION ALL
SELECT '2021-04' UNION ALL
SELECT '2021-05' UNION ALL
SELECT '2021-06' UNION ALL
SELECT '2021-07' ) dates
LEFT JOIN mahindra ON DATE_FORMAT(mahindra.seized_date, '%Y-%m') = dates.`Seized Date`
GROUP BY dates.`Seized Date`
As #Akina says, if the seized_date value does not exist anywhere in your table, you cannot expect it to be present in your results at all.
You need to create a column containing all required dates and then you can do something like perform a join onto that column.
Here's an example of how I might do it.
SELECT TO_CHAR(DATEADD('MONTH', -n, (CURRENT_DATE+1)),'YYYY-MM') AS seized_date
FROM (
SELECT ROW_NUMBER() OVER () AS n
FROM mahindra LIMIT 10
)
ORDER BY seized_date DESC
This is creates the following output.
Code explanation:
The inner query is based on a window function.
The window function itself is simple. We're basically telling the computer to assign a value of 10 to n. To a human, "10" is a numeric value assigned to a specific thing or count of things (eg the temperature is 10 degrees, or I have 10 apples), but to the processor/computer, it doesn't mean much on its own. At least not in our scenario. So we simply tell the processor to count some rows ROW_NUMBER and when it finds 10 rows, that's what 10 looks like. You can use LIMIT to make this greater or fewer than 10 months.
In the outer query we just take today's date CURRENT_DATE and subtract n months from it as in (DATEADD('MONTH', -n, (CURRENT_DATE+1).
In our case, n is 10 months.
Now you have a column of dates, formatted as you per your requirements YYYY-MM.
You can write the query such that you LEFT JOIN your precessed data set to these dates on their corresponding values.
The reason why this is a good way of doing things is that you don't have to manually enter any dates or use a UNION join. You let the window function do the work for you, meaning you can go back in time as far as you need/or want very easily by changing the LIMIT value. This allows for greater efficiency in the event where you need to go back over multiple years, for example.
I'm developping a dashboard for a restaurant/bar.
I want to manipulate the date , so for example:
If they sell something at 02 january ,01h30 .
This amount should be added to the amount of the 1st January and not to the amount of the 2nd January.
So basically on the next day from 00h00 until 03h59, the amount that they sell, should be added to the previous date.
At the moment my SQL query just displays both dates but I want it grouped in 1 date. If this isn't possible with SQL, I have my dashboard in PHP, so I can eventually manipulate it in PHP if anyone could provide me that info.
My query
select CONVERT(CHAR(10), receiptdatetime, 120), datename(DW,receiptdatetime),
sum(rld.NetAmount), count(rl.ReceiptId)
from receipt r, receiptline rl, vw_ReceiptLineDetail rld
where rl.ReceiptId = r.ReceiptId and
rl.ModifiedKind != 300
and rld.ReceiptLineId = rl.ReceiptLineId and
receiptdatetime <= DATEADD(HOUR,4,DATEADD(DAY,1, '01/01/2018'))
and receiptdatetime >= DATEADD(HOUR,4,'01/01/2018')
group by CONVERT(CHAR(10), receiptdatetime, 120), datename(DW, receiptdatetime)
order by 1
So the current output is like this (shortened):
Date Amount
1 01/01/2018 100
2 02/01/2018 20
But I want it like this
Date Amount
1 01/01/2018 120
You can use this query and work with dates as you want
here is you first query that gives you that result
select CONVERT(CHAR(10), receiptdatetime, 120), datename(DW,receiptdatetime),
sum(rld.NetAmount), count(rl.ReceiptId)
from receipt r, receiptline rl, vw_ReceiptLineDetail rld
where rl.ReceiptId = r.ReceiptId and
rl.ModifiedKind != 300
and rld.ReceiptLineId = rl.ReceiptLineId and
receiptdatetime <= DATEADD(HOUR,4,DATEADD(DAY,1, '01/01/2018'))
and receiptdatetime >= DATEADD(HOUR,4,'01/01/2018')
group by CONVERT(CHAR(10), receiptdatetime, 120), datename(DW, receiptdatetime)
order by 1
you can use this result as a first select with this :
With CTE as
(
select CONVERT(CHAR(10), receiptdatetime, 120), datename(DW,receiptdatetime),
sum(rld.NetAmount), count(rl.ReceiptId)
from receipt r, receiptline rl, vw_ReceiptLineDetail rld
where rl.ReceiptId = r.ReceiptId and
rl.ModifiedKind != 300
and rld.ReceiptLineId = rl.ReceiptLineId and
receiptdatetime <= DATEADD(HOUR,4,DATEADD(DAY,1, '01/01/2018'))
and receiptdatetime >= DATEADD(HOUR,4,'01/01/2018')
group by CONVERT(CHAR(10), receiptdatetime, 120), datename(DW, receiptdatetime)
order by 1
)
so like this you have the result stored on a table called CTE
NOw i don't have the data so i will create my owne Variable table to store the result that you got from first query you can use your CTE tabale as a source instade of #Table
Declare #Table table (
id int,
dates date,
amout int
)
insert into #Table
select 1 , '2018-01-01' , 100 union
select 2 , '2018-01-02' , 20 union
select 2 , '2018-02-02' , 200 union
select 2 , '2018-01-03' , 20 union
select 2 , '2018-01-04' , 20
now to get the Amout with the result that you want here is the query to use
you do the select from CTE :
select sum(amout) as Amout from #Table
where dates between '2018-01-01' and '2018-01-04'
Result :
Amout
160
now you will use that result and union it with you table to get the ID that you want and the date that you want and i thing you should convert the last table date into nvarchar(50) so you will have this result
1- when you do the whole month
ID Date Amout
1 2018-01 160
2- when you do by timeframe
ID Date Amout
1 '2018-01-01 2018-01-14' 160
you can start by hardcoding the ID and Date as you want and union is to the result Amout that you get from the query
or you can do variables to configure the ID and date that you want to show with the Amout
thanks if you ahve any questions i'm here i have done the test on my local and it works and i hope that this is what you need :)
use the :
;With CTE as ( select (RowCount() Over (Partition by Date order by id) row_count) , date, amout
from tables and where conditions
Then on the select Add the amount that have the same Date into each others
selecting from CTE table
if you want to Add the Amounts of all day on the same month then select Only Year and Month on Date column then do the partition by over this Date
Am trying to find the min value from past 30 days, in my table there is one entry for every day, am using this query
SELECT MIN(low), date, low
FROM historical_data
WHERE name = 'bitcoin'
ORDER BY STR_TO_DATE(date,'%d-%m-%Y') DESC
LIMIT 7
But this value not returing the correct value. The structure of my table is
Table structure
And table data which is store is like this
Table data style
Now what i need is to get the minimum low value. But my query not working it give me wrong value which even did not exist in table as well.
Updates:
Here is my updated Table Structure.
enter image description here
And here is my data in this table which look like this
enter image description here
Now if you look at the data, i want to check the name of token omisego and fatch the low value from past 7 days which will be from 2017-12-25 to 2017-12-19
and in this cast the low value is 9.67, but my current query and the query suggested by my some member did not brings the right answer.
Update 2:
http://rextester.com/TDBSV28042
Here it is, basically i have more then 1400 coins and token historical data, which means that there will me more then 1400 entries for same date like 2017-12-25 but having different name, total i have more then 650000 records. so every date have many entries with different names.
To get the lowest row per group you could use following
SELECT a.*
FROM historical_data a
LEFT JOIN historical_data b ON a.name = b.name
AND a.low > b.low
WHERE b.name IS NULL
AND DATE(a.date) >= '2017-12-19' AND DATE(a.date) <= '2017-12-25'
AND a.name = 'omisego'
or
SELECT a.*
FROM historical_data a
JOIN (
SELECT name,MIN(low) low
FROM historical_data
GROUP BY name
) b USING(name,low)
WHERE DATE(a.date) >= '2017-12-19' AND DATE(a.date) <= '2017-12-25'
AND a.name = 'omisego'
DEMO
For last 30 day of 7 days or n days you could write above query as
SELECT a.*, DATE(a.`date`)
FROM historical_data2 a
LEFT JOIN historical_data2 b ON a.name = b.name
AND DATE(b.`date`) >= CURRENT_DATE() - INTERVAL 30 DAY
AND DATE(b.`date`) <= CURRENT_DATE()
AND a.low > b.low
WHERE b.name IS NULL
AND a.name = 'omisego'
AND DATE(a.`date`) >= CURRENT_DATE() - INTERVAL 30 DAY
AND DATE(a.`date`) <= CURRENT_DATE()
;
DEMO
But note it may return more than one records where low value is same, to choose 1 row among these you have specify another criteria to on different attribute
Consider grouping the same and running the clauses
SELECT name, date, MIN(low)
FROM historical_data
GROUP BY name
HAVING name = 'bitcoin'
AND STR_TO_DATE(date, '%M %d,%Y') > DATE_SUB(NOW(), INTERVAL 30 DAY);
Given the structure, the above query should get you your results.
// Try this code ..
SELECT MIN(`date`) AS date1,low
FROM historical_data
WHERE `date` BETWEEN now() - interval 1 month
AND now() ORDER by low ASC;
I have the below table:
studentid VARCHAR(12)
latetime DATETIME
attendance CHAR(1)
latetime only have weekdays.
Some of the days the students will have "Parents letter" indicated by V for attendance column.
I need to group these attendance column V by consecutive week days.
Then count these occurrences.
Each group of consecutive days are counted as 1 letter.
My SQLFIDDLE: http://sqlfiddle.com/#!2/55d5b/1
This SQLFIDDLE sample data should return
STUDENTID LETTERCOUNT
a1111 3
b2222 2
a1111 - 3 counts
-----
1. 2014-01-02
2. 2014-01-27
2. 2014-01-29 and 2014-01-30
b2222 - 2 counts
-----
1. 2014-01-02 and 2014-01-03
2. 2014-01-24, 2014-01-27 and 2014-01-28
I tried various methods from the below SO without any proper result yet:
How to GROUP BY consecutive data (date in this case)
MySQL: group by consecutive days and count groups
I can do this programatically in PHP by looping through the results and manually checking for each record + its next date. But i was trying to acheive the same with SQL.
Any help / direction towards finding a solution will be much appreciated.
This is derived from one of the answers in MySQL: group by consecutive days and count groups. I added the WITH ROLLUP option to get the letter count into the same query, and used GROUP_CONCAT to show all the dates. I made the INTERVAL conditional on the weekday, to skip over weekends; holidays aren't taken into account, though.
In my version of the fiddle I changed the latetime column to date, so I could remove all the DATE() functions from the SQL.
SELECT studentid, IFNULL(dates, '') dates, IF(dates IS NULL, lettercount, '') lettercount
FROM (
SELECT studentid, dates, COUNT(*) lettercount
FROM (
SELECT v.studentid,
GROUP_CONCAT(latetime ORDER BY latetime SEPARATOR ', ') dates
FROM
(SELECT studentid, latetime,
#start_date := IF(#last_student IS NULL OR #last_student <> studentid,
1,
IF(#last_latetime IS NULL
OR (latetime - INTERVAL IF(WEEKDAY(latetime) = 0, 3, 1) DAY) > #last_latetime, latetime, #start_date)) AS start_date,
#last_latetime := latetime,
#last_student := studentid
FROM
studentattendance, (SELECT #start_date := NULL, #last_latetime := NULL, #last_student := NULL) vars
WHERE attendance = 'V'
ORDER BY
studentid, latetime
) v
GROUP BY
v.studentid, start_date) x
GROUP BY studentid, dates WITH ROLLUP) y
WHERE studentid IS NOT NULL
ORDER BY studentid, dates
http://sqlfiddle.com/#!2/6c944/12
I have this query:
SELECT COUNT(*) as clicks, DATE_FORMAT(FROM_UNIXTIME(click_date), '%w %M %Y') as point
FROM tracking
WHERE click_date < $end_date AND click_date > $start_date
GROUP BY DAY(FROM_UNIXTIME(click_date))
Where $start_date is two weeks ago and $end_date is today's date.
I am trying find all clicks made each day for a particular date range. I also want to include days where there has been no clicks. Since naturally there isn't an entry for these in my database I need to include them some how, how can I best do this whilst showing all dates from start date to end date. This what I currently have, lots of gaps for this two week date range.
Array
(
[0] => Array
(
[clicks] => 17
[point] => 0 February 2011
)
[1] => Array
(
[clicks] => 3
[point] => 1 February 2011
)
[2] => Array
(
[clicks] => 14
[point] => 5 February 2011
)
[3] => Array
(
[clicks] => 1
[point] => 1 February 2011
)
[4] => Array
(
[clicks] => 8
[point] => 2 February 2011
)
)
Can this possibly be done via a pure SQL query or do I have to use some php logic?
Btw, why do I have 0 February 2011 as my first date! Hmm, I also seem to have duplicate dates, that shouldn't happen, maybe my GROUP BY isn't working correctly?
Thanks all for any help.
Can this possibly be done via a pure SQL query or do I have to use some php logic?
Yes, it is better to create a Numbers table (single column N) that contains nothing but the numbers 0 to 999. It can be used for many things, not least a query like the below:
SELECT COUNT(t.click_date) as clicks,
DATE_FORMAT(adddate($start_date, interval N day), '%d %M %Y') as point
FROM Numbers
LEFT JOIN tracking t
ON t.click_date >= adddate($start_date, interval N day)
and t.click_date < adddate($start_date, interval (N+1) day)
WHERE N between 0 and datediff($start_date, $end_date)
GROUP BY N
Btw, why do I have 0 February 2011 as my first date
You're using the wrong format. It's UPPER case W not lower for day-of-week, so '%W %M %Y' or '%d %M %Y' for day-of-month.
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-format
maybe my GROUP BY isn't working correctly?
You are using GROUP BY DAY(FROM_UNIXTIME(click_date)) note "day" not weekday, but you are displaying (or trying to) "%W" (weekday) - pick one, don't mix them.
EDIT: If you prefer not to materialize (create as a real table) a Numbers sequence table, you can construct one on the fly. It won't be pretty.
Note: N1, N2 and N3 below combine to give a possible range of 0-999
SELECT COUNT(t.click_date) as clicks,
DATE_FORMAT(adddate($start_date, interval N day), '%d %M %Y') as point
FROM (
select N1 * 100 + N2 * 10 + N3 as N
from (
select 0 N1 union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all
select 8 union all select 9) N1
cross join (
select 0 N2 union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all
select 8 union all select 9) N2
cross join (
select 0 N3 union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all
select 8 union all select 9) N3
) Numbers
LEFT JOIN tracking t
ON t.click_date >= adddate($start_date, interval N day)
and t.click_date < adddate($start_date, interval (N+1) day)
WHERE N between 0 and datediff($start_date, $end_date)
GROUP BY N
EDIT #2: A straight Dates table
Put this in a new window in phpMyAdmin or run it as a batch. It creates a table named Dates, with every single date from day 1900-01-01 (or change in the script) to 2300-01-01 (or change).
DROP PROCEDURE IF EXISTS FillDateTable;
delimiter //
CREATE PROCEDURE FillDateTable()
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
drop table if exists datetable;
create table datetable (thedate datetime primary key, isweekday smallint);
SET #x := date('1900-01-01');
REPEAT
insert into datetable (thedate, isweekday) SELECT #x, case when dayofweek(#x) in (1,7) then 0 else 1 end;
SET #x := date_add(#x, interval 1 day);
UNTIL #x > date('2300-01-01') END REPEAT;
END//
delimiter ;
CALL FillDateTable;
With such a utility table, your query can be just
SELECT COUNT(t.click_date) as clicks,
DATE_FORMAT(thedate, '%d %M %Y') as point
FROM Dates
LEFT JOIN tracking t
ON t.click_date >= thedate
and t.click_date < adddate(thedate, interval 1 day)
WHERE thedate between $start_date and $end_date
GROUP BY thedate
In my opinion you are better off doing this type of logic in your code. But if you wanted to do it in pure SQL you could construct a query to give you the results of all the days between one day and the next ... either by inserting into a temp table or an in memory table... then left join that into your results so that you get all the days regardless of if there were results for that day.
I'd stick to PHP logic, looping between the lowest date and the highest date, and incrementing one day at a time.
You could probably do it in SQL with some fancy joins, but I won't even start to consider that nastiness!
BTW, %w is the day of the week, starting 0=Sunday. You probably wanted %d.