Strapi GROUP BY and COUNT fields with the same value - api

Is there any way in strapi to group by entries with the same fields and count its total?
Trying to create a Poll App which has "Response" Collection containing an "Answer" Field (enum: a, b, c, d). Would like to group responses with the same answers. Something like this:
{
"answer": "a",
"total": 3
}, {
"answer": "b",
"total": 1
}
Is it possible out of the box?
To give more context, here's its sql counterpart:
select *, count(answers) from responses group by answers

there is no known default way for groupby with entity service, however there is count query:
/src/answer/controllers/answer.js
const { createCoreController } = require("#strapi/strapi").factories;
module.exports = createCoreController("api::answer.answer", ({ strapi }) => ({
async find(ctx) {
let { query } = ctx;
let answers = await strapi.db.query("api::answer.answer").findMany({
...query,
});
answers = await Promise.all(answers.map(async (answer) => ({
...answer,
total: await strapi.db.query("api::answer.answer").count({where: '...'})
})))
return answers
},
}));
or you can use raw query like this:
let { rows } = await strapi.db.connection.raw(
`select id from posts where published_at IS NOT null order by random() limit ${count};
`);
or
let { rows } = await strapi.db.connection.raw(
`select *, count(answers) from responses group by answers;`);

Related

How to get aggregate count with date attribute via linq

I have a table of data where I'm recording hits on a website. The table tracks the timestamp of the hit as well as the source. I want to pull hits per day, with the counts categorized by the source. So the result would be something like
{
google: {
{ 01/01/2000, 123 }, // date, count of hits for that date
{ 01/02/2000, 134 },
{ 01/03/2000, 223 },
{ 01/04/2000, 145 },
},
yahoo: {
{ 01/01/2000, 223 },
{ 01/02/2000, 434 },
{ 01/03/2000, 123 },
{ 01/04/2000, 135 },
}
}
Is there a way to get this data aggregated in this format using linq? At the moment I can get the aggregate totals of the source / count using
var counts = tableName.GroupBy(x => x.ColumnId)
.Select(g => new { g.Key, Count = g.Count() });
but adding the additional date attribute is eluding me.
You need to perform grouping again by date:
var counts = tableName
.GroupBy(x => x.ColumnId)
.Select(g => new
{
Category = g.Key,
Items = g.GroupBy(x => x.Date.Date)
.Select(x => new
{
Date = x.Key,
Count = x.Count()
})
.ToList()
});

Loopback 4 - How to find the highest value in column (Like SELECT MAX(column) from Table)?

I want to find the highest value in a specific column of a specific table. It should be very simple.
this is the documentation of LB4 https://loopback.io/doc/en/lb2/Where-filter But I didn't find it there.
We did this through a custom repository method where we execute a select max() query and have a custom controller method (i.e. /next-id) that calls it.
Repository method:
async nextId(): Promise<any> {
return this.dataSource
.execute('select MAX(id)+5 as nextId from route_lookup')
.then(data => {
if (data[0].NEXTID === null) {
data[0].NEXTID = 1005;
}
return data[0].NEXTID;
});
}
Controller method:
#get('/route-lookups/next-id')
#response(200, {
description: 'Next avaialble id for route lookup',
content: {
'application/json': {
schema: {
type: 'number',
},
},
},
})
async nextId(): Promise<number> {
return await this.routeLookupRepository.nextId();
}
Within the Loopback Filter Documentation they do mention a way to achieve this, even though it's not as obvious.
/weapons?filter[where][effectiveRange][gt]=900&filter[limit]=3
Essentially you can do the following:
Identify the column of interest.
Use the gt operator to set a min number
Add order if you wanted to ensure the sorting order is as expected.
Limit the results to 1.
Here is a code example:
Employees.find({
where: {
age: {
gt: 1
}
},
order: 'age ASC',
limit: 1
})
Please let me know if this is what you were going for or if you need some more support.

How to show generated SQL / raw SQL in TypeORM queryBuilder

