sql to mongodb translation - sql

I wonder how we can do the below translation from sql to mongoDB:
Assume the table has below structure:
table
=====
-----
##id contribution time
1 300 Jan 2, 1990
2 1000 March 3, 1991
And I want to find a ranking list of ids in the descending orders of their number of contributions.
'$' This is what I do using sql:
select id, count(*) c from table group by id order by c desc;
How can I translate this complex sql into mongoDB using count(), order() and group()?
Thank you very much!

Setting up test data with:
db.donors.insert({donorID:1,contribution:300,date:ISODate('1990-01-02')})
db.donors.insert({donorID:2,contribution:1000,date:ISODate('1991-03-03')})
db.donors.insert({donorID:1,contribution:900,date:ISODate('1992-01-02')})
You can use the new Aggregation Framework in MongoDB 2.2:
db.donors.aggregate(
{ $group: {
_id: "$donorID",
total: { $sum: "$contribution" },
donations: { $sum: 1 }
}},
{ $sort: {
donations: -1
}}
)
To produce the desired result:
{
"result" : [
{
"_id" : 1,
"total" : 1200,
"donations" : 2
},
{
"_id" : 2,
"total" : 1000,
"donations" : 1
}
],
"ok" : 1
}

Check the mongodb Aggregation
db.colection.group(
{key: { id:true},
reduce: function(obj,prev) { prev.sum += 1; },
initial: { sum: 0 }
});
After you get the result, sort it by sum.

Related

Mongo Group By query

I have data stored in a Mongo collection that is structured like this:
{
"numberAtPending" : 3,
"numberAtInProgress" : 5,
"numberAtCancelled" : 1,
"numberAtShipped" : 50,
"timeOfRequest" : ISODate("2022-01-10T12:52:15.813Z"),
"requestingSupplier" : "SUPPLIER_1",
},
{
"numberAtPending" : 5,
"numberAtInProgress" : 3,
"numberAtCancelled" : 4,
"numberAtShipped" : 35,
"timeOfRequest" : ISODate("2022-01-15T09:11:02.992Z"),
"requestingSupplier" : "SUPPLIER_1",
},
{
"numberAtPending" : 12,
"numberAtInProgress" : 3,
"numberAtCancelled" : 1,
"numberAtShipped" : 21,
"timeOfRequest" : ISODate("2022-01-10T14:21:55.221Z"),
"requestingSupplier" : "SUPPLIER_2",
}
I wish to construct a query that would let me sum up each count in each entry and group by requestingSupplier.
For example, I would like to answer the question, for the month of January '22, what was the sum of each entity and get a response similar to:-
"TotalNumberAtPending": 300
"TotalNumberAtInProgress" : 150,
"TotalNumberAtCancelled" : 70,
"TotalNumberAtShipped" : 400
"Supplier" : "SUPPLIER_1",
"TotalNumberAtPending": 230
"TotalNumberAtInProgress" : 110,
"TotalNumberAtCancelled" : 40,
"TotalNumberAtShipped" : 300
"Supplier" : "SUPPLIER_2",
Any help most appreciated!
thanks and regards
You can try this query (also I'm assuming the output you show is an example and not the real values because I don't know from where can you get 300, 150, 400...)
So, try this:
You have two options to match values in the range of two dates. If you want to input the name of the month and the year you can try something like:
Use $expr and $eq with $year and $month. And then you can use as input exaclty the number of the desired month or year.
{
"$match": {
"$expr": {
"$and": [
{
"$eq": [
{
"$month": "$timeOfRequest"
},
1
]
},
{
"$eq": [
{
"$year": "$timeOfRequest"
},
2022
]
}
]
}
}
}
Or you can match by the date range. If you want to get all documents from 01-2022 you can use this $match stage where the range is from the first second of January (equal) to the first second of February (not equal, so i.e. is the last second of January).
{
"$match": {
"timeOfRequest": {
"$gte": ISODate("2022-01-01T00:00:00Z"),
"$lt": ISODate("2022-02-01T00:00:00Z")
}
}
}
So, with the filter done you only need to use $group like this to generate the desired fields values.
{
"$group": {
"_id": "$requestingSupplier",
"TotalNumberAtPending": {
"$sum": "$numberAtPending"
},
"TotalNumberAtInProgress": {
"$sum": "$numberAtInProgress"
},
"TotalNumberAtCancelled": {
"$sum": "$numberAtCancelled"
},
"TotalNumberAtShipped": {
"$sum": "$numberAtShipped"
},
"Supplier": {
"$first": "$requestingSupplier"
}
}
}
Example here and here

