Joining with set-returning function (SRF) and access columns in SQLAlchemy - postgresql

Suppose I have an activity table and a subscription table. Each activity has an array of generic references to some other object, and each subscription has a single generic reference to some other object in the same set.
CREATE TABLE activity (
id serial primary key,
ob_refs UUID[] not null
);
CREATE TABLE subscription (
id UUID primary key,
ob_ref UUID,
subscribed boolean not null
);
I want to join with the set-returning function unnest so I can find the "deepest" matching subscription, something like this:
SELECT id
FROM (
SELECT DISTINCT ON (activity.id)
activity.id,
x.ob_ref, x.ob_depth,
subscription.subscribed IS NULL OR subscription.subscribed = TRUE
AS subscribed,
FROM activity
LEFT JOIN subscription
ON activity.ob_refs #> array[subscription.ob_ref]
LEFT JOIN unnest(activity.ob_refs)
WITH ORDINALITY AS x(ob_ref, ob_depth)
ON subscription.ob_ref = x.ob_ref
ORDER BY x.ob_depth DESC
) sub
WHERE subscribed = TRUE;
But I can't figure out how to do that second join and get access to the columns. I've tried creating a FromClause like this:
act_ref_t = (sa.select(
[sa.column('unnest', UUID).label('ob_ref'),
sa.column('ordinality', sa.Integer).label('ob_depth')],
from_obj=sa.func.unnest(Activity.ob_refs))
.suffix_with('WITH ORDINALITY')
.alias('act_ref_t'))
...
query = (query
.outerjoin(
act_ref_t,
Subscription.ob_ref == act_ref_t.c.ob_ref))
.order_by(activity.id, act_ref_t.ob_depth)
But that results in this SQL with another subquery:
LEFT OUTER JOIN (
SELECT unnest AS ob_ref, ordinality AS ref_i
FROM unnest(activity.ob_refs) WITH ORDINALITY
) AS act_ref_t
ON subscription.ob_refs #> ARRAY[act_ref_t.ob_ref]
... which fails because of the missing and unsupported LATERAL keyword:
There is an entry for table "activity", but it cannot be referenced from this part of the query.
So, how can I create a JOIN clause for this SRF without using a subquery? Or is there something else I'm missing?
Edit 1 Using sa.text with TextClause.columns instead of sa.select gets me a lot closer:
act_ref_t = (sa.sql.text(
"unnest(activity.ob_refs) WITH ORDINALITY")
.columns(sa.column('unnest', UUID),
sa.column('ordinality', sa.Integer))
.alias('act_ref'))
But the resulting SQL fails because it wraps the clause in parentheses:
LEFT OUTER JOIN (unnest(activity.ob_refs) WITH ORDINALITY)
AS act_ref ON subscription.ob_ref = act_ref.unnest
The error is syntax error at or near ")". Can I get TextAsFrom to not be wrapped in parentheses?

It turns out this is not directly supported by SA, but the correct behaviour can be achieved with a ColumnClause and a FunctionElement. First import this recipe as described by zzzeek in this SA issue. Then create a special unnest function that includes the WITH ORDINALITY modifier:
class unnest_func(ColumnFunction):
name = 'unnest'
column_names = ['unnest', 'ordinality']
#compiles(unnest_func)
def _compile_unnest_func(element, compiler, **kw):
return compiler.visit_function(element, **kw) + " WITH ORDINALITY"
You can then use it in joins, ordering, etc. like this:
act_ref = unnest_func(Activity.ob_refs)
query = (query
.add_columns(act_ref.c.unnest, act_ref.c.ordinality)
.outerjoin(act_ref, sa.true())
.outerjoin(Subscription, Subscription.ob_ref == act_ref.c.unnest)
.order_by(act_ref.c.ordinality.desc()))

Related

Getting duplicate column ERROR while trying to insert same column with two different datatypes using SELECT INTO clause in PostgreSql

