Convert an SQL query with row_number to AR code - postgresql

I have somewhat complex SQL query that I need to convert to nice and clean AR code, and I'm having some troubles with it.
Here's the query:
SELECT a.*
FROM
fixed_assets a
INNER JOIN
(
SELECT e.id, e.fixed_asset_id
FROM
fixed_asset_book_entries e
WHERE e.book_id = %SOME_VALUE_1%
) e_mod
ON e_mod.fixed_asset_id = a.id
INNER JOIN
(
SELECT s.fixed_asset_book_entry_id,
s.status,
ROW_NUMBER() OVER (PARTITION BY s.fixed_asset_book_entry_id ORDER BY s.created_at DESC) AS rn
FROM
status_changes s
WHERE s.created_at < %SOME_VALUE_2%
) s_mod
ON s_mod.fixed_asset_book_entry_id = e_mod.id AND s_mod.rn = 1 AND s_mod.status <> 'inactive'
ORDER BY a.id;
So, the point of it all is to extract such fixed_assets rows, that have related fixed_asset_book_entries with certain book_id, and it's last status_change before certain date has any status except inactive.
What I want to end up with is a class-level (scope?) method FixedAsset.active_within_book_on_date(book_id, date), that will return FixedAsset objects, that comply with restrictions I've explained above. I'm familiar with joins method, but I'm not sure how to handle row_number function except passing raw SQL to joins call.

I think the best you can do is something like the following. In lib/sql_template.rb:
class SqlTemplate
attr_reader :sql
# Load the file and process the ERB
# Call it like this:
# sql = SqlTemplate.new(filename, binding)
def initialize(filename, the_binding)
raw_code = File.read(File.join(Rails.root, 'lib/sql', filename))
template = ERB.new(raw_code)
#sql = template.result(the_binding)
end
end
Then define your raw SQL in lib/sql/active_within_book_on_date.sql. Which would then allow you to do this:
class FixedAsset
def self.active_within_book_on_date(book_id, date)
template = SqlTemplate.new('active_within_book_on_date.sql', binding)
self.find_by_sql(template.sql)
end
end
Your SQL file would look like this:
SELECT a.*
FROM
fixed_assets a
INNER JOIN
(
SELECT e.id, e.fixed_asset_id
FROM
fixed_asset_book_entries e
WHERE e.book_id = <%=book_id%>
) e_mod
ON e_mod.fixed_asset_id = a.id
INNER JOIN
(
SELECT s.fixed_asset_book_entry_id,
s.status,
ROW_NUMBER() OVER (PARTITION BY s.fixed_asset_book_entry_id ORDER BY s.created_at DESC) AS rn
FROM
status_changes s
WHERE s.created_at < '<%=date%>'
) s_mod
ON s_mod.fixed_asset_book_entry_id = e_mod.id AND s_mod.rn = 1 AND s_mod.status <> 'inactive'
ORDER BY a.id;
That's probably as 'nice and clean' as you can get.

Related

Strange Behaviour on Postgresql query

