Get series of counts with conditions using ActiveRecord - sql

I'm displaying a graph of Subscription counts that may have been soft_destroyed_at at some point.
To do this I run a query for each month which is of course not as good as one big honking query but my SQL skills fail me once again.
Here's how I do it in Ruby:
months = (0..12).map { |i| i.months.ago.end_of_month }
stats = Hash[months.map do |eom|
[
eom.beginning_of_month.to_date,
Subscription.where(
'created_at < ?' \
'AND (soft_destroyed_at IS NULL ' \
' OR soft_destroyed_at > ?) ' \
'AND (suspended_at IS NULL ' \
' OR suspended_at > ?)',
eom, eom, eom
).count
]
end]
# => { 2018-04-01 => 10, 2018-03-01 => 15, ... }
How could I write this as one query using ActiveRecord – or if necessary using raw SQL>
The database is Postgres 10.2, the app is Rails 5.2.
Thanks!

You can use this query (I used 12 month in 2017; just change as you wish). This assumes a Postgresql DB, as you said in a comment:
query =
"select months.month, count(created_at) "\
"from "\
"(select DATE '2017-01-01'+(interval '1' month * generate_series(0,11)) as month, "\
"DATE '2017-02-01'+(interval '1' month * generate_series(0,11)) as next) months "\
"outer join subscriptions on "\
"created_at < month and "\
"(soft_destroyed_at IS NULL or soft_destroyed_at >= next) and "\
"(suspended_at IS NULL OR suspended_at >= next) "\
"group by month "\
"order by month"
results = ActiveRecord::Base.connection.execute(query)
The first subquery (the select inside from) generates this:
month next
"2017-01-01 00:00:00";"2017-02-01 00:00:00"
"2017-02-01 00:00:00";"2017-03-01 00:00:00"
"2017-03-01 00:00:00";"2017-04-01 00:00:00"
"2017-04-01 00:00:00";"2017-05-01 00:00:00"
"2017-05-01 00:00:00";"2017-06-01 00:00:00"
"2017-06-01 00:00:00";"2017-07-01 00:00:00"
"2017-07-01 00:00:00";"2017-08-01 00:00:00"
"2017-08-01 00:00:00";"2017-09-01 00:00:00"
"2017-09-01 00:00:00";"2017-10-01 00:00:00"
"2017-10-01 00:00:00";"2017-11-01 00:00:00"
"2017-11-01 00:00:00";"2017-12-01 00:00:00"
"2017-12-01 00:00:00";"2018-01-01 00:00:00"
Next is only used to make it easier to check if the subscription was active at least until next month (destroyed or suspended are >= next (which guarantees the subscriber was active during current month).

Related

Case Statements - How to subtract n years from specific data/time field in Redshift

I Need your help regarding this case statement that has be stumped.
I have a field called "activity_end_date_time". This field is a date/time field i am trying to write a simple case statement to say
If there is activity in the last 3 years(from todays data) then "3 years"
If there is activity more than last 3 years(from todays data) then "More 3 years"
If there is no activity i.e. null then "Null"
My thinking is
Todays date = 26/01/2023- 1095 days (3 years) = 27/01/2020
anything prior to 27/01/2020 should be "More than 3 years"
However i have examples where Last_Activity_End_Date is "2018-12-01", however my case statement is returning "3 Years"
This is my case statement
case
when Last_Activity_End_Date < Dateadd(year,3,Last_Activity_End_Date ) then '3 Years'
when Last_Activity_End_Date >= Dateadd(year,+3,Last_Activity_End_Date ) then 'More than 3 Years'
when Last_Activity_End_Date IS null THEN 'NULL'
end as "Last_Activity_Identifer"
Looking forward to your help
Because Amazon Redshift is based on PostgreSQL, you can try to use PostgreSQL function age in next way:
select
case
when age(Last_Activity_End_Date) < '3 year' then '3 Years'
when age(Last_Activity_End_Date) >= '3 year' then 'More than 3 Years'
end as "Last_Activity_Identifer";
test it online: https://sqlize.online/s/lt
Your logic is comparing the column to itself. You need to compare to 3 year before today's date. Using your structure and comparison operators:
case
when getdate() < Dateadd(year,3,Last_Activity_End_Date ) then '3 Years'
when getdate() >= Dateadd(year,3,Last_Activity_End_Date ) then 'More than 3 Years'
when Last_Activity_End_Date IS null THEN 'NULL'
end as "Last_Activity_Identifer"
This is untested but should work and shows the logic needed for the comparision.

How to order by the same filed twice by condition

I have this model :
public class Post {
private Long expiryDate; // A timestamp
}
And repository as :
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
Page<Post> findAllByOrderByExpiryDateDesc(Pageable pageable);
}
What I want to do:
When the expiry date has passed (post expired, current date greater than expiry date) => orderBy desc
Else, When the expiry date not yet passed (post not expired) => orderBy asc
Example: if I have the expiry date list: Monday, Tuesday, Wednesday, Thursday and Friday
and that today is Wednesday (Wednesday not yet expired).
Wanted result:
Wednesday
Thursday
Friday
Tuesday
Monday
Someone got any solution please?
You could try the following query:
SELECT p FROM Post p
ORDER BY CASE
WHEN p.expiryDate > CURRENT_TIMESTAMP THEN p.expiryDate
ELSE -p.expiryDate
END ASC
(you might need some casting to make it work, but since you didn't mention the RDBMS you're using, you'll need to check it out yourself)
I found the solution (with the help of #crizzis),
This Query work perfectly :
#Query(value = "from Post as p ORDER BY " +
"CASE WHEN p. expiryDate > :currentTime THEN -p.expiryDate ELSE null END desc," +
"CASE WHEN p. expiryDate < :currentTime THEN -p.expiryDate ELSE null END asc")
Page<Post> findAll(Long currentTime, Pageable pageable);
For native query (h2 database example)
SELECT * FROM Post p
ORDER BY
CASE WHEN p.expiryDate > currentTime THEN -p.expiryDate END desc,
CASE WHEN p.expiryDate < currentTime THEN -p.expiryDate END asc;

Access Date Query Problem for Today's Date

I have a set of data that includes a field called ReleaseDate, type of Date/Time, that gets written using NOW() at the time a record is added.
I can open the table and filter for today's date and get a result of 152 records.
I can run this following query, enter today's date at the prompts, and also get a result of 152 records.
SELECT ProductData.ReleaseDate, ProductData.Shift, ProductData.ExtrusionLine, ProductData.RollDensity
FROM ProductData
WHERE (((ProductData.ReleaseDate) Between [Please Enter Start Date mm/dd/yyyy] & " " & #12/30/1899# And [Please Enter End Date mm/dd/yyyy] & " " & #12/30/1899 23:59:59#));
If I run the following query no records are retrieved. I cannot figure out why.
SELECT ProductData.ReleaseDate, ProductData.Shift, ProductData.ExtrusionLine, ProductData.RollDensity
FROM ProductData
WHERE (((ProductData.ReleaseDate)=Date()));
From what I have read Date() as a criteria should return any records with today's date. Prompting for the date is not an option.
In addition to Gutav's answer, better use:
WHERE ProductData.ReleaseDate >= Date() AND ProductData.ReleaseDate < DateAdd("d", 1, Date())
as this can use an index on ProductData.ReleaseDate (faster). See my answer on Unable to use today's date as a criteria in a query
It's because ReleaseDate contains a time part (from Now()). You can strip that:
WHERE Fix(ProductData.ReleaseDate) = Date();

How to take current date from order date to get the prospective days aging number

UPDATE:
I figured it out, sorry for the lack of details prior. I had to convert the date so it was showing 04/12/2017 instead of the original 20170412 and then break it down per day.
SELECT
PHPICK00.PHORDT "Days Aging",
COUNT(DISTINCT(PHPICK00.PHPKTN)) "Total Orders"
FROM
PHPICK00
WHERE
PHPICK00.PHORDT>= 0
GROUP BY
PHPICK00.PHORDT SELECT COUNT(PHPCTL) "Orders",
CASE When
SUBSTR(DIGITS(PHSTDT),6,2)||'/'||
SUBSTR(DIGITS(PHSTDT),8,2)||'/'||
SUBSTR(DIGITS(PHSTDT),2,4) = current date -1 days then '1 Day'
when
SUBSTR(DIGITS(PHSTDT),6,2)||'/'||
SUBSTR(DIGITS(PHSTDT),8,2)||'/'||
SUBSTR(DIGITS(PHSTDT),2,4) = current date -2 days then '2 Day'
when
SUBSTR(DIGITS(PHSTDT),6,2)||'/'||
SUBSTR(DIGITS(PHSTDT),8,2)||'/'||
SUBSTR(DIGITS(PHSTDT),2,4) = current date -3 days then '3 Day'
when
SUBSTR(DIGITS(PHSTDT),6,2)||'/'||
SUBSTR(DIGITS(PHSTDT),8,2)||'/'||
SUBSTR(DIGITS(PHSTDT),2,4) = current date -3 days then '4+ Days'
else 'Ship Start Date Not Yet Met' END AS "Days Aging"
FROM PHPICK00
WHERE PHPSTF < '90' AND PHSTDT > 0
I am trying to have the return statement show the days aging per order. All I am able to retrieve is date as the generic form "20170412", whereas, I am wanting it to output "1", "2", "3" etc.
I'm very new to SQL! Hope this is an easy fix.
SELECT
PHPICK00.PHORDT "Order Date",
COUNT(DISTINCT(PHPICK00.PHPKTN)) "Total Orders"
FROM PHPICK00
WHERE PHPICK00.PHORDT = PHPICK00.PHORDT - GETDATE()
GROUP BY PHPICK00.PHORDT
ORDER BY PHPICK00.PHORDT DESC
I think you want this -- kinda hard to tell tho. Might want to be clearer in your quesiton.
DATEDIFF(day,PHPICK00.PHORDT,GETDATE())

how to calculate time difference in rails

I have a field that is a timestamp. I want to calculate the time difference between that timestamp and the current time and show the time as something humanly readable like
2 days remaining #don't show hours when > 1 day is remaining
once less than 1 day is remaining I'll have a javascript countdown ticker.
I've built the dotiw library to do exactly this: http://github.com/radar/dotiw.
This is based off the distance_of_time_in_words method in Rails which is not quite accurate enough, and so I've made it more accurate with dotiw.
Try this:
if end_date < Time.now # ended already
return 'Ended'
elsif end_date > (Time.now + 1.day) # more than 1 day away
diff_in_days = ((end_date - Time.now).to_i / 1.day)
days_string = diff_in_days.to_s
days_string += (diff_in_days > 1) ? ' Days' : ' Day'
return days_string
else # ending today
diff_in_HMS = Time.at(end_date - Time.now).gmtime.strftime('%R:%S')
return diff_in_HMS
end
It prints "X Days" if end_date is > 1 day away, HH:MM:SS if ending today, and "Ended" if end_date was in the past.