I need to insert createdate column twice with two different datatypes one with the datatype defined in the table itself and another in char datatype.
I can insert it by changing the alias name of createdate column but can't insert with same alias name which i need.
so help me out to get correct way of doing it.
My query:
SELECT DISTINCT TE.id, T.debatchqueuelink, TE.transactionlink,
EC.errorclassification, TE.errorvalue,
EC.errorparameter, TE.classificationlink, TE.description,
TE.createdate AS createdate, TO_CHAR(TE.createdate, 'MM/dd/yyyy') AS createdate,
TE.status, TE.rebutt, TE.rebuttedstatus, BQ.appbatchnumber,
BQ.scanbatchnumber, BQ.clientlink, BQ.locationlink, T.patientid,
(DEUD.firstname|| ' ' ||DEUD.lastname) AS deusername, DEUD.email AS deuseremail,
(QCUD.firstname|| ' ' ||QCUD.lastname) AS qcusername, TE.inactive,
TE.decomment
INTO table373
FROM qctransactionerror TE
INNER JOIN errorclassification EC ON EC.id = TE.classificationlink
INNER JOIN qctransaction T ON T.id = TE.transactionlink
INNER JOIN batchqueue BQ ON T.debatchqueuelink = BQ.id
INNER JOIN batchqueue QCBQ ON T.qcbatchqueuelink = QCBQ.id
INNER JOIN userdetail QCUD ON QCBQ.assignedto = QCUD.id
INNER JOIN userdetail DEUD ON BQ.assignedto = DEUD.id
WHERE TE.inactive='t'
AND TE.status IN ('ERROR','QCCORRECTED')
LIMIT 0
The actual error message I am getting is:
Duplicate column:column "createdate" specified more than once

Postgres Error: missing FROM-clause entry for table

I have a query and am using left joins. I have the left join clause as follows:
left outer join ( select pup.brokerage_code, pcz.zip, count (pup.aggregate_id) as VerifiedAgentCount
from partner_user_profiles pup
join partner_user_roles pure on pure.user_profile_id = pup.id
join profile_coverage_zips pcz on pcz.profile_id = pup.id
where lower(pure.role) = 'agent'
and pup.verification_status like 'Verified%'
group by pup.brokerage_code, pcz.zip) vac on vac.brokerage_code = b.brokerage_code and pcz.zip = bcz.zip
However I am getting the error message saying that I am missing the FROM entry clause for "pcz" however I aliased the table in the join clause so I am not sure what is wrong.
You have defined the table alias pcz within the sub-select however the alias no longer exists when the outside the sub-select. At the point you have used it the appropriate alias is the one for the entire sub-select, in this case vac. So: vac.zip = = bcz.zip
left outer join ( select pup.brokerage_code, pcz.zip, count (pup.aggregate_id) as VerifiedAgentCount
from partner_user_profiles pup
join partner_user_roles pure on pure.user_profile_id = pup.id
join profile_coverage_zips pcz on pcz.profile_id = pup.id
where lower(pure.role) = 'agent'
and pup.verification_status like 'Verified%'
group by pup.brokerage_code, pcz.zip
) vac on vac.brokerage_code = b.brokerage_code
and vac.zip = bcz.zip

Using (joining and selecting from) an Postgres CTE in SQLAlchemy's hybrid_property

I have a Postgres function loaded in to a database which takes two arguments to fetch some data. I'm trying to use SQLAlchemy's hybrid_property.expression to access and make it available in the resulting select.
Here's the header of the function:
CREATE OR REPLACE FUNCTION the_func(int, character varying)
RETURNS TABLE (
mymodel_id int,
x_const character varying,
y_const character varying,
selection_priority int
)
AS ...
In SQL, to use it would look like this:
SELECT
COALESCE(b.x_const, b.y_const) AS constant,
FROM a
LEFT JOIN the_func(a.id, 'abc') AS b
ON b.mymodel_id = a.id;
However, I'm struggling to get this into SQLAlchemy:
class MyModel:
id = sa.Column(sa.Int)
#hybrid_property
def the_const(self):
"""Python version..."""
# ???
#the_const.expression
def the_const(cls):
"""Use the CTE..."""
# somehow join?
# ...join(
# aliased(
# func.the_func(MyModel.id, 'abc'),
# name="const_func"
# )
# )...
return select([text("const_func.constant")]).as_scalar()
I think it would be possible to get the CTE into SQLAlchemy's CTE (similar to how the OP does here: Join on a CTE in SQLAlchemy) but wonder if there's a way to use them when they are already loaded in the DB (as in this case).
With a bit of help from Adding a join to an SQL Alchemy expression that already has a select_from() I found a solution
class MyModel:
id = sa.Column(sa.Int)
#the_const.expression
def the_const(cls):
"""Use the CTE..."""
# Alias the CTE:
const_func = aliased(
func.the_func(MyModel.id, 'abc'),
name="b"
)
# Set up the join:
the_join = cls.__table__
the_join = the_join.join(
const_func,
text("b.mymodel_id") == MyModel.id
)
# Finally select using the text() wrapper, from the joined tables
query = select(
[text("COALESCE(b.x_const, b.y_const)")]
).select_from(the_join)
return query.as_scalar()