We created a view in Postgres and I am getting strange result.
View Name: event_puchase_product_overview
When I try to get records with *, I get the correct result. but when I try to get specific fields, I get wrong values.
I hope the screens attached here can explain the problem well.
select *
from event_purchase_product_overview
where id = 15065;
select id, departure_id
from event_puchase_product_overview
where id = 15065;
VIEW definition:
CREATE OR REPLACE VIEW public.event_puchase_product_overview AS
SELECT row_number() OVER () AS id,
e.id AS departure_id,
e.type AS event_type,
e.name,
p.id AS product_id,
pc.name AS product_type,
product_date.attribute AS option,
p.upcomming_date AS supply_date,
pr.date_end AS bid_deadline,
CASE
WHEN (pt.categ_id IN ( SELECT unnest(tt.category_ids) AS unnest
FROM ( SELECT string_to_array(btrim(ir_config_parameter.value, '[]'::text), ', '::text)::integer[] AS category_ids
FROM ir_config_parameter
WHERE ir_config_parameter.key::text = 'trip_product_flight.product_category_hotel'::text) tt)) THEN e.maximum_rooms
WHEN (pt.categ_id IN ( SELECT unnest(tt.category_ids) AS unnest
FROM ( SELECT string_to_array(btrim(ir_config_parameter.value, '[]'::text), ', '::text)::integer[] AS category_ids
FROM ir_config_parameter
WHERE ir_config_parameter.key::text = 'trip_product_flight.product_category_flight'::text) tt)) THEN e.maximum_seats
WHEN (pt.categ_id IN ( SELECT unnest(tt.category_ids) AS unnest
FROM ( SELECT string_to_array(btrim(ir_config_parameter.value, '[]'::text), ', '::text)::integer[] AS category_ids
FROM ir_config_parameter
WHERE ir_config_parameter.key::text = 'trip_product_flight.product_category_bike'::text) tt)) THEN e.maximum_bikes
ELSE e.maximum_seats
END AS departure_qty,
CASE
WHEN now()::date > pr.date_end AND po.state::text = 'draft'::text THEN true
ELSE false
END AS is_deadline,
pl.product_qty::integer AS purchased_qty,
pl.comments,
pl.price_unit AS unit_price,
rp.id AS supplier,
po.id AS po_ref,
po.state AS po_state,
po.date_order AS po_date,
po.user_id AS operator,
pl.po_state_line AS line_status
FROM event_event e
LEFT JOIN product_product p ON p.related_departure = e.id
LEFT JOIN product_template pt ON pt.id = p.product_tmpl_id
LEFT JOIN product_category pc ON pc.id = pt.categ_id
LEFT JOIN purchase_order_line pl ON pl.product_id = p.id
LEFT JOIN purchase_order po ON po.id = pl.order_id
LEFT JOIN purchase_order_purchase_requisition_rel prr ON prr.purchase_order_id = po.id
LEFT JOIN purchase_requisition pr ON pr.id = prr.purchase_requisition_id
LEFT JOIN res_partner rp ON rp.id = po.partner_id
LEFT JOIN ( SELECT p_1.id AS product_id,
pav.name AS attribute
FROM product_product p_1
LEFT JOIN product_attribute_value_product_product_rel pa ON pa.prod_id = p_1.id
LEFT JOIN product_attribute_value pav ON pav.id = pa.att_id
LEFT JOIN product_attribute pat ON pat.id = pav.attribute_id
WHERE pat.name::text <> ALL (ARRAY['Date'::character varying, 'Departure'::character varying]::text[])) product_date ON product_date.product_id = p.id
WHERE (p.id IN ( SELECT DISTINCT mrp_bom_line.product_id
FROM mrp_bom_line)) AND p.active
ORDER BY e.id, pt.categ_id, p.id;
If I add new event_event or new product_product I'll get a new definition of row_number in my view, then the column ID of my view is not stable.
at least you can't use row_number as Id of the view,
If you insist to use row_number, you can use the Order By "creation DATE" by this way all new records will be as last lines in the view and this will not change the correspondency between ID (row_number) and other columns.
Hope that helps !
Very likely the execution plan of your query depends on the columns you select. Compare the execution plans!
Your id is generated using the row_number window function. Now window functions are executed before the ORDER BY clause, so the order will depend on the execution plan and hence on the columns you select.
Using row_number without an explicit ordering doesn't make any sense.
To fix that, don't use
row_number() OVER ()
but
row_number() OVER (ORDER BY e.id, pt.categ_id, p.id)
so that you have a reliable ordering.
In addition, you should omit the ORDER BY clause at the end.

How to translate SQL to DAX, Need to add FILTER

