Skip to main content

Column and Distinct

Simple column selection

export type ParcelQLSimpleColumn = {
column: string | string[];
type?: string | string[];
};

Examples from the flow_events table.

1. Selecting single column

Request Query

{
"action": "query",
"table": "flow_events",
"columns": [
{
"column": "address"
}
]
}

Generated SQL

select `address` from `flow_events`;

Response

{
"data": [
{
"address": "0xecfad18ba9582d4f"
},
{
"address": "0x0b2a3299cc857e29"
},
...
]
}

2. Selecting multiple columns

Request Query

{
"action": "query",
"table": "flow_events",
"columns": [
{
"column": "address"
},
{
"column": "contract"
}
]
}

Generated SQL

select `address`, `contract` from `flow_events`;

Response

{
"data": [
{
"address": "0xecfad18ba9582d4f",
"contract": "A.ecfad18ba9582d4f.JoyrideMultiToken"
},
{
"address": "0x0b2a3299cc857e29",
"contract": "A.0b2a3299cc857e29.TopShot"
},
{
"address": "0x0b2a3299cc857e29",
"contract": "A.0b2a3299cc857e29.TopShot"
},
{
"address": "0x0b2a3299cc857e29",
"contract": "A.0b2a3299cc857e29.TopShot"
},
...
]
}

3. Selecting all columns using *

Request Query

{
"action": "query",
"table": "flow_events",
"columns": [
{
"column": "*"
}
]
}

Generate SQL

select * from `flow_events`

Response

{
"data": [
{
"address": "0xecfad18ba9582d4f",
"contractName": "JoyrideMultiToken",
"contract": "A.ecfad18ba9582d4f.JoyrideMultiToken",
"event": "A.ecfad18ba9582d4f.JoyrideMultiToken.JoyrideMultiTokenInfoEvent",
"eventName": "JoyrideMultiTokenInfoEvent",
"timestamp": "2023-07-23T14:51:14.827Z",
"collectionId": "e8738554d413d79deb510342f45fa77b22210ed0470a715d17f33918ea386b97",
"transactionId": "969f6aa1cc41b4ed020f4a2a8cc8a7c5e2c5550408444f0a68857fb113664c6b",
"eventIndex": "4",
"blockId": "8e169acb8e21cb93209036af13a0f5bd94cf23e9937c61ebf397172c0c4e8611",
"blockHeight": "57301601",
"payload": {
"notes": "depositToken for userA.231cc0dbbcffc4b7.RLY.Vault"
}
},
{
"address": "0x0b2a3299cc857e29",
"contractName": "TopShot",
"contract": "A.0b2a3299cc857e29.TopShot",
"event": "A.0b2a3299cc857e29.TopShot.MomentMinted",
"eventName": "MomentMinted",
"timestamp": "2023-07-23T14:51:14.827Z",
"collectionId": "08bd5009d60ab9fed01e639dccc78fd79d445e0879c25f7f6293e0393955faf9",
"transactionId": "4f53d086e7c80eac323d32654b6250c1f272ffe9c1c30bfb5a74c372eb4bde99",
"eventIndex": "0",
"blockId": "8e169acb8e21cb93209036af13a0f5bd94cf23e9937c61ebf397172c0c4e8611",
"blockHeight": "57301601",
"payload": {
"setID": 90,
"playID": 4046,
"momentID": "43023669",
"serialNumber": 7301,
"subeditionID": 0
}
},
...
]
}

You can generate the same query by providing columns as undefined.

4. JSON data type selection

The payload data from event is stored in jsonb format. Let's have a look how we can use ParcelQL to select attribute from payload.

This is how A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted is stored in the database.