Update all using alias from an aggregate value derived from a Join

SELECT activities.id, max(symbols.bought_at) AS bought_at
FROM "activities"
JOIN holdings ON trackable_id = holdings.id AND trackable_type = 'Holding'
JOIN symbols on symbols.holding_id = holdings.id
GROUP BY activities.id"
I have a SQL that looks like the above. This works fine. However, I want to update all activities' created_at to the alias bought_at. I get an error that bought_at is not a column. Is it possible to do so in Postgres?
you can use that query as the source for an UPDATE statement:
update activities
set created_at = t.bought_at
from (
SELECT activities.id, max(symbols.bought_at) AS bought_at
FROM activities
JOIN holdings ON trackable_id = holdings.id AND trackable_type = 'Holding'
JOIN symbols on symbols.holding_id = holdings.id
GROUP BY activities.id
) t
where activities.id = t.id;
This assumes that activities.id is the primary key of that table.

Postgresql, how to SELECT json object without duplicated rows

I'm trying to find out how to get JSON object results of selected rows, and not to show duplicated rows.
My current query:
SELECT DISTINCT ON (vp.id) jsonb_agg(jsonb_build_object('affiliate',a.*)) as affiliates, jsonb_agg(jsonb_build_object('vendor',vp.*)) as vendors FROM
affiliates a
INNER JOIN related_affiliates ra ON a.id = ra.affiliate_id
INNER JOIN related_vendors rv ON ra.product_id = rv.product_id
INNER JOIN vendor_partners vp ON rv.vendor_partner_id = vp.id
WHERE ra.product_id = 79 AND a.is_active = true
GROUP BY vp.id
The results that I receive from this is:
[
affiliates: {
affiliate: affiliate1
affiliate: affiliate2
},
vendors: {
vendor: vendor1,
vendor: vendor1,
}
As you can see in the second record, vendor is still vendor1 because there are no more results, so I'd like to also know if there's a way to remove duplicates.
Thanks.
First point : the result you display here above doesn't conform the json type : the keys are not double-quoted, the string values are not double-quoted, having dupplicated keys in the same json object ('{"affiliate": "affiliate1", "affiliate": "affiliate2"}' :: json) is not be accepted with the jsonb type (but it is with the json type).
Second point : you can try to add the DISTINCT key word directly in the jsonb_agg function :
jsonb_agg(DISTINCT jsonb_build_object('vendor',vp.*))
and remove the DISTINCT ON (vp.id) clause.
You can also add an ORDER BY clause directly in any aggregate function. For more information, see the manual.
You could aggregate first, then join on the results of the aggregates:
SELECT a.affiliates, v.vendors
FROM (
select af.id, jsonb_agg(jsonb_build_object('affiliate',af.*)) as affiliates
from affiliates af
group by af.id
) a
JOIN related_affiliates ra ON a.id = ra.affiliate_id
JOIN related_vendors rv ON ra.product_id = rv.product_id
JOIN (
select vp.id, jsonb_agg(jsonb_build_object('vendor',vp.*)) as vendors
from vendor_partners vp
group by vp.id
) v ON rv.vendor_partner_id = v.id
WHERE ra.product_id = 79
AND a.is_active = true