Iterate over all Rows and Columns in DBT - Jinja - loops

I have a table that looks something like this:
id
name
table_name
agg_type
agg_column
condition,unique
1
count_of_courses
stg_courses
count
null
null
2
count_of_bundles
stg_bundles
count
null
null
3
count_of_products
stg_products
count
null
null
4
sum_of_gmv
stg_transactions
sum
revenue_usd
null
5
count_of_course_admins
stg_users
count
user_id
is_course_admin
My goal is to use this table as metadata to feed into a large jinja loop that loops over each table_name and performs different aggregations (count, sum) depending on the metadata from that table above. I'd then union all of the table results together.
It might look something like this:
{% set results =
["count_of_courses", "courses", 'count'],
["count_of_bundles", "bundles", 'count'],
["count_of_products", "products", 'count'],
["sum_of_gmv", "stg_transactions", 'sum', 'revenue_usd'],
["count_of_course_admins", "users", 'count', 'user_id', 'is_course_admin is true', 'distinct']
%}
{% for i in results %}
SELECT
user_id,
{{ i[3] }}( --where i[3] refers to the agg_type (COUNT/SUM)
{%- if i[3] == 'sum' -%}
{{- i[4] -}}
{%- elif i[6] == 'distinct' -%}
distinct {{ i[4] }}
{%- else -%}
*
{%- endif -%}
FROM analytics.{{ i[2] }} -- where i[2] refers to the table name
{% if not loop.last %}
union all
{% endif %}
{% endfor %}
Looking for help on how to deal with agate tables, lists, etc. The source data is coming from a dbt seed (which is essentially a table) so I'll need a way to convert the table into a list of some sort.
I have been successful manually passing a list from the set operator in dbt (as shown above) but I'm not able to convert the dbt sedd/model into this list format (also shown below).
["count_of_courses", "courses", 'count'],
["count_of_bundles", "bundles", 'count'],
["count_of_products", "products", 'count'],
["sum_of_gmv", "stg_transactions", 'sum', 'revenue_usd'],
["count_of_course_admins", "users", 'count', 'user_id', 'is_course_admin is true', 'distinct']
I'd love to know how best I can translate the table (from above) into this list format so I can do all of this dynamically and not have to manually add to a list.
Thanks.

For future readers:
I experimented and researched a bit more and eventually landed on the solution of a generic macro which essentially stores SQL query results in a list. It looks like this:
{# This macro collects data from a query and inserts it into a list #}
{% macro query_to_list(query) %}
{% set query_to_process %}
{{ query }}
{% endset %}
{% set results = run_query(query_to_process) %}
{% if execute %}
{% set results_list = results.rows %}
{% else %}
{% set results_list = [] %}
{% endif %}
{{ return(results_list) }}
{% endmacro %}
Beyond this, I can now call the macro and store the results in a variable, iterate over them, etc. Here's what this might look like. I'm giving a simplified version but essentially, you should be able to parse any of the values from the query/variable and use them in magical ways:
Passing a Query into the Macro
{% set query %}
select id, name, table_name
from {{ ref( 'rollups' ) }}
order by id
{% endset %}
Iterating over the Query Results
{% for i in query_to_list(query) %}
{{ log(dbt_utils.pretty_log_format(i), info=True) }}
select
user_id,
'{{ i[2] }}' as table_name,
'{{ i[1] }}' as name
from {{ref( [i[2]]|join('') ) }} {# Extracts the name of the table. We use 'join' to convert it to a string #}
{# If there is a condition, we extract that here #}
{% if i[6] %}
where {{ i[6] }}
{% endif %}
group by 1, 2, 3, 4
{% if not loop.last %}
union all
{% endif %}
{% endfor %}

That's a smart way to build your models !
Maybe the best function for your use case would be run_query (ref here).
I can't test the code below but it should be something similar:
{% set fetch_operations_query %}
SELECT * FROM your.seed.table
{% endset %}
{% set results = run_query(fetch_operations_query) %}
{% if execute %}
{% set results_list = results.rows %}
{% else %}
{% set results_list = [] %}
{% endif %}
{% for row in result_list %}
SELECT
user_id,
{{ row[3] }}( --where row[3] refers to the agg_type (COUNT/SUM)
{%- if row[3] == 'sum' -%}
{{- row[4] -}}
{%- elif row[6] == 'distinct' -%}
distinct {{ row[4] }}
{%- else -%}
*
{%- endif -%}
FROM analytics.{{ row[2] }} -- where row[2] refers to the table name
{% if not loop.last %}
union all
{% endif %}
{% endfor %}

Related

Shopify - Add Number (1,2,3) before Product in Collection

I want the collection page to look like a numbered list (1. product 2. product 3. product ...) with a number in front of each product title.
In the product-grid-item.liquid I found the title and added {{ forloop.index }} in front of it but nothing shows up. When I add {% for product in collection.products %}{% endfor %} around it, it shows "123456789 title". When I add {% for product in collection.products %} before the top div and {% endfor %} after the bottom div, it repeats the whole collection multiple times.
When I use {% cycle '1', '2', '3', '4', '5' %} it shows "1." in front of every product.
What am I doing wrong? Are there other ways? Any help would be appreciated.
Okay, so this bellow code is for default Dawn theme from Shopify.
You need to edit the main-collection-product-grid.liquid and then navigate to code render 'card-product' and pass the foorloop.index as counter
on file card-product.liquid use it before the title
Need to add some logic to calculate correct number, add this code to calulcate it
{%- if paginate.pages > 1 -%}
{% assign page_size = paginate.page_size %}
{% if paginate.current_page > 1 %}
{% assign currPage = paginate.current_page | minus: 1 %}
{% assign loop_item = page_size | times: currPage %}
{% assign loop_item = loop_item | plus: forloop.index %}
{% else %}
{% assign loop_item = forloop.index %}
{% endif %}
{% else %}
{% assign loop_item = forloop.index %}
{% endif %}

How do I flatten an array of arrays in shopify liquid?

I've tried to loop through an array of arrays and capture the output using the below:
The array:
["cat1","cat2","cat3","cat4"] ["cat1","cat2"] ["cat4"] ["cat5"]
My attempt:
{% capture newCategories %}
{% for categoryArr in categories %}
{% for category in categoryArr %}
{{category}}
{% endfor %}
{% endfor %}
{% endcapture %}
newCategories --> // new array with items
and it still doesn't give me a flattened array, any ideas?
You should be able to leverage the concat operator: https://shopify.github.io/liquid/filters/concat/
Something like:
{% assign newCategories = '' | split '' %}
{% for categoryArr in categories %}
{% assign newCategories = newCategories | concat: categoryArr %}
{% endfor %}

In Liquid (Shopify) how am I able to get the index position of a specific array object?

In a snippet called 'sb' I have this content
{% assign seller_id = 'another_seller_shop_name,test_seller' | split: ',' %}
{% assign seller_html = 'Another Seller Desc,Seller Description' | split: ',' %}
In the template page - in this case, collection-list I have referenced this snippet
{% include 'sb' %}{% assign seller_id_page = collection.title | replace: ' ','_' | downcase %}
'seller_id_page' will equal to one of the values in 'seller_id'. I just want to be able to return the position of this value, so I can then assign seller_html[x] an index value and render field correctly.
You will need to loop the array and get the index of the corresponding equality.
In code:
{% for item in seller_id %}
{% if item == seller_id_page %}
{% assign position = forloop.index0 %}
{% break %}
{% endif %}
{% endfor %}
{{ seller_html[position] }}
That's the just of it.

Dynamically populate a forloop array in Shopify liquid framework

I'm trying to create my own array in Shopify so that I can easily populate a slick slider. I've tried creating a numeric array and then using that to call the variables, but it doesn't seem to be working. I just keep getting "unexpected character" etc.
{% assign number_group = "1, 2, 3, 4, 5" | split: ','%}
{%- assign block_1_link = "/collections/col-1" %}
{%- assign block_1_title = "Title 1" %}
{%- assign block_1_img = "imglink" %}
{%- assign block_2_link = "/collections/col-2" %}
{%- assign block_2_title = "Title 2" %}
{%- assign block_2_img = "imglink" %}
<div class="slick-slider-row">
<div class="wrapper">
<h2>MyCompants<h2>
{% for NUMBER in number_group %}
{%- assign this_link = "block_"{{NUMBER}}"_link" %}
{%- assign this_image = "block_"{{NUMBER}}"_img" %}
{%- assign this_title = "block_"{{NUMBER}}"_title" %}
<div class="responsive carousel slider-mobile-hide">
<div class="slick-slide">
<a href="{{this_link}}">
<img class="lazyload" alt="{{ this_title}}" src="{{
this_image}}">
</a>
<p><a class="slider-collection-name-link" href="
{{this_link}}">{{this_title}}</a></p>
</div>
{% endfor %}
I assume that you are new in the liquid language, so I will describe it as clearly as possible.
For loops
There are two types of FOR loops in liquid.
1) The standard one where you list all items of an array:
{% for product in collection.products %}
Where collection.products is an array of products.
2) The manual array which you are trying to build
{% for i in (1..5) %}
Where 1 is the starting index and 5 is the ending.
Liquid syntax
There are two types of liquid syntax.
{{ }}
and
{% %}
The {{ }} is used for output of content and {% %} is used for liquid logic (if/assign/capture/for etc...)
Mistakes in your code
{%- assign this_link = "block_"{{NUMBER}}"_link" %}
This is an invalid syntax. You can't have liquid inside of liquid. If you want to do it like that, the proper way will be:
{%- assign this_link = "block_" | append: NUMBER | append: "_link" %}
or
{% capture this_link %}block_{{NUMBER}}_link{% endcapture %}
In addition this {{this_link}} will output the string "block_1_link" and not the variable block_1_link.
In order to output the variable you will need to wrap it in square brackets like so {{[this_link]}} since you are assign a string to it.
I assume you are learning but this syntax will probably never come in a real project, because you shouldn't have static variables
How should your code look
{%- assign block_1_link = "/collections/col-1" %}
{%- assign block_1_title = "Title 1" %}
{%- assign block_1_img = "imglink" %}
{%- assign block_2_link = "/collections/col-2" %}
{%- assign block_2_title = "Title 2" %}
{%- assign block_2_img = "imglink" %}
<div class="slick-slider-row">
<div class="wrapper">
<h2>MyCompants<h2>
{% for NUMBER in (1..5) %}
{%- assign this_link = "block_" | append: NUMBER | append: "_link" %}
{%- assign this_image = "block_" | append: NUMBER | append: "_img" %}
{%- assign this_title = "block_" | append: NUMBER | append: "_title" %}
<div class="responsive carousel slider-mobile-hide">
<div class="slick-slide">
<a href="{{[this_link]}}">
<img class="lazyload" alt="{{ [this_title] }}" src="{{ [this_image] }}">
</a>
<p><a class="slider-collection-name-link" href="
{{[this_link]}}">{{[this_title]}}</a></p>
</div>
{% endfor %}

How do i array a collection in 3 cols in liquid by a status

I wanna array my collection in 3 cols, for 3 different statuses. And if there isn't any gig/item with that status, it has to say ' No projects'
I have tried this:
<div class="col-sm">
<h2>Up next</h2>
{% assign next = site.gigs | gig.status == 'Next' | sort: gig.date %}
{% if next.gigs.size == 0 %}
No projects
{% else %}
{% for gig in next %}
{{ gig.title }}
{% endfor %}
{% endif %}
</div>
<div class="col-sm">
<h2>Working on</h2>
{% assign on = site.gigs | gig.status == 'On' | sort: gig.date %}
{% if on.gigs.size == 0 %}
No projects
{% else %}
{% for gig in on %}
{{ gig.title }}
{% endfor %}
{% endif %}
</div>
<div class="col-sm">
<h2>Done</h2>
{% assign done = site.gigs | gig.status == 'Done' | sort: gig.date %}
{% if done.gigs.size == 0 %}
No projects
{% else %}
{% for gig in done %}
{{ gig.title }}
{% endfor %}
{% endif %}
</div>
But it just arrays all of the gigs/items :(
Maybe it can be done in a way more simple way.
I don't know if you could make one compact liquid code and array 3 columns by counting the number of different statuses.
Help!
Ordering
Our statuses are "Next", "On" and "Done". They must be ordered in such order.
As this is not an alphabetical sort order, we need to define this order by ourself.
In _config.yml :
status-order:
-
name: "Next" ### status as it is set on gigs (!!! Case-Sensitive !!!)
display: "Up Next" ### status column header to display
-
name: "On"
display: "Working On"
-
name: "Done"
display: "Done"
We can now loop over site.status-order and get our gigs in desired status order.
{% for status in site.status-order %}
{{ status.name }} - {{ status.display }}
{% endfor %}
Presenting
As your current code is a little repetitive, we can factor it like this :
{% for status in site.status-order %}
{% assign items = site.gigs | where: 'status', status.name | sort: date %}
<div class="col-sm">
<h2>{{ status.display }} ({{ items.size }})</h2>
{% if items.size > 0 %}
<ul>
{% for item in items %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% else %}
No project
{% endif %}
</div>
{% endfor %}
Note
You must be sure to set status with the right case (eg : "Next" an not "next").
And the right type. If you set status: On it is understood as the true boolean value, not the string "on". In this case the correct status expression is status: "On". It must be quoted or double quoted to be understood as "On" string.
Any item with incorrectly cased or typed status expression will not appear in our listing.

Resources