I want to create calculated table that will summarize In_Force Premium from existing table fact_Premium.
How can I filter the result by saying:
TODAY() has to be between `fact_Premium[EffectiveDate]` and (SELECT TOP 1 fact_Premium[ExpirationDate] ORDE BY QuoteID DESC)
In SQL I'd do that like this:
`WHERE CONVERT(date, getdate()) between CONVERT(date, tblQuotes.EffectiveDate)
and (
select top 1 q2.ExpirationDate
from Table2 Q2
where q2.ControlNo = Table1.controlno
order by quoteid` desc
)
Here is my DAX statement so far:
In_Force Premium =
FILTER(
ADDCOLUMNS(
SUMMARIZE(
//Grouping necessary columns
fact_Premium,
fact_Premium[QuoteID],
fact_Premium[Division],
fact_Premium[Office],
dim_Company[CompanyGUID],
fact_Premium[LineGUID],
fact_Premium[ProducerGUID],
fact_Premium[StateID],
fact_Premium[ExpirationDate]
),
"Premium", CALCULATE(
SUM(fact_Premium[Premium])
),
"ControlNo", CALCULATE(
DISTINCTCOUNT(fact_Premium[ControlNo])
)
), // Here I need to make sure TODAY() falls between fact_Premium[EffectiveDate] and (SELECT TOP 1 fact_Premium[ExpirationDate] ORDE BY QuoteID DESC)
)
Also, what would be more efficient way, to create calculated table from fact_Premium or create same table using sql statement (--> Get Data--> SQL Server) ?
There are 2 potential ways in T-SQL to get the next effective date. One is to use LEAD() and another is to use an APPLY operator. As there are few facts to work with here are samples:
select *
from (
select *
, lead(EffectiveDate) over(partition by CompanyGUID order by quoteid desc) as NextEffectiveDate
from Table1
join Table2 on ...
) d
or
select table1.*, oa.NextEffectiveDate
from Table1
outer apply (
select top(1) q2.ExpirationDate AS NextEffectiveDate
from Table2 Q2
where q2.ControlNo = Table1.controlno
order by quoteid desc
) oa
nb. an outer apply is a little similar to a left join in that it will allow rows with a NULL to be returned by the query, if that is not needed than use cross apply instead.
In both these approaches you may refer to NextEffectiveDate in a final where clause, but I would prefer to avoid using the convert function if that is feasible (this depends on the data).

tsql - Group By on computed columns

