컬렉션에서 새로운 Firebase 데이터베이스 인 Cloud Firestore를 사용하는 항목 수를 계산할 수 있습니까?
그렇다면 어떻게해야합니까?
답변
업데이트 (2019 년 4 월)-FieldValue.increment (큰 수집 솔루션 참조)
많은 질문과 마찬가지로, 대답은 – 그것은 의존한다 .
프런트 엔드에서 대량의 데이터를 처리 할 때는 매우주의해야합니다. Firestore는 프론트 엔드를 느리게 만드는 것 외에도 백만 번의 읽기 당 0.60 달러를 청구합니다 .
소장 (100 개 미만의 서류)
주의해서 사용-프론트 엔드 사용자 경험에 영향을 줄 수 있음
이 반환 된 배열에 대해 너무 많은 논리를 수행하지 않는 한 프런트 엔드에서이를 처리하는 것이 좋습니다.
db.collection('...').get().then(snap => {
size = snap.size // will return the collection size
});
중간 수집 (100 ~ 1000 개 문서)
주의해서 사용-Firestore 읽기 호출에는 많은 비용이 소요될 수 있습니다
프런트 엔드에서이를 처리하는 것은 사용자 시스템 속도를 늦출 가능성이 너무 높기 때문에 실현 불가능합니다. 이 논리 서버 측을 처리하고 크기 만 반환해야합니다.
이 방법의 단점은 여전히 Firestore 읽기 (컬렉션 크기와 동일)를 호출하는 것입니다. 장기적으로는 예상보다 많은 비용이 발생할 수 있습니다.
클라우드 기능 :
...
db.collection('...').get().then(snap => {
res.status(200).send({length: snap.size});
});
프런트 엔드 :
yourHttpClient.post(yourCloudFunctionUrl).toPromise().then(snap => {
size = snap.length // will return the collection size
})
대규모 수집 (1000 개 이상의 문서)
가장 확장 가능한 솔루션
FieldValue.increment ()
2019 년 4 월부터 Firestore는 이제 이전의 데이터를 읽지 않고도 카운터를 완전히 원자 적으로 증가시킬 수 있습니다 . 이를 통해 여러 소스에서 동시에 업데이트 할 때 (이전에 트랜잭션을 사용하여 해결 한 경우) 올바른 카운터 값을 유지하면서 수행하는 데이터베이스 읽기 수를 줄일 수 있습니다.
문서의 삭제 또는 생성을 수신함으로써 데이터베이스에있는 카운트 필드에 추가하거나 제거 할 수 있습니다.
firestore 문서- 분산 카운터를 참조
하거나 Jeff Delaney의 데이터 집계 를 살펴보십시오 . 그의 가이드는 AngularFire를 사용하는 모든 사람에게 정말 환상적이지만 그의 교훈은 다른 프레임 워크에도 적용됩니다.
클라우드 기능 :
export const documentWriteListener =
functions.firestore.document('collection/{documentUid}')
.onWrite((change, context) => {
if (!change.before.exists) {
// New document Created : add one to count
db.doc(docRef).update({numberOfDocs: FieldValue.increment(1)});
} else if (change.before.exists && change.after.exists) {
// Updating existing document : Do nothing
} else if (!change.after.exists) {
// Deleting document : subtract one from count
db.doc(docRef).update({numberOfDocs: FieldValue.increment(-1)});
}
return;
});
이제 프론트 엔드에서이 numberOfDocs 필드를 쿼리하여 컬렉션의 크기를 얻을 수 있습니다.
답변
가장 간단한 방법은 “querySnapshot”의 크기를 읽는 것입니다.
db.collection("cities").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});
“querySnapshot”내 문서 배열의 길이를 읽을 수도 있습니다.
querySnapshot.docs.length;
또는 빈 값을 읽어 “querySnapshot”이 비어 있으면 부울 값이 반환됩니다.
querySnapshot.empty;
답변
내가 아는 한 여기에는 내장 솔루션이 없으며 노드 sdk에서만 가능합니다. 당신이 있다면
db.collection('someCollection')
당신이 사용할 수있는
.select([fields])
선택할 필드를 정의합니다. 빈 select ()를 수행하면 문서 참조 배열을 얻을 수 있습니다.
예:
db.collection('someCollection').select().get().then(
(snapshot) => console.log(snapshot.docs.length)
);
이 솔루션은 모든 문서를 다운로드하는 최악의 경우를위한 최적화 일 뿐이며 대규모 컬렉션에는 적용되지 않습니다!
또한 이것 좀 봐 :
클라우드 경우 FireStore와 컬렉션의 문서 수의 수를 얻는 방법
답변
큰 컬렉션 의 문서 수를 세어주의하십시오 . 모든 컬렉션에 대해 미리 계산 된 카운터를 원하면 firestore 데이터베이스와 약간 복잡합니다.
이 경우 다음과 같은 코드가 작동하지 않습니다.
export const customerCounterListener =
functions.firestore.document('customers/{customerId}')
.onWrite((change, context) => {
// on create
if (!change.before.exists && change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count + 1
}))
// on delete
} else if (change.before.exists && !change.after.exists) {
return firestore
.collection('metadatas')
.doc('customers')
.get()
.then(docSnap =>
docSnap.ref.set({
count: docSnap.data().count - 1
}))
}
return null;
});
https://firebase.google.com/docs/functions/firestore-events#limitations_and_guarantees : Firestore 문서에서 알 수 있듯이 모든 클라우드 Firestore 트리거는 dem 등원이어야하기 때문입니다.
해결책
따라서 코드가 여러 번 실행되지 않도록하려면 이벤트 및 트랜잭션으로 관리해야합니다. 이것은 대규모 수집 카운터를 처리하는 특별한 방법입니다.
const executeOnce = (change, context, task) => {
const eventRef = firestore.collection('events').doc(context.eventId);
return firestore.runTransaction(t =>
t
.get(eventRef)
.then(docSnap => (docSnap.exists ? null : task(t)))
.then(() => t.set(eventRef, { processed: true }))
);
};
const documentCounter = collectionName => (change, context) =>
executeOnce(change, context, t => {
// on create
if (!change.before.exists && change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: ((docSnap.data() && docSnap.data().count) || 0) + 1
}));
// on delete
} else if (change.before.exists && !change.after.exists) {
return t
.get(firestore.collection('metadatas')
.doc(collectionName))
.then(docSnap =>
t.set(docSnap.ref, {
count: docSnap.data().count - 1
}));
}
return null;
});
사용 사례는 다음과 같습니다.
/**
* Count documents in articles collection.
*/
exports.articlesCounter = functions.firestore
.document('articles/{id}')
.onWrite(documentCounter('articles'));
/**
* Count documents in customers collection.
*/
exports.customersCounter = functions.firestore
.document('customers/{id}')
.onWrite(documentCounter('customers'));
보다시피, 다중 실행을 방지하는 핵심 은 컨텍스트 객체에서 eventId 라는 속성 입니다. 동일한 이벤트에 대해 함수가 여러 번 처리 된 경우 이벤트 ID는 모든 경우에 동일합니다. 불행히도 데이터베이스에는 “이벤트”컬렉션이 있어야합니다.
답변
2020 년에는 여전히 Firebase SDK에서 사용할 수 없지만 Firebase 확장 (베타) 에서는 사용할 수 있지만 설정 및 사용이 매우 복잡합니다.
합리적인 접근
도우미 … (만들기 / 삭제가 중복 된 것처럼 보이지만 onUpdate보다 저렴함)
export const onCreateCounter = () => async (
change,
context
) => {
const collectionPath = change.ref.parent.path;
const statsDoc = db.doc("counters/" + collectionPath);
const countDoc = {};
countDoc["count"] = admin.firestore.FieldValue.increment(1);
await statsDoc.set(countDoc, { merge: true });
};
export const onDeleteCounter = () => async (
change,
context
) => {
const collectionPath = change.ref.parent.path;
const statsDoc = db.doc("counters/" + collectionPath);
const countDoc = {};
countDoc["count"] = admin.firestore.FieldValue.increment(-1);
await statsDoc.set(countDoc, { merge: true });
};
export interface CounterPath {
watch: string;
name: string;
}
Firestore 후크 내보내기
export const Counters: CounterPath[] = [
{
name: "count_buildings",
watch: "buildings/{id2}"
},
{
name: "count_buildings_subcollections",
watch: "buildings/{id2}/{id3}/{id4}"
}
];
Counters.forEach(item => {
exports[item.name + '_create'] = functions.firestore
.document(item.watch)
.onCreate(onCreateCounter());
exports[item.name + '_delete'] = functions.firestore
.document(item.watch)
.onDelete(onDeleteCounter());
});
행동
건물 루트 수집 및 모든 하위 수집 이 추적됩니다.
여기 /counters/
루트 경로 아래
이제 수집 횟수가 자동으로 업데이트됩니다! 카운트가 필요한 경우 컬렉션 경로를 사용하고 접두사를 붙입니다 counters
.
const collectionPath = 'buildings/138faicnjasjoa89/buildingContacts';
const collectionCount = await db
.doc('counters/' + collectionPath)
.get()
.then(snap => snap.get('count'));
답변
@Matthew에 동의 합니다. 이러한 쿼리를 수행하면 비용이 많이 듭니다 .
[프로젝트를 시작하기 전에 개발자를위한 조언]
우리는이 상황을 처음에 예상 했으므로 실제로 모든 카운터를 type 필드에 저장하기 위해 문서와 함께 카운터를 만들 수 있습니다 number
.
예를 들면 다음과 같습니다.
콜렉션의 각 CRUD 조작에 대해 카운터 문서를 업데이트하십시오.
- 새로운 컬렉션 / 서브 컬렉션 을 생성 할 때 : (카운터에서 +1) [1 쓰기 작업]
- 컬렉션 / 서브 컬렉션 을 삭제 하는 경우 : (카운터에서 -1) [1 쓰기 작업]
- 기존 수집 / 하위 수집 을 업데이트 할 때는 카운터 문서에서 아무것도하지 마십시오. (0)
- 때를 기존 수집 / 하위 수집 읽을 카운터 문서에서 아무것도하지 마십시오 : (0)
다음에 컬렉션 수를 얻으려면 문서 필드를 쿼리하거나 가리켜 야합니다. [1 회 읽기 동작]
또한 컬렉션 이름을 배열에 저장할 수 있지만 까다로울 수 있습니다. firebase의 배열 조건은 다음과 같습니다.
// we send this
['a', 'b', 'c', 'd', 'e']
// Firebase stores this
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
// since the keys are numeric and sequential,
// if we query the data, we get this
['a', 'b', 'c', 'd', 'e']
// however, if we then delete a, b, and d,
// they are no longer mostly sequential, so
// we do not get back an array
{2: 'c', 4: 'e'}
따라서 컬렉션을 삭제하지 않으려는 경우 실제로 모든 컬렉션을 쿼리 할 때마다 배열을 사용하여 컬렉션 목록 이름을 저장할 수 있습니다.
그것이 도움이되기를 바랍니다!
답변
아니요, 현재 집계 쿼리를 기본적으로 지원하지 않습니다. 그러나 할 수있는 일이 몇 가지 있습니다.
첫 번째는 여기 에 문서화되어 있습니다 . 트랜잭션 또는 클라우드 기능을 사용하여 집계 정보를 유지 보수 할 수 있습니다.
이 예는 함수를 사용하여 하위 등급의 등급 수와 평균 등급을 추적하는 방법을 보여줍니다.
exports.aggregateRatings = firestore
.document('restaurants/{restId}/ratings/{ratingId}')
.onWrite(event => {
// Get value of the newly added rating
var ratingVal = event.data.get('rating');
// Get a reference to the restaurant
var restRef = db.collection('restaurants').document(event.params.restId);
// Update aggregations in a transaction
return db.transaction(transaction => {
return transaction.get(restRef).then(restDoc => {
// Compute new number of ratings
var newNumRatings = restDoc.data('numRatings') + 1;
// Compute new average rating
var oldRatingTotal = restDoc.data('avgRating') * restDoc.data('numRatings');
var newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;
// Update restaurant info
return transaction.update(restRef, {
avgRating: newAvgRating,
numRatings: newNumRatings
});
});
});
});
jbb가 언급 한 솔루션은 문서를 자주 계산하지 않으려는 경우에도 유용합니다. select()
각 문서를 모두 다운로드하지 않도록 명령문 을 사용하십시오 (카운트 만 필요할 때 많은 대역폭이 필요함). select()
솔루션은 모바일 앱에서 작동하지 않도록 지금은 서버 SDK에서만 사용할 수 있습니다.