How to convert sql query with exist into mongodb query

I have two documents on mongodb, these are percentages and items. I'm good at SQL, I can write PLSql query as follows but i can not convert to mongodb query. Because my mongodb level of knowledge is at the beginning. Actually I know I have to use $gt for the and condition. But I don't know how I can say not exists or union keyword for mongodb. How can I write mongodb query? which keywords should i search for?
select p.*, "to_top" as list
from percentages p
where p.percentage > 5
and p.updatetime > sysdate - 1/24
and not exists (select 1
from items i
where i.id = p.p_id
and i.seller = p.seller)
order by p.percentage desc
union
select p2.*, "to_bottom" as list
from percentages p2
where p2.percentage > 5
and p2.updatetime > sysdate - 1/24
and exists (select 1
from items i2
where i2.id = p2.p_id
and i2.seller = p2.seller)
order by p2.percentage desc
There is no UNION for MongoDB. Luckely, each query is performed on the same collection and have very close condition, so we can implement "Mongo way" query.
Explanation
Normally, alsmost all complex SQL queries are done with the MongoDB aggregation framework.
We filter document by percentage / updatetime. Explanation why we need to use $expr
SQL JOIN / Subquery is done with the $lookup operator.
SQL SYSDATE in MongoDB way can be NOW or CLUSTER_TIME variable.
db.percentages.aggregate([
{
$match: {
percentage: { $gt: 5 },
$expr: {
$gt: [
"$updatetime",
{
$subtract: [
ISODate("2020-06-14T13:00:00Z"), //Change to $$NOW or $$CLUSTER_TIME
3600000
]
}
]
}
}
},
{
$lookup: {
from: "items",
let: {
p_id: "$p_id",
seller: "$seller"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [ "$$p_id", "$id"]
},
{
$eq: [ "$$seller", "$seller"]
}
]
}
}
},
{
$limit: 1
}
],
as: "items"
}
},
{
$addFields: {
list: {
$cond: [
{
$eq: [{$size: "$items"}, 0]
},
"$to_top",
"$to_bottom"
]
},
items: "$$REMOVE"
}
},
{
$sort: { percentage: -1 }
}
])
MongoPlayground
Note: The MongoDB aggregation has the $facet operator that allows to perform different queries on the same collection.
SCHEMA:
db.percentages.aggregate([
{$facet:{
q1:[...],
q2:[...],
}},
//We apply "UNION" the result documents for each pipeline into single array
{$project:{
data:{$concatArrays:["$q1","$q2"]}
}},
//Flatten array into single object
{$unwind:"$data"}
//Replace top-level document
{$replaceWith:"$data"}
])
MongoPlayground
why you don't import your mangoDB data into oracle and use sql(that is more easy and powerful than mango.)

How do I sum up two fields in the Mongoshell?

I have a find statement in a store database that looks like this:
db.Purchases.find( {}, { store: 1, total: 1, _id: 0 } ).sort( { "store" : 1} ) =
{ "store" : DBRef("Location", ObjectId("5dae22702486f7d89ba7633c")), "total" : "$1500" }
{ "store" : DBRef("Location", ObjectId("5dae227f2486f7d89ba7633d")), "total" : "$156.88" }
{ "store" : DBRef("Location", ObjectId("5dae22992486f7d89ba7633e")), "total" : "$1510" }
{ "store" : DBRef("Location", ObjectId("5dae22992486f7d89ba7633e")), "total" : "$3000" }
{ "store" : DBRef("Location", ObjectId("5dae22cd2486f7d89ba76340")), "total" : "$156.88" }
I need to sum the totals from output 3 and 4 (i.e $1510 and $3000) and display the result as one line in the output along all the other outputs. How do I do this?
Try this:
db.Purchases.aggregate(
[
{
$group:
{
_id: "$store",
totalAmount: { $sum: "$total"}
}
}
]
)
Observation: total must be a numeric type in MongoDB.