{
"address": "0x4eb8a10cb9f87357",
"contractName": "NFTStorefrontV2",
"contract": "A.4eb8a10cb9f87357.NFTStorefrontV2",
"event": "A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted",
"eventName": "ListingCompleted",
"timestamp": "2023-07-23T14:36:36.814Z",
"collectionId": "bf6c7237e02528a51143c2543a68d9e337f6d7f2308055751ae3d8f6167153c6",
"transactionId": "2bcc3aa168c8e9f80cf0a3bee6e0efcfad59d57cd5a81ffa721de9827e3722b6",
"eventIndex": "8",
"blockId": "23dc7175fd1d5d52b3c349008d6df3e313653df24d0fa1ee74eb2d529b85aa53",
"blockHeight": "57300898",
"payload": {
"nftID": "2",
"expiry": "1733817600000",
"nftType": {
"kind": "Resource",
"type": "",
"fields": [
{
"id": "uuid",
"type": {
"kind": "UInt64"
}
},
{
"id": "id",
"type": {
"kind": "UInt64"
}
},
{
"id": "editionID",
"type": {
"kind": "UInt32"
}
},
{
"id": "serialNumber",
"type": {
"kind": "UInt64"
}
}
],
"typeID": "A.eee6bdee2b2bdfc8.Basketballs.NFT",
"initializers": []
},
"nftUUID": "71226420",
"customID": "",
"purchased": true,
"salePrice": "10.00000000",
"commissionAmount": "0.00000000",
"listingResourceID": "1192177821",
"commissionReceiver": null,
"salePaymentVaultType": {
"kind": "Resource",
"type": "",
"fields": [
{
"id": "uuid",
"type": {
"kind": "UInt64"
}
},
{
"id": "balance",
"type": {
"kind": "UFix64"
}
}
],
"typeID": "A.ead892083b3e2c6c.DapperUtilityCoin.Vault",
"initializers": []
},
"storefrontResourceID": "506239605"
}
}

Let's create a query to select all A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted and get their nft types. The typeID attribute in payload.nftType contains the resource address of NFT.

Request Query

{
"action": "query",
"table": "flow_events",
"columns": [
{
"column": ["payload", "nftType", "typeID"]
}
],
"filter": {
"column": "event",
"operator": "=",
"value": "A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted"
}
}

Generated SQL

select (`payload`->'nftType'->>'typeID') from `flow_events` where (`event` = 'A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted');

Response

{
"data": [
{
"?column?": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
},
{
"?column?": "A.d0bcefdf1e67ea85.HWGarageCardV2.NFT"
},
{
"?column?": "A.d0bcefdf1e67ea85.HWGarageCardV2.NFT"
},
{
"?column?": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
},
{
"?column?": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
},
{
"?column?": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
},
{
"?column?": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
},
{
"?column?": "A.d0bcefdf1e67ea85.HWGarageCardV2.NFT"
},
...
]
}

We can see the name ?column?. In the upcoming section, we will be taught how to assign custom names to columns using the alias feature. Furthermore, in the later part of the tutorial, we'll explore the usage of distinct to extract unique values and gain an understanding of the filter functionality.

5. Typecasting values

Performing typecasting is a vital functionality that proves useful in the Excluence API, particularly when handling significant numeric values stored as strings. Presently, ParcelQL offers support for typecasting to the following data types:

[
// Numbers
'integer',
'smallint',
'double precision',
'decimal',
'bigint',
// Text and string
'text',
// Boolean
'boolean',
// Date and time
'date',
// Json
'json',
'jsonb'
]

To understand typecasting in ParcelQL, let's take the A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted event, in the payload storefrontResourceID is an UInt64 value and stored as string in the database. Now, let's explore how you can efficiently convert this string value into an integer.

Request Query

{
"action": "query",
"table": "flow_events",
"columns": [
{
"column": ["payload", "listingResourceID"],
"alias": "listingResourceID"
},
{
"column": ["payload", "storefrontResourceID"],
"type": "integer"
}
],
"filter": {
"column": "event",
"operator": "=",
"value": "A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted"
}
}

Generated SQL

