PostgresSQL Cannot order by json_build_object result (got from subquery) - sql

I have a SQL query. And I'd like to order by json field:
SELECT "ReviewPacksModel"."id",
(SELECT json_build_object(
'totalIssues', COUNT(*),
'openIssues', COUNT(*) filter (where "issues".status = 'Open'),
'fixedIssues', COUNT(*) filter (where "issues".status = 'Fixed')
)
FROM "development"."issues" "issues"
JOIN "development"."reviewTasks" as "rt" ON "issues"."reviewTaskId" = "rt".id
WHERE "issues"."isDeleted" = false
AND "rt"."reviewPackId" = "ReviewPacksModel"."id"
) as "issueStatistic"
FROM "development"."reviewPacks" AS "ReviewPacksModel"
WHERE "ReviewPacksModel"."projectId" = '2'
AND "ReviewPacksModel"."mode" IN ('Default', 'Live')
AND "ReviewPacksModel"."status" IN ('Draft', 'Active')
ORDER BY "issueStatistic"->'totalIssues'
LIMIT 50;
And I get an error:
ERROR: column "issueStatistic" does not exist
If I try to order by issueStatistic without ->'totalIssues', I will get another error:
ERROR: could not identify an equality operator for type json
It seems like I cannot extract field from the JSON.
I also tested it with this query:
SELECT "ReviewPacksModel".*,
(SELECT Count(*)
FROM "development"."issues" "issues"
JOIN "development"."reviewTasks" as "rt" ON "issues"."reviewTaskId" = "rt".id
WHERE "issues"."isDeleted" = false
AND "rt"."reviewPackId" = "ReviewPacksModel"."id"
) AS "issueStatistic"
FROM "development"."reviewPacks" AS "ReviewPacksModel"
WHERE "ReviewPacksModel"."projectId" = '2'
AND "ReviewPacksModel"."mode" IN ('Default', 'Live')
AND "ReviewPacksModel"."status" IN ('Draft', 'Active')
ORDER BY "issueStatistic"
LIMIT 50;
And it works without any problems. But I cannot use it cause it's not possible to return multiple columns from a subquery. I also tried to use alternatives like array_agg, json_agg, etc. but it doesn't help.
I know that it's possible to make multiple queries, but they aren't super fast and for me it's better to use json_build_object.

You can use aliases in ORDER BY, but you cannot use expressions involving aliases.
You'll have to use a subquery.
Also, you cannot order on a json. You'll have to convert it to a sortable data type. In the following I assume it is a number; you'll have to adapt the query if my assumption is wrong.
SELECT id, "issueStatistic"
FROM (SELECT "ReviewPacksModel"."id",
(SELECT json_build_object(
'totalIssues', COUNT(*),
'openIssues', COUNT(*) filter (where "issues".status = 'Open'),
'fixedIssues', COUNT(*) filter (where "issues".status = 'Fixed')
)
FROM "development"."issues" "issues"
JOIN "development"."reviewTasks" as "rt" ON "issues"."reviewTaskId" = "rt".id
WHERE "issues"."isDeleted" = false
AND "rt"."reviewPackId" = "ReviewPacksModel"."id"
) as "issueStatistic"
FROM "development"."reviewPacks" AS "ReviewPacksModel"
WHERE "ReviewPacksModel"."projectId" = '2'
AND "ReviewPacksModel"."mode" IN ('Default', 'Live')
AND "ReviewPacksModel"."status" IN ('Draft', 'Active')
) AS subq
ORDER BY CAST ("issueStatistic"->>'totalIssues' AS bigint)
LIMIT 50;

demos:db<>fiddle
You cannot order by type json because, simply spoken, there is no definition on how to handle different types included in the JSON object. But this gives you a type json:
"issueStatistic"->'totalIssues'
However, type jsonb can be ordered. So, instead of creating a type json object, you should use jsonb_build_object() to create a type jsonb object.
Alternatively you could cast your expression into type int (mind the ->> operator instead of your -> which casts the output into type text which can be directly cast into type int):
("issueStatistic"->>'totalIssues')::int
Edit:
As Laurenz mentioned correctly, to use aliases you need a separate subquery:
SELECT
*
FROM (
-- <your query minus ORDER clause>
) s
ORDER BY "issueStatistic"->'totalIssues'

Related

Why same query results are different on BigQuery editor and sqlalchemy?