How to implement the follow RMDB query in MongoDB

My team started to use MongoDB now and wanna migrate some sql to Mongo.
For example, I have an order table and has the fields price and quanty.I want to query the price*quanty greater than 100. sql is like below
select * from Order where price * quanty > 100;
How to use "price * quanty" this kind query in Mongo?
Thanks.
You can do this by using the $expr operator to use aggregation expressions within your query:
db.orders.find({
$expr: {
$gt: [
{ $multiply: ["$price", "$quantity"] },
100
]
}
})
As JohnnyHK points out you can use $expr, but as an alternative you can also use aggregation to first create a new field that is the product of two other fields:
db.orders.aggregate([
{ $set: { product: { $multiply: [ "$price", "$quantity" ] } } }
])
Note: $set is new in 4.2 and just an alias for $addFields
Then add a $match stage that only matches documents with the new product field meeting your condition:
db.orders.aggregate([
{ $set: { product: { $multiply: [ "$price", "$quantity" ] } } },
{ $match: { product: { $gt: 100 } } }
])

filter data on jsonb with postgres

imagine you have website where people post their ads. So, each ad has some selected properties, for example cars has different engine types, gears, colors and etc. Those properties user selects before submiting a listing.
I store selected properties in a jsonb format in listings table, look at the data column:
.
So, each listing contains data like this:
{
"properties":[
{
"id":"1",
"value_id":"1"
},
{
"id":"2",
"value_id":"5"
},
{
"id":"3",
"value_id":"9"
},
{
"id":"4",
"value":"2.0"
},
{
"id":"7",
"value":"2017"
},
{
"id":"6",
"value":"180.000"
}
]
}
Now, the question is:
1) How to filter listings by those id's and value's which are in json? For example, show listings where id = 2 and it's value = 5 AND id = 3 and it's value = 9 and so on. I dont need OR, i need AND. So, filter data by multiple id's and value's.
2) First point + ability to compare id's and value's (greater or lower than).
answering first point, it's probably the first time when I find use for jsonb[]:
t=# with c(a,j) as (values(18,'{
"properties":[
{
"id":"1",
"value_id":"1"
},
{
"id":"2",
"value_id":"5"
},
{
"id":"3",
"value_id":"9"
},
{
"id":"4",
"value":"2.0"
},
{
"id":"7",
"value":"2017"
},
{
"id":"6",
"value":"180.000"
}
]
}'::jsonb), (19,'{"properties":[{"id": "1", "value_id": "1"}]}'))
, m as (select a, array_agg(jb.value)::jsonb[] ar from c, jsonb_array_elements(j->'properties') jb group by a)
select a
from m
where '{"id": "1", "value_id": "1"}'::jsonb = any(ar)
and '{"id": "3", "value_id": "9"}'::jsonb = any(ar);
a
----
18
(1 row)
and for the second requirement - it won't be that short, as you need to compare (and thus parse json):
t=# with c(a,j) as (values(18,'{
"properties":[
{
"id":"1",
"value_id":"1"
},
{
"id":"2",
"value_id":"5"
},
{
"id":"3",
"value_id":"9"
},
{
"id":"4",
"value":"2.0"
},
{
"id":"7",
"value":"2017"
},
{
"id":"6",
"value":"180.000"
}
]
}'::jsonb), (19,'{"properties":[{"id": "1", "value_id": "1"}]}'))
, m as (select a, jb.value->>'id' id,jb.value->>'value_id' value_id from c, jsonb_array_elements(j->'properties') jb)
, n as (select m.*, count(1) over (partition by m.a)
from m
join c on c.a = m.a and ((id::int >= 1 and value_id::int <2) or (id::int >2 and value_id::int <= 9)))
select distinct a from n
where count > 1;
a
----
18
(1 row)
with basic idea to use OR to get possible rows and then check if ALL of OR conditions were met