Please advice on a better way to do this.
I am sure this can be done in one query itself.
declare #tempTale table (ID bigint, ArticleDate datetime,CommentDate
datetime,MostRecentDate datetime)
declare #MinDate datetime;
set #MinDate = getdate();
set #MinDate = DATEADD(YEAR,-100,#MinDate)
insert into #tempTale
select USER_ARTICLEID, User_Article.CREATED_ON, coalesce(comment.CREATED_ON,#MinDate),
case when coalesce(User_Article.CREATED_ON,#MinDate) > coalesce(comment.CREATED_ON,#MinDate) then User_Article.CREATED_ON else comment.CREATED_ON end as MostRecentDate
from User_Article left join Comment on Comment.CONTENTID = User_Article.USER_ARTICLEID and comment.CONTENT_TYPE = User_Article.CONTENT_TYPE
order by MostRecentDate desc
select distinct top 10 ID,MAX(MostRecentDate) from #tempTale group by ID
order by MAX(MostRecentDate) desc
obvious change is to use sub-queries:
select distinct top 10 ID, MAX(MostRecentDate) from
(
select
USER_ARTICLEID as ID,
(case
when coalesce(User_Article.CREATED_ON,#MinDate) > coalesce(comment.CREATED_ON,#MinDate) then User_Article.CREATED_ON
else comment.CREATED_ON end) as MostRecentDate
from User_Article
left join Comment
on Comment.CONTENTID = User_Article.USER_ARTICLEID and comment.CONTENT_TYPE = User_Article.CONTENT_TYPE
)
group by ID
order by 2 desc
but you don't group on computed columns, so you can go with simple one:
select distinct top 10
USER_ARTICLEID as ID,
(case
when coalesce(User_Article.CREATED_ON,#MinDate) > coalesce(comment.CREATED_ON,#MinDate) then User_Article.CREATED_ON
else comment.CREATED_ON end) as MostRecentDate
from User_Article
left join Comment
on Comment.CONTENTID = User_Article.USER_ARTICLEID and comment.CONTENT_TYPE = User_Article.CONTENT_TYPE
group by USER_ARTICLEID
order by 2 desc

ERROR: function coalerse(bigint, integer) does not exist

I have this query, where I want to return zero values instead of null ones.
create view ct as
select userid, coalerse(count(tweets), 0) as nooftweets, coalerse(count(distinct mention), 0) as mention
from (
select t.user_id as userid, t.id as tweets, m.mentionedusers_id as mention, row_number() over (partition by m.tweet_id order by m.mentionedusers_id
) rn
from "tweet_mentUsers" m right join tweet t on m.tweet_id = t.id where text like '#%') a where rn <= 2 group by 1
However I get this error message:
ERROR: function coalerse(bigint, integer) does not exist
LINE 2: select userid, coalerse(nooftweets, 0), coalerse(mention, 0)...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
Do you have any idea?
I think the COALESCE function will do what you want.
create view ct as
select userid, coalesce(count(tweets), 0) as nooftweets, coalesce(count(distinct mention), 0) as mention
from (
select t.user_id as userid, t.id as tweets, m.mentionedusers_id as mention, row_number() over (partition by m.tweet_id order by m.mentionedusers_id
) rn
from "tweet_mentUsers" m right join tweet t on m.tweet_id = t.id where text like '#%') a where rn <= 2 group by 1

T-SQL Subquery on Latest Date with InnerJoin

I want to do a similiar thing like this guy:
T-SQL Subquery Max(Date) and Joins
I have to do this with an n:m relation.
So the layout is:
tbl_Opportunity
tbl_Opportunity_tbl_OpportunityData
tbl_OpportunityData
So as you see there is an intersection table which connects opportunity with opportunitydata.
For every opportunity there are multiple opportunity datas. In my view i only want a list with all opportunites and the data from the latest opportunity datas.
I tried something like this:
SELECT
dbo.tbl_Opportunity.Id, dbo.tbl_Opportunity.Subject,
dbo.tbl_User.UserName AS Responsible, dbo.tbl_Contact.Name AS Customer,
dbo.tbl_Opportunity.CreationDate, dbo.tbl_Opportunity.ActionDate AS [Planned Closure],
dbo.tbl_OpportunityData.Volume,
dbo.tbl_OpportunityData.ChangeDate, dbo.tbl_OpportunityData.Chance
FROM
dbo.tbl_Opportunity
INNER JOIN
dbo.tbl_User ON dbo.tbl_Opportunity.Creator = dbo.tbl_User.Id
INNER JOIN
dbo.tbl_Contact ON dbo.tbl_Opportunity.Customer = dbo.tbl_Contact.Id
INNER JOIN
dbo.tbl_Opprtnty_tbl_OpprtnityData ON dbo.tbl_Opportunity.Id = dbo.tbl_Opprtnty_tbl_OpprtnityData.Id
INNER JOIN
dbo.tbl_OpportunityData ON dbo.tbl_Opprtnty_tbl_OpprtnityData.Id2 = dbo.tbl_OpportunityData.Id
The problem is my view now includes a row for every opportunity data, since I don't know how to filter that I only want the latest data.
Can you help me? is my problem description clear enough?
thank you in advance :-)
best wishes,
laurin
; WITH Base AS (
SELECT dbo.tbl_Opportunity.Id, dbo.tbl_Opportunity.Subject, dbo.tbl_User.UserName AS Responsible, dbo.tbl_Contact.Name AS Customer,
dbo.tbl_Opportunity.CreationDate, dbo.tbl_Opportunity.ActionDate AS [Planned Closure], dbo.tbl_OpportunityData.Volume,
dbo.tbl_OpportunityData.ChangeDate, dbo.tbl_OpportunityData.Chance
FROM dbo.tbl_Opportunity INNER JOIN
dbo.tbl_User ON dbo.tbl_Opportunity.Creator = dbo.tbl_User.Id INNER JOIN
dbo.tbl_Contact ON dbo.tbl_Opportunity.Customer = dbo.tbl_Contact.Id INNER JOIN
dbo.tbl_Opprtnty_tbl_OpprtnityData ON dbo.tbl_Opportunity.Id = dbo.tbl_Opprtnty_tbl_OpprtnityData.Id INNER JOIN
dbo.tbl_OpportunityData ON dbo.tbl_Opprtnty_tbl_OpprtnityData.Id2 = dbo.tbl_OpportunityData.Id
)
, OrderedByDate AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY ChangeDate DESC) RN FROM Base
)
SELECT * FROM OrderedByDate WHERE RN = 1
To make it more readable I'm using CTE (the WITH part). In the end the real "trick" is doing a ROW_NUMBER() partitioning the data by tbl_Opportunity.Id and ordering the partitions by ChangeDate DESC (and I call it RN). Clearly the maximum date in each partition will be RN = 1 and then we filter it by RN.
Without using CTE it will be something like this:
SELECT * FROM (
SELECT dbo.tbl_Opportunity.Id, dbo.tbl_Opportunity.Subject, dbo.tbl_User.UserName AS Responsible, dbo.tbl_Contact.Name AS Customer,
dbo.tbl_Opportunity.CreationDate, dbo.tbl_Opportunity.ActionDate AS [Planned Closure], dbo.tbl_OpportunityData.Volume,
dbo.tbl_OpportunityData.ChangeDate, dbo.tbl_OpportunityData.Chance,
ROW_NUMBER() OVER (PARTITION BY dbo.tbl_Opportunity.Id ORDER BY dbo.tbl_OpportunityData.ChangeDate DESC) RN
FROM dbo.tbl_Opportunity INNER JOIN
dbo.tbl_User ON dbo.tbl_Opportunity.Creator = dbo.tbl_User.Id INNER JOIN
dbo.tbl_Contact ON dbo.tbl_Opportunity.Customer = dbo.tbl_Contact.Id INNER JOIN
dbo.tbl_Opprtnty_tbl_OpprtnityData ON dbo.tbl_Opportunity.Id = dbo.tbl_Opprtnty_tbl_OpprtnityData.Id INNER JOIN
dbo.tbl_OpportunityData ON dbo.tbl_Opprtnty_tbl_OpprtnityData.Id2 = dbo.tbl_OpportunityData.Id
) AS Base WHERE RN = 1
The statement can be simplified for one more step further:
SELECT TOP 1 WITH TIES
dbo.tbl_Opportunity.Id, dbo.tbl_Opportunity.Subject, dbo.tbl_User.UserName AS Responsible,
dbo.tbl_Contact.Name AS Customer, dbo.tbl_Opportunity.CreationDate,
dbo.tbl_Opportunity.ActionDate AS [Planned Closure], dbo.tbl_OpportunityData.Volume,
dbo.tbl_OpportunityData.ChangeDate, dbo.tbl_OpportunityData.Chance
FROM
dbo.tbl_Opportunity INNER JOIN
dbo.tbl_User ON dbo.tbl_Opportunity.Creator = dbo.tbl_User.Id INNER JOIN
dbo.tbl_Contact ON dbo.tbl_Opportunity.Customer = dbo.tbl_Contact.Id INNER JOIN
dbo.tbl_Opprtnty_tbl_OpprtnityData ON dbo.tbl_Opportunity.Id = dbo.tbl_Opprtnty_tbl_OpprtnityData.Id INNER JOIN
dbo.tbl_OpportunityData ON dbo.tbl_Opprtnty_tbl_OpprtnityData.Id2 = dbo.tbl_OpportunityData.Id
ORDER BY
ROW_NUMBER() OVER (PARTITION BY dbo.tbl_Opportunity.Id ORDER BY dbo.tbl_OpportunityData.ChangeDate DESC);