My bigquery query is :
SELECT d.type AS `error_type`, count('d.type') AS `count`
FROM `table_android`, unnest(`table_android`.`exceptions`) AS `d`
WHERE `table_android`.`event_timestamp` BETWEEN '2022-12-15' AND '2022-12-20' GROUP BY `error_type` ORDER BY `count` desc;
This query is working fine in bigquery editor. But same version of query with sqlalchemy I could not get same results.
sqlalchemy query :
sa.select(
sa.literal_column("d.type").label("Error_Type"),
sa.func.count("Error_Type").label("count"),
)
.select_from(sa.func.unnest(table_android.c.exceptions).alias("d"))
.group_by("Error_Type")
.order_by(desc("count"))
.where(table_android.c.event_timestamp.between('2022-12-15', '2022-12-20'))
.cte("main_table")
Correct result :
Wrong result:
I am using python-bigquery-sqlalchemy library. table_android.exceptions column struct is like that :
column types :
And this is render of sqlalchemy query :
SELECT `d`.`type` AS `Error_Type`, count(`d`.`type`) AS `count` FROM `table_android`, unnest(`table_android`.`exceptions`) AS `d` WHERE `table_android`.`event_timestamp` BETWEEN '2022-12-05' AND '2022-12-20' GROUP BY `Error_Type` ORDER BY `count` DESC
I see correct result in bigquery editor. But sqlalchemy is not shows correct result. How should i edit my sqlalchemy query for correct results ?
I don't have bigquery so I can't test this but I think you want column and not literal_column. Also I think you can create an implicit CROSS JOIN by including both the table and the unnested column in the select_from.
# I'm testing with postgresql but it doesn't have easy to make structs
from sqlalchemy.dialects import postgresql
table_android = Table("table_android", metadata,
Column("id", Integer, primary_key=True),
Column("exceptions", postgresql.ARRAY(String)),
Column("event_timestamp", Date))
from sqlalchemy.sql import column, func, select
d = func.unnest(table_android.c.exceptions).alias("d")
# You might be able to do d.column["type"].label("Error_Type")
type_col = column("d.type").label("Error_Type")
count_col = func.count(type_col).label("count")
print (select(
type_col,
count_col,
)
.select_from(table_android, d)
.group_by(type_col)
.order_by(count_col)
.where(table_android.c.event_timestamp.between('2022-12-15', '2022-12-20'))
.cte("main_table"))

TypeORM: column must appear in the GROUP BY clause or be used in an aggregate function

I'm trying to work out why the following raw SQL works perfectly with PostGres, whereas the SQL generated via TypeORM does not.
Running this works:
SELECT symbol, MAX(created_at) AS created_at
FROM update_history
WHERE exchange = 'NYSE'
AND data_type = 'companyRecord'
GROUP BY symbol
ORDER BY created_at ASC
Example: https://dbfiddle.uk/yocl-rBq
Whereas, the TypeORM sql generated by the following:
const result = this.repository
.createQueryBuilder('h1')
.select(['MAX(h1.createdAt) AS created_at', 'h1.symbol'])
.where('h1.exchange = :exchange', { exchange })
.andWhere('h1.dataType = :dataType', { dataType })
.groupBy('h1.symbol')
.orderBy({ created_at: 'ASC' })
.take(limit)
.getMany();
/* Produces this:
SELECT "h1"."symbol" AS "h1_symbol",
MAX("h1"."created_at") AS created_at
FROM "update_history" "h1"
WHERE "h1"."exchange" = 'NYSE'
AND "h1"."data_type" = 'companyRecord'
GROUP BY "h1"."symbol"
ORDER BY created_at ASC LIMIT 5000
*/
Example (second select box): https://dbfiddle.uk/yocl-rBq
Returns the following error:
QueryFailedError: column "h1.id" must appear in the GROUP BY clause or be used in an aggregate function
If I add h1.id to the GROUP BY clause, the query no longer returns the correct result set, as it is effectively different.
As you can see from the DBFiddle links, both generated SQL queries work outside of TypeORM.
What am I missing here?
You're using getMany() which always adds id field in the select. The query that you've pasted is most probably has been logged before adding getMany(). Try setting logging: true in your ormconfig.js and you'll see the exact query being fired.
You should use getRawMany() and you'll be able to get the exact query and also the desired result.

Issue With SQL Pivot Function