select (`payload`->>'listingResourceID') as `listingResourceID`, (`payload`->>'storefrontResourceID')::integer from `flow_events` where (`event` = 'A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted');

Response

{
"data": [
{
"listingResourceID": "1192177821",
"int4": 506239605
},
{
"listingResourceID": "1144944279",
"int4": 995020166
},
{
"listingResourceID": "1144944945",
"int4": 995020166
},
{
"listingResourceID": "1192178848",
"int4": 506242189
},
{
"listingResourceID": "1192179885",
"int4": 506239605
},
...
]
}

Upon examination, it becomes evident that the storefrontResourceID for the aforementioned event, with a corresponding listingResourceID of "1192177821" has been successfully transformed from a string into an integer data type.

type attribute in ParcelQLSimpleColumn is a string array. The developers can chain the typecasting for example for converting any value to {any value type} -> text -> decimal the type: ['text', 'decimal] and it will generate typecasting SQL ::text::decimal.

Complex column selection and manipulation

export type ParcelQLColumn = Partial<ParcelQLSimpleColumnWithCase> & {
alias?: string;
} & (
| {
function: ParcelQLAggregationFunction | ParcelQLColumnFunction;
parameters?: (unknown | ParcelQLSimpleColumnWithCase)[];
}
| {
window: ParcelQLWindow;
function: ParcelQLWindowFunction;
parameters?: (unknown | ParcelQLSimpleColumnWithCase)[];
}
| {}
);

1. Aliasing the column

Taking the above example. The previous example returned ?column? due un-determinable column name, Let's have a look how we can alias any column.

For aliasing ParcelQLColumn has alias property. It generates an as SQL statement.

{
"action": "query",
"table": "flow_events",
"columns": [
{
"column": ["payload", "nftType", "typeID"],
"alias": "nftTypeID"
}
],
"filter": {
"column": "event",
"operator": "=",
"value": "A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted"
}
}

Generated SQL

select (`payload`->'nftType'->>'typeID') as `nftTypeID` from `flow_events` where (`event` = 'A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted');

Response

{
"data": [
{
"nftTypeID": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
},
{
"nftTypeID": "A.d0bcefdf1e67ea85.HWGarageCardV2.NFT"
},
{
"nftTypeID": "A.d0bcefdf1e67ea85.HWGarageCardV2.NFT"
},
{
"nftTypeID": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
},
...
]
}

2. Case clause

export interface ComparisonFilterColumn
extends Partial<ParcelQLSimpleColumnWithCase> {
function?: ParcelQLAggregationFunction | ParcelQLColumnFunction;
parameters?: (unknown | ParcelQLSimpleColumnWithCase)[];
}

interface _CompFilter {
column: string | string[] | ComparisonFilterColumn;
operator: ComparisonOps;
type?: string | string[];
}

export type CompFilter = _CompFilter &
({ value: unknown } | { rightColumn: ComparisonFilterColumn });
export interface ParcelQLCase {
when: { and: CompFilter[] } | { or: CompFilter[] } | CompFilter;
then: unknown | ParcelQLSimpleColumn;
}

export interface ParcelQLCaseWhen {
cases: ParcelQLCase[];
else: unknown | ParcelQLSimpleColumn;
}

export interface ParcelQLSimpleColumnWithCase {
column: string | string[] | ParcelQLCaseWhen;
type?: string | string[];
}

The CASE expression goes through conditions and returns a value when the first condition is met (like an if-then-else statement). So, once a condition is true, it will stop reading and return the result. If no conditions are true, it returns the value in the ELSE clause.

Let's illustrate its usage through an example. Consider a scenario where we aim to construct a query that returns three columns labeled high, medium, and low based on the salePrice of any A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted event. The column values will be categorized as follows:

  • high: if the salePrice is greater than or equal to 8.
  • medium: if the salePrice is lesser than 8 but greater than or equal to 4.
  • low: if the salePrice is lesser than 4.

This way, the query will effectively group the salePrice values into these three categories for further analysis.

Request Query

{
"action": "query",
"table": "flow_events",
"columns": [
{
"column": ["payload", "listingResourceID"],
"alias": "listingResourceID"
},
{
"column": ["payload", "salePrice"],
"alias": "salePrice"
},
{
"column": {
"cases": [
{
"when": {
"column": ["payload", "salePrice"],
"type": "decimal",
"operator": ">=",
"value": 8
},
"then": "high"
},
{
"when": {
"and": [
{
"column": ["payload", "salePrice"],
"type": "decimal",
"operator": "<",
"value": 8
},
{
"column": ["payload", "salePrice"],
"type": "decimal",
"operator": ">=",
"value": 4
}
]
},
"then": "medium"
}
],
"else": "low"
},
"alias": "sentiment"
}
],
"filter": {
"column": "event",
"operator": "=",
"value": "A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted"
}
}

Generated SQL

select (`payload`->>'listingResourceID') as `listingResourceID`, (`payload`->>'salePrice') as `salePrice`, CASE WHEN (`payload`->>'salePrice')::decimal >= 8 THEN 'high' WHEN (`payload`->>'salePrice')::decimal < 8 AND (`payload`->>'salePrice')::decimal >= 4 THEN 'medium' ELSE 'low' END as `sentiment` from `flow_events` where (`event` = 'A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted');

Response

{
"data": [
{
"listingResourceID": "1192177821",
"salePrice": "10.00000000",
"sentiment": "high"
},
{
"listingResourceID": "1144944279",
"salePrice": "2.99000000",
"sentiment": "low"
},
{
"listingResourceID": "1144944945",
"salePrice": "4.99000000",
"sentiment": "medium"
},
{
"listingResourceID": "1192178848",
"salePrice": "10.00000000",
"sentiment": "high"
},
...
]
}

In the upcoming Filter tutorial, we will delve into a comprehensive understanding of CompFilter.

Distinct

export interface ParcelQLColumnWithoutWindow
extends Partial<ParcelQLSimpleColumnWithCase> {
alias?: string;
function?: ParcelQLAggregationFunction | ParcelQLColumnFunction;
parameters?: (unknown | ParcelQLSimpleColumnWithCase)[];
}

export interface ParcelQLDistinct {
on?: ParcelQLSimpleColumn;
columns: ParcelQLColumnWithoutWindow[];
}

Let's use the above example to understand how we can use distinct clause to fetch unique nftType, eliminating any duplicate entries.

Request Query

{
"action": "query",
"table": "flow_events",
"columns": [],
"distinct": {
"columns": [
{
"column": ["payload", "nftType", "typeID"]
}
]
},
"filter": {
"column": "event",
"operator": "=",
"value": "A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted"
}
}

Generate SQL

select DISTINCT (`payload`->'nftType'->>'typeID') from `flow_events` where (`event` = 'A.4eb8a10cb9f87357.NFTStorefrontV2.ListingCompleted');

Response

{
"data": [
{
"?column?": "A.0b2a3299cc857e29.TopShot.NFT"
},
{
"?column?": "A.321d8fcde05f6e8c.Seussibles.NFT"
},
{
"?column?": "A.329feb3ab062d289.UFC_NFT.NFT"
},
{
"?column?": "A.807c3d470888cc48.Flunks.NFT"
},
{
"?column?": "A.87ca73a41bb50ad5.Golazos.NFT"
},
{
"?column?": "A.c6945445cdbefec9.TuneGONFT.NFT"
},
{
"?column?": "A.d0bcefdf1e67ea85.HWGarageCardV2.NFT"
},
{
"?column?": "A.e4cf4bdc1751c65d.PackNFT.NFT"
},
{
"?column?": "A.eee6bdee2b2bdfc8.Basketballs.NFT"
}
]
}

NOTE: Currently, ParcelQL does not provide direct support for in-column distinct values, such as COUNT(DISTINCT ...) statements.