I developed typeorm querybuilder. For the purpose of debugging, I'd like to show the generated SQL query.
I tested printSql() method, but it didn't show any SQL query.
const Result = await this.attendanceRepository
.createQueryBuilder("attendance")
.innerJoin("attendance.child", "child")
.select(["attendance.childId","child.class","CONCAT(child.firstName, child.lastName)"])
.where("attendance.id= :id", { id: id })
.printSql()
.getOne()
console.log(Result);
It returned the following:
Attendance { childId: 4, child: Child { class: 'S' } }
My desired result is to get the generated SQL query.
Is there any wrong point? Is there any good way to get the SQL query?
.getQuery() or .getSql()
const sql1 = await this.attendanceRepository
.createQueryBuilder("attendance")
.innerJoin("attendance.child", "child")
.select(["attendance.childId","child.class","CONCAT(child.firstName, child.lastName)"])
.where("attendance.id= :id", { id: id })
.getQuery();
console.log(sql1);
const sql2 = await this.attendanceRepository
.createQueryBuilder("attendance")
.innerJoin("attendance.child", "child")
.select(["attendance.childId","child.class","CONCAT(child.firstName, child.lastName)"])
.where("attendance.id= :id", { id: id })
.getSql();
console.log(sql2);
printSql can also be used, but it will only print when logging is enabled.
#Module({
imports: [
TypeOrmModule.forRoot({
...options
logging: true
}),
],
})
await this.attendanceRepository
.createQueryBuilder("attendance")
.innerJoin("attendance.child", "child")
.select(["attendance.childId","child.class","CONCAT(child.firstName, child.lastName)"])
.where("attendance.id= :id", { id: id })
.printSql();

Filtering 2 or more categories in vue.js

I have a code where i filtered an array with a specific category.
response.data.items.filter(item => item.category_id === categ_id_1)
Now i want to add more categories in the filter (categ_id_2, categ_id_3). How do i do that?
Assuming the category_id is a primitive (ie string, number, etc), the easiest way I can think of is to maintain a Set of wanted categories and use Set.prototype.has() for filtering.
const categories = new Set(['categ_id_1', 'categ_id_2', ...])
response.data.items.filter(({ category_id }) => categories.has(category_id))
If you're wanting the list to be reactive in Vue, something like this...
data: () => ({
categoryFilters: ['categ_id_1', 'categ_id_2']
}),
computed: {
categoryFilterSet () {
return new Set(this.categoryFilters)
}
},
methods: {
async loadAndFilterData () {
const response = await axios({...}) // just guessing
const filtered = response.data.items.filter(({ category_id }) =>
this.categoryFilterSet.has(category_id))
}
}
The reason for using the computed property is that Vue does not work reactively with Set so you must back it up with an array.
Another way using Array.prototype.includes() which is used to pass/fail the test of filter for each element.
const items = [{
"a": 1,
"category_id": "categ_id_1"
}, {
"a": 2,
"category_id": "categ_id_2"
}, {
"a": 3,
"category_id": "categ_id_3"
}, {
"a": 4,
"category_id": "categ_id_4"
}, {
"a": 5,
"category_id": "categ_id_5"
}];
const search = ["categ_id_2", "categ_id_3"];
const result = items.filter(e => search.includes(e.category_id));
console.info("result::", result);
Try it as
response.data.items.filter(item => item.category_id === categ_id_1 ||item.category_id === categ_id_2 ||item.category_id === categ_id_3)

Duplicate items in list after an API update

I'm learning vuejs and I'm doing a weather app, the goal is to rank cities with an index (humidex). I fetch weather information by API (axios) in order to collect data from several cities. I want to auto update data every x minutes, problem : some of my results are duplicated (the new data don't replace the old one).
I tried to set an unique key (based on latitude and longitude) for each item, it works for several results but not for all.
data () {
return {
items:[],
show: false,
cities: cities,
newCity:''
}
},
components: {
Item
},
computed: {
sortHumidex() {
return this.items.slice().sort((a,b) => {
return this.getHumidex(b) - this.getHumidex(a) || b.current.temp_c - a.current.temp_c
})
}
},
methods: {
addCity() {
if (this.newCity.trim().length == 0) {
return
}
this.cities.push(this.newCity)
this.newCity = ''
},
getHumidex: (el) => {
const e = 6.112 * Math.pow(10,(7.5*el.current.temp_c/(237.7+el.current.temp_c)))
*(el.current.humidity/100)
return Math.round(el.current.temp_c + 5/9 * (e-10))
},
indexGeo: (e) => {
const lat = Math.round(Math.abs(e.location.lat))
const lon = Math.round(Math.abs(e.location.lon))
return lat.toString() + lon.toString()
},
getApi: function () {
const promises = [];
this.cities.forEach(function(element){
const myUrl = apiUrl+element;
promises.push(axios.get(myUrl))
});
let self = this;
axios
.all(promises)
.then(axios.spread((...responses) => {
responses.forEach(res => self.items.push(res.data))
}))
.catch(error => console.log(error));
}
},
created() {
this.getApi()
this.show = true
}
}
The render when I update API :
By pushing to the existing array of items, you have to deal with the possibility of duplicates. This can be eliminated simply by replacing items every time the API call is made.
Replace:
responses.forEach(res => self.items.push(res.data))
with:
self.items = responses.map(res => res.data)