I have a SQL query where I am trying to replace null results with zero. My code is producing an error
[1]: ORA-00923: FROM keyword not found where expected
I am using an Oracle Database.
Select service_sub_type_descr,
nvl('Single-occupancy',0) as 'Single-occupancy',
nvl('Multi-occupancy',0) as 'Multi-occupancy'
From
(select s.service_sub_type_descr as service_sub_type_descr, ch.claim_id,nvl(ci.item_paid_amt,0) as item_paid_amt
from table_1 ch, table_" ci, table_3 s, table_4 ppd
where ch.claim_id = ci.claim_id and ci.service_type_id = s.service_type_id
and ci.service_sub_type_id = s.service_sub_type_id and ch.policy_no = ppd.policy_no)
Pivot (
count(distinct claim_id), sum(item_paid_amt) as paid_amount For service_sub_type_descr IN ('Single-occupancy', 'Multi-occupancy')
)
This expression:
nvl('Single-occupancy',0) as 'Single-occupancy',
is using an Oracle bespoke function to say: If the value of the string Single-occupancy' is not null then return the number 0.
That logic doesn't really make sense. The string value is never null. And, the return value is sometimes a string and sometimes a number. This should generate a type-conversion error, because the first value cannot be converted to a number.
I think you intend:
coalesce("Single-occupancy", 0) as "Single-occupancy",
The double quotes are used to quote identifiers, so this refers to the column called Single-occupancy.
All that said, fix your data model. Don't have identifiers that need to be quoted. You might not have control in the source data but you definitely have control within your query:
coalesce("Single-occupancy", 0) as Single_occupancy,
EDIT:
Just write the query using conditional aggregation and proper JOINs:
select s.service_sub_type_descr, ch.claim_id,
sum(case when service_sub_type_descr = 'Single-occupancy' then item_paid_amt else 0 end) as single_occupancy,
sum(case when service_sub_type_descr = 'Multi-occupancy' then item_paid_amt else 0 end) as multi_occupancy
from table_1 ch join
table_" ci
on ch.claim_id = ci.claim_id join
table_3 s
on ci.service_type_id = s.service_type_id join
table_4 ppd
on ch.policy_no = ppd.policy_no
group by s.service_sub_type_descr, ch.claim_id;
Much simpler in my opinion.
for column aliases, you have to use double quotes !
don't use
as 'Single-occupancy'
but :
as "Single-occupancy",

OrientDB using LET values in subQuery

How can you use a LET temporary variable inside the Where clause in an OrientDB SQL subQuery.
Here is the context in wich I'm trying to use it.
select *, $t.d from Currency
let $t = (select createdDate.asLong() as d from 13:1)
where createdDate.asLong() >= $t.d and #rid <> #13:1
order by createdDate ASC
The validation in the where statement for the dates does not work. The subQuery actually works on its own. The Query works as well when replacing $t.d with the result from the subQuery.
The $t.d is an array so you are comparing something like createdDate.asLong() >= [1234599]
You have to do this: createdDate.asLong() >= $t[0].d

Need to make a new column queryable

I created the column frfpost_short but now I am having trouble making the column usable in a query.
select t.corridor,
s.corridor_code_rb,t.roadway,s.SVYLENG2012,
round(cast(t.frfpost as float),3)) as frfpost_short,
s.FRFPOST,s.BEG_GN
from SEC_FILE_IMPORT_2014 t,NORTH_VAN_DATA_VIEW_MOD_032015 s,
where frfpost_short = s.FRFPOST -- This line is causing problems
-- I would like to make frfpost_short queryable
and t.corridor = s.CORRIDOR_CODE
order by 1
Use a subquery or repeat the expression. Aliases defined in the select are not available in the where:
select t.corridor,
s.corridor_code_rb,t.roadway, s.SVYLENG2012,
round(cast(t.frfpost as float), 3) as frfpost_short,
s.FRFPOST, s.BEG_GN
from SEC_FILE_IMPORT_2014 t join
NORTH_VAN_DATA_VIEW_MOD_032015 s
on round(cast(t.frfpost as float), 3) = s.FRFPOST and
t.corridor = s.CORRIDOR_CODE
order by 1
I fixed your query. In particular, learn to use explicit join syntax.
You can't use aliases in where clause, you need to use below solution
select t.corridor,
s.corridor_code_rb,t.roadway,s.SVYLENG2012,
round(cast(t.frfpost as float),3)) as frfpost_short,
s.FRFPOST,s.BEG_GN
from SEC_FILE_IMPORT_2014 t,NORTH_VAN_DATA_VIEW_MOD_032015 s,
where round(cast(t.frfpost as float),3)) = s.FRFPOST
and t.corridor = s.CORRIDOR_CODE
order by 1