How to obtain the "largest day" in a week? - sql

How to get a data when the "largest day" of the week does not exist?
For example, if Friday does not exist in my database for a particular week (assuming that Saturday and Sundays are not in my database), I would still like to be able to get the data from Thursday. If Both Friday and Thursday do not exist, I would like to get the data from Wednesday etc.
This is what I currently have (this code allows me to obtain the last day of the month from my database):
/**Select Last Day Of Month**/
SELECT * FROM [mytable]
WHERE [date] IN (SELECT MAX([date])
FROM [mytable]
GROUP BY MONTH([Date]), YEAR([Date]))
I also understand that you can use the DATEPART function to get all datas from a particular day (i.e. Friday), the thing I'm not sure about is how to get a thursday if Friday doesn't exist. I'm looking to grab all the data that has the corresponding features, not one particular week. Thanks!
As a clearer example:
Say I have four input data in my database->
1. 2016/8/19(Fri), red
2. 2016/8/18(Thu), blue
3. 2016/8/11(Thu), red
4. 2016/8/10(Wed), red
after the query is executed, I would like to have:
1. 2016/8/19(Fri), red
3. 2016/8/11(Thu), red
They are selected because the two data are the corresponding "largest" data in that week.

See if the following query helps. It displays the last day of every week in the given input data.
declare #table table(
date datetime
)
insert into #table
values
('08/01/2016'),
('08/02/2016'),
('08/03/2016'),
('08/04/2016'),
('08/05/2016'),
('08/06/2016'),
('08/07/2016'),
('08/08/2016'),
('08/09/2016'),
('08/10/2016'),
('08/11/2016'),
('08/12/2016'),
('08/13/2016'),
('08/14/2016'),
('08/15/2016'),
('08/16/2016'),
('08/17/2016'),
('08/18/2016'),
('08/19/2016'),
('08/20/2016'),
('08/21/2016'),
('08/22/2016'),
('08/23/2016')
;with cte as
(
select date, row_number() over(partition by datepart(year,date),datepart(week,date) order by date desc) as rn from #table
)
select date,datename(weekday,date) from cte
where rn = 1

You can extract various weekparts and construct a suitable ROW_NUMBER() expression.
E.g. the following assigns the row number 1 to the latest day within each week:
ROW_NUMBER() OVER (
PARTITION BY DATEPART(year,[date]),DATEPART(week,[date])
ORDER BY DATEPART(weekday,[date]) DESC) as rn

Related

Grouping by last day of each month—inefficient running

I am attempting to pull month end balances from all accounts a customer has for every month. Here is what I've written. This runs correctly and gives me what I want—but it also runs extremely slowly. How would you recommend speeding it up?
SELECT DISTINCT
[AccountNo]
,SourceDate
,[AccountType]
,[AccountSub]
,[Balance]
FROM [DW].[dbo].[dwFactAccount] AS fact
WHERE SourceDate IN (
SELECT MAX(SOURCEDATE)
FROM [DW].[dbo].[dwFactAccount]
GROUP BY MONTH(SOURCEDATE),
YEAR (SOURCEDATE)
)
ORDER BY SourceDate DESC
I'd try a window function:
SELECT * FROM (
SELECT
[AccountNo]
,[SourceDate]
,[AccountType]
,[AccountSub]
,[Balance]
,ROW_NUMBER() OVER(
PARTITION BY accountno, EOMONTH(sourcedate)
ORDER BY sourcedate DESC
) as rn
FROM [DW].[dbo].[dwFactAccount]
)x
WHERE x.rn = 1
The row number will establish an incrementing counter in order of sourcedate descending. The counter will restart from 1 when the month in sourcedate changes (or the account number changes) thanks to the EOMONTH function quantising any date in a given month to be the last date of the month (2020-03-9 12:34:56 becomes 2020-03-31, as do all other datetimes in March). Any similar tactic to quantise to a fixed date in the month would also work such as using YEAR(sourcedate), MONTH(sourcedate)
You need to build a dimension table for the date with Date as PK, and your SourceDate in the fact table ref. that date dimension table.
Date dimension table can have month, year, week, is_weekend, is_holiday, etc. columns. You join your fact table with the date dimension table and you can group data using any columns in date table you want.
Your absolute first step should be to view the execution plan for the query and determine why the query is slow.
The following explains how to see a graphical execution plan:
Display an Actual Execution Plan
The steps to interpreting the plan and optimizing the query are too much for an SO answer, but you should be able to find some good articles on the topic by Googling. You could also post the plan in an edit to your question and get some real feedback on what steps to take to improve query performance.

Calculate clients per week based on start date and end date in MS Access

In a MS Access DB I have information about clients' arrival and departure dates. Based on this I would like to calculate the number of clients that will be visiting per week.
Consider the example below. Peter arrives in the first week of January and leaves on the third week (weeks start on Sunday). Mary on the other hand arrives and leaves in the first week.
I would like the output to be as shown below, with the week number in the first column and the total number of guests in the second.
What is the best way of achieving this please?
My solution with german date format, test with ORACLE. I build some calendar table (tw_test_week) for every week to join it.
CREATE TABLE tw_test_client (
client VARCHAR2(10),
arrival DATE,
departure DATE
);
INSERT INTO tw_test_client VALUES
( 'Peter', to_date('01.01.2018','DD.MM.YYYY'), to_date('11.01.2018','DD.MM.YYYY'));
INSERT INTO tw_test_client VALUES
( 'Mary', to_date('01.01.2018','DD.MM.YYYY'), to_date('01.02.2018','DD.MM.YYYY'));
CREATE TABLE tw_test_week (
weekid INT,
started DATE,
ended DATE
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('01.01.2018','DD.MM.YYYY'),'WW'),
to_date('01.01.2018','DD.MM.YYYY'),
to_date('07.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('08.01.2018','DD.MM.YYYY'),'WW'),
to_date('08.01.2018','DD.MM.YYYY'),
to_date('14.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('15.01.2018','DD.MM.YYYY'),'WW'),
to_date('15.01.2018','DD.MM.YYYY'),
to_date('21.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('22.01.2018','DD.MM.YYYY'),'WW'),
to_date('22.01.2018','DD.MM.YYYY'),
to_date('28.01.2018','DD.MM.YYYY')
);
INSERT INTO tw_test_week VALUES
( to_char(to_date('29.01.2018','DD.MM.YYYY'),'WW'),
to_date('29.01.2018','DD.MM.YYYY'),
to_date('04.02.2018','DD.MM.YYYY')
);
SELECT w.weekid, COUNT(*)
FROM tw_test_week w
JOIN tw_test_client c
ON w.started BETWEEN c.arrival and c.departure
GROUP BY w.weekid
ORDER BY w.weekid;
Result
WEEKID COUNT
1 2
2 2
3 1
4 1
5 1
Create a table containing the numbers 1 to 53. This table in my query is called WeekNumTable and contains a single field with each week number listed.
SELECT WeekNum
, COUNT(WeekNum) AS TotalClients
FROM WeekNumTable INNER JOIN ClientTable ON
WeekNumTable.WeekNum>=DatePart("ww",ClientTable.Arrival-Weekday(ClientTable.Arrival,1)+7) AND
WeekNumTable.WeekNum<=DatePart("ww",ClientTable.Departure-Weekday(ClientTable.Departure,1)+7)
GROUP BY WeekNum
Your example confused me a little as you list week 3 as having 1 client, while the data table doesn't.
Edit: The one problem that I can see with this is that if you do want to look at the next 100 weeks you'll need something to separate the years otherwise week 1 from both years will be lumped together.
You can use a series of queries to obtain this.
First, create a query named Ten:
SELECT DISTINCT Abs([id] Mod 10) AS N
FROM MSysObjects;
Then you can create a query, ClientDays, that lists all your dates between first arrival and latest departure:
SELECT DISTINCT
[Ten_0].[N]+[Ten_1].[N]*10+[Ten_2].[N]*100 AS Id,
DateAdd("d",[Ten_0].[N]+[Ten_1].[N]*10+[Ten_2].[N]*100,[StartDate]) AS [Date]
FROM
Ten AS Ten_0,
Ten AS Ten_1,
Ten AS Ten_2,
(Select
Min([Arrival]) As StartDate,
DateDiff("d", Min([Arrival]), Max([Departure])) As Days
From
ClientDates) AS T
WHERE
((([Ten_0].[N]+[Ten_1].[N]*10+[Ten_2].[N]*100)<=[Days])
AND
((Ten_0.N)<=[Days]\1)
AND
((Ten_1.N)<=[Days]\10)
AND
((Ten_2.N)<=[Days]\100));
Use this in yet a query, ClientWeeks, to find the week numbers:
SELECT
Year([Date]) AS [Year],
DatePart("ww",[Date]) AS Week,
ClientDates.Client
FROM
ClientDays,
ClientDates
WHERE
ClientDays.Date Between [Arrival] And [Departure]
GROUP BY
Year([Date]),
DatePart("ww",[Date]),
ClientDates.Client;
Finally, count the clients:
SELECT
ClientWeeks.Year,
ClientWeeks.Week,
Count(ClientWeeks.Client) AS TotalClients
FROM
ClientWeeks
GROUP BY
ClientWeeks.Year,
ClientWeeks.Week;
Please note, that you will have trouble counting around New Year as you intend to use an inconsistent week numbering method.
The only week number that is unambiguous is the ISO-8601 system of yyyy-ww because the first and/or last week will cross the calendar year boundaries.
Leave a note if you wish to implement this as it cannot be done with native VBA functions; custom functions must be used, but I don't wish to post them here if you will not use them.

How to use window functions to get meterics for today, last 7 days, last 30 days for each value of the date?

My problem seems simple on paper:
For a given date, give me active users for that given date, active users in given_Date()-7, active users in a given_Date()-30
i.e. sample data.
"timestamp" "user_public_id"
"23-Sep-15" "805a47023fa611e58ebb22000b680490"
"28-Sep-15" "d842b5bc5b1711e5a84322000b680490"
"01-Oct-15" "ac6b5f70b95911e0ac5312313d06dad5"
"21-Oct-15" "8c3e91e2749f11e296bb12313d086540"
"29-Nov-15" "b144298810ee11e4a3091231390eb251"
for 01-10 the count for today would be 1, last_7_days would be 3, last_30_days would be 3+n (where n would be the count of the user_ids that fall in dates that precede Oct 1st in a 30 day window)
I am on redshift amazon. Can somebody provide a sample sql to help me get started?
the outputshould look like this:
"timestamp" "users_today", "users_last_7_days", "users_30_days"
"01-Oct-15" 1 3 (3+n)
I know asking for help/incomplete solutions are frowned upon, but this is not getting any other attention so I thought I would do my bit.
I have been pulling my hair out trying to nut this one out, alas, I am a beginner and something is not clicking for me. Perhaps yourself or others will be able to drastically improve my answer, but I think I am on the right track.
SELECT replace(convert(varchar, [timestamp], 111), '/','-') AS [timestamp], -- to get date in same format as you require
(SELECT COUNT([TIMESTAMP]) FROM #SIMPLE WHERE ([TIMESTAMP]) = ([timestamp])) AS users_today,
(SELECT COUNT([TIMESTAMP]) FROM #SIMPLE WHERE [TIMESTAMP] BETWEEN DATEADD(DY,-7,[TIMESTAMP]) AND [TIMESTAMP]) AS users_last_7_days ,
(SELECT COUNT([TIMESTAMP]) FROM #SIMPLE WHERE [TIMESTAMP] BETWEEN DATEADD(DY,-30,[TIMESTAMP]) AND [timestamp]) AS users_last_30_days
FROM #SIMPLE
GROUP BY [timestamp]
Starting with this:
CREATE TABLE #SIMPLE (
[timestamp] datetime, user_public_id varchar(32)
)
INSERT INTO #SIMPLE
VALUES('23-Sep-15','805a47023fa611e58ebb22000b680490'),
('28-Sep-15','d842b5bc5b1711e5a84322000b680490'),
('01-Oct-15','ac6b5f70b95911e0ac5312313d06dad5'),
('21-Oct-15','8c3e91e2749f11e296bb12313d086540'),
('29-Nov-15','b144298810ee11e4a3091231390eb251')
The problem I am having is that each row contains the same counts, despite my grouping by [timestamp].
Step 1-- Create a table which has daily counts.
create temp table daily_mobile_Sessions as
select "timestamp" ,
count(user_public_id) over (partition by "timestamp" ) as "today"
from mobile_sessions
group by 1, mobile_sessions.user_public_id
order by 1 DESC
Step 2 -- From the table above. We create yet another table which can use the "today" field, and we apply the window function to Sum the counts.
select "timestamp", today,
sum(today) over (order by "timestamp" rows between 6 PRECEDING and CURRENT ROW) as "last_7days",
sum(today) over (order by "timestamp" rows between 29 PRECEDING and CURRENT ROW) as "last_30days"
from daily_mobile_Sessions group by "timestamp" , 2 order by 1 desc

Display a rolling 12 weeks chart in SSRS report

I am calling the data query in ssrs like this:
SELECT * FROM [DATABASE].[dbo].[mytable]
So, the current week is the last week from the query (e.g. 3/31 - 4/4) and each number represents the week before until we have reached the 12 weeks prior to this week and display in a point chart.
How can I accomplish grouping all the visits for all locations by weeks and adding it to the chart?
I suggest updating your SQL query to Group by a descending Dense_Rank of DatePart(Week,ARRIVED_DATE). In this example, I have one column for Visits because I couldn't tell which columns you were using to get your Visit count:
-- load some test data
if object_id('tempdb..#MyTable') is not null
drop table #MyTable
create table #MyTable(ARRIVED_DATE datetime,Visits int)
while (select count(*) from #MyTable) < 1000
begin
insert into #MyTable values
(dateadd(day,round(rand()*100,0),'2014-01-01'),round(rand()*1000,0))
end
-- Sum Visits by WeekNumber relative to today's WeekNumber
select
dense_rank() over(order by datepart(week,ARRIVED_DATE) desc) [Week],
sum(Visits) Visits
from #MyTable
where datepart(week,ARRIVED_DATE) >= datepart(week,getdate()) - 11
group by datepart(week,ARRIVED_DATE)
order by datepart(week,ARRIVED_DATE)
Let me know if I can provide any more detail to help you out.
You are going to want to do the grouping of the visits within SQL. You should be able to add a calculated column to your table which is something like WorkWeek and it should be calculated on the days difference from a certain day such as Sunday. This column will then by your X value rather than the date field you were using.
Here is a good article that goes into first day of week: First Day of Week

MySQL to get the count of rows that fall on a date for each day of a month

I have a table that contains a list of community events with columns for the days the event starts and ends. If the end date is 0 then the event occurs only on the start day. I have a query that returns the number of events happening on any given day:
SELECT COUNT(*) FROM p_community e WHERE
(TO_DAYS(e.date_ends)=0 AND DATE(e.date_starts)=DATE('2009-05-13')) OR
(DATE('2009-05-13')>=DATE(e.date_starts) AND DATE('2009-05-13')<=DATE(e.date_ends))
I just sub in any date I want to test for "2009-05-13".
I need to be be able to fetch this data for every day in an entire month. I could just run the query against each day one at a time, but I'd rather run one query that can give me the entire month at once. Does anyone have any suggestions on how I might do that?
And no, I can't use a stored procedure.
Try:
SELECT COUNT(*), DATE(date) FROM table WHERE DATE(dtCreatedAt) >= DATE('2009-03-01') AND DATE(dtCreatedAt) <= DATE('2009-03-10') GROUP BY DATE(date);
This would get the amount for each day in may 2009.
UPDATED: Now works on a range of dates spanning months/years.
Unfortunately, MySQL lacks a way to generate a rowset of given number of rows.
You can create a helper table:
CREATE TABLE t_day (day INT NOT NULL PRIMARY KEY)
INSERT
INTO t_day (day)
VALUES (1),
(2),
…,
(31)
and use it in a JOIN:
SELECT day, COUNT(*)
FROM t_day
JOIN p_community e
ON day BETWEEN DATE(e.start) AND IF(DATE(e.end), DATE(e.end), DATE(e.start))
GROUP BY
day
Or you may use an ugly subquery:
SELECT day, COUNT(*)
FROM (
SELECT 1 AS day
UNION ALL
SELECT 2 AS day
…
UNION ALL
SELECT 31 AS day
) t_day
JOIN p_community e
ON day BETWEEN DATE(e.start) AND IF(DATE(e.end), DATE(e.end), DATE(e.start))
GROUP BY
day