I have a single bucket (Couchbase Community edition 6.5) consisting of the following documents:
employees {
employeeGroupId: string,
type: "Employee"
}
clocks {
employeeId: string,
areaId: string
date: string,
type: "Clock"
}
Each employee has multiple corresponding clock items for each day. I need to get the following:
first clock -> clockIn
last clock -> clockOut
I have written the following query which gets the first and last clock items with execution time <100 ms:
SELECT META(employee).id AS employeeId,
employee.employeeGroupId,
MIN(clock.date) AS clockIn,
MAX(clock.date) AS clockOut
FROM `bucket` employee LEFT
JOIN `bucket` clock ON clock.employeeId = META(employee).id
AND type = "Clock"
AND clock.date BETWEEN "2020-06-01T00:00:00.000Z" AND "2020-06-02T00:00:00.000Z"
WHERE employee.type = "Employee"
GROUP BY employee;
The problem is I need to get the corresponding areaId with the matching clock.
I have written the following query that does. I create two separate sub queries that sort all of the clock items for the day first ascending and then descending and select the first item.
CREATE INDEX adv_employeeId_type_date_blockId ON `bucket`(`employeeId`,`type`,`date`,`blockId`)
CREATE INDEX adv_employeeId_type_date ON `bucket`(`employeeId`,`type`,`date`)
CREATE INDEX adv_type_employeeId_date ON `bucket`(`type`,`employeeId`,`date`)
SELECT META(employee).id AS employeeId,
employee.employeeGroupId,
clockIn,
clockOut
FROM `bucket` employee
LEFT JOIN (
SELECT obj.employeeId,
obj.date,
obj.areaId
FROM `bucket` obj
WHERE obj.employeeId = META(employee).id
AND obj.type = "Clock"
AND obj.date BETWEEN "2020-06-01T00:00:00.000Z" AND "2020-06-02T00:00:00.000Z"
ORDER BY obj.date
LIMIT 1) clockIn ON clockIn.employeeId = META(employee).id
LEFT JOIN (
SELECT obj.employeeId,
obj.date,
obj.areaId
FROM `bucket` obj
WHERE obj.employeeId = META(employee).id
AND obj.type = "Clock"
AND obj.date BETWEEN "2020-06-01T00:00:00.000Z" AND "2020-06-02T00:00:00.000Z"
ORDER BY obj.date DESC
LIMIT 1) clockOut ON clockOut.employeeId = META(employee).id
WHERE employee.type = "Employee"
GROUP BY employee,
clockIn,
clockOut;
The problem is that the above query is inefficient with execution time >10 seconds.
In other words I need to get additional object values from the aggregate MIN() and MAX() functions.
I am sure the second query is not the most efficient method to achieve this, does anyone have any other suggestions?
CREATE INDEX ix1 ON `bucket`(type, `employeeGroupId`) WHERE type = "Employee";
CREATE INDEX ix2 ON `bucket`(`employeeId`, date, areaId) WHERE type = "Clock";
SELECT META(employee).id AS employeeId,
employee.employeeGroupId,
minclock[0] AS clockIn,
minclock[1] AS clockInAreaId,
maxclock[0] AS clockOut,
maxclock[1] AS clockOutAreaId
FROM `bucket` AS employee LEFT
JOIN `bucket` AS clock ON clock.employeeId = META(employee).id
AND type = "Clock"
AND clock.date BETWEEN "2020-06-01T00:00:00.000Z" AND "2020-06-02T00:00:00.000Z"
WHERE employee.type = "Employee"
GROUP BY employee
LETTING minclock = MIN([clock.date,clock.areaId]),
maxclock = MAX([clock.date,clock.areaId]);
OR
SELECT META(employee).id AS employeeId,
employee.employeeGroupId,
MIN([clock.date, {clock.date, clock.areaId}])[1] AS clockIn,
MAX([clock.date, {clock.date, clock.areaId}])[1] AS clockOut,
FROM `bucket` AS employee LEFT
JOIN `bucket` AS clock ON clock.employeeId = META(employee).id
AND type = "Clock"
AND clock.date BETWEEN "2020-06-01T00:00:00.000Z" AND "2020-06-02T00:00:00.000Z"
WHERE employee.type = "Employee"
GROUP BY employee;
Use MIN/MAX on Array. 0th expression is MIN/MAX expression. reset of array positions used only on ties (similar like ORDER BY multiple fields). Result will complete ARRAY expression.
Choose which positions you want project. This technique allows you project non-group by expressions.
Related
I have an oracle query that i am using to collect records that have a buyer type code of VTEST
but i also need to populate records in the same query that have code matching ADULT but onlyt matching a particular sales channel.
the listed tables dont have a sales channel link but the transaction table does have sales channeland it can join to the event_seat table by the transaction_id field
so basically i want to pull records that match VTEST and ADULT but where as the adult ones can ONLY match the sales channel id of 6
any help is greatly appreciated
SELECT
e.event_date,
e.venue_id,
e.description AS event,
t.price,
t.ticket_id,
bt.buyer_type_code,
bt.description AS buyer_type,
btg.description AS ticket_category,
SUM(sci.actual_amount) AS tax,
t.price + SUM(sci.actual_amount) AS revenue,
e.event_id,
coupon.coupon_code
FROM
event e
INNER JOIN event_seat es ON e.event_id = es.event_id
INNER JOIN ticket t ON es.ticket_id = t.ticket_id
LEFT JOIN service_charge_item sci ON sci.ticket_id = t.ticket_id
INNER JOIN buyer_type bt ON t.buyer_type_id = bt.buyer_type_id
INNER JOIN buyer_type_group btg ON bt.buyer_type_group_id = btg.buyer_type_group_id
LEFT JOIN coupon ON t.coupon_id = coupon.coupon_id
WHERE
e.event_date > '1-JAN-2022'
AND e.description LIKE '%Testshow%'
AND e.description NOT LIKE '%Join%'
AND e.description NOT LIKE '%Left%'
AND e.event_status_code = 'SAL'
GROUP BY
e.event_date,
e.venue_id,
e.description,
t.price,
t.ticket_id,
bt.buyer_type_code,
bt.description,
btg.description,
e.event_id,
coupon.coupon_code
HAVING
bt.buyer_type_code LIKE 'VTEST'
Considering you haven't done the joins and it's through 2 tables, I will leave this part to you, but you can implement the logic you describe by removing the HAVING clause and add in the where:
AND bt.buyer_type_code IN ('VTEST','ADULT')
AND 6 = CASE WHEN buyer_type_code = 'ADULT' THEN sales_tbl.channel_id --or whatever it's name is
ELSE 6
END
I have a problem where for a single value there can be maximum of two rows. I have to select only one row based on the column value.Below example can show my data and what I am trying to achieve.
Table Item with the following information
Item Dist_Building Country_Building
I123 B123 B245
I980 B980 B345
I780 B780 B445
Table item_info with the following columns
Item_number Building_Nbr Building_Area Value
I123 B123 District 10
I123 B245 Country 20
I980 B980 District 50
I780 B445 Country 20
Select the items from the Item table and check for the corresponding Building information present in Item_info table. If for a certain item, values are present at District and Country level then select the District level VALUE else select the Country Level VALUE(i.e. row with district area will take the priority over the Country )
Item_number Value
I123 10
I980 50
I780 20
are the Dist_Building, Country_Building, and Building_Nbr important here? It looks like theyre not, in which case if you create a new table from the item_info table like so...
select
item_number
, building_nbr
, sum(case when building_area = 'District' then value end) as District_Val
, sum(case when building_area = 'Country' then value end) as Country_Val
, coalesce(District_Val, Country_Val) as Value
into new_item_info
from item_info
group by
1,2
you should be able to just left join it to the item table.
(im not that familiar with Oracle SQL, so you may have to break the above code in to two steps)
One way to do this is with a join to either building, and then keep the one with the 'higher' area value (which works here as 'District' sorts after 'Country'):
select i.item,
max(ii.value) keep (dense_rank last order by ii.building_area) as value
from item i
join item_info ii on ii.item_number = i.item
and (
(ii.building_nbr = i.dist_building and ii.building_area = 'District')
or (ii.building_nbr = i.country_building and ii.building_area = 'Country')
)
group by i.item;
ITEM VALUE
---- ----------
I123 10
I780 20
I980 50
You could make that more explicit with a case expression in the order-by clause if you don't want to rely on that sort order.
You could also do two outer joins and use coalesce to pick the one you want:
select i.item,
coalesce(iid.value, iic.value) as value
from item i
left join item_info iid on iid.item_number = i.item
and iid.building_nbr = i.dist_building
and iid.building_area = 'District'
left join item_info iic on iic.item_number = i.item
and iic.building_nbr = i.country_building
and iic.building_area = 'Country';
ITEM VALUE
---- ----------
I123 10
I780 20
I980 50
db<>fiddle
You can achieve your goal by simply using sub-queries and the NVL function:
SELECT Item
, NVL((SELECT VALUE FROM item_info ii
WHERE ii.item_number = i.item
AND ii.Building_Nbr = i.Dist_Building)
,(SELECT VALUE FROM item_info ii
WHERE ii.item_number = i.item
AND ii.Building_Nbr = i.Country_Building)) VALUE
FROM Item i
The below query returns a list of the most popular theatre and rowtype combinations sorted by total amount:
so for example:
NAME ROWTYPE TOTALAMOUNT
theatre1 middle 200
theatre2 front 190
theatre1 front 150
theatre2 middle 100
Whereas what I need is simply the maximum per theatre:
theatre1 middle 200
theatre2 front 190
Query:
SELECT name, rowtype, sum
from ( select
name, rowtype, sum(totalamount) sum from trow, fact, theatre
Where trow.trowid = fact.trowid
AND
theatre.theatreid = fact.theatreid
GROUP BY rowtype, name
)
ORDER BY sum DESC, name, rowtype ;
You can use window functions for this:
select name, rowtype, sum
from (select name, rowtype, sum(totalamount) as sumta,
max(sum(totalamount)) over (partition by name) as maxsumta
from trow join
fact
on trow.trowid = fact.trowid join
theatre
on theatre.theatreid = fact.theatreid
group byrowtype, name
) nr
where sumta = maxsumta;
In addition, you should learn to use proper, explicit JOIN syntax. A simple rule: Never use commas in the FROM clause. Always use proper explicit JOIN syntax.
Put your current query into a common table expression and then use window functions to find the max total amount for each theatre.
WITH cte AS
(
SELECT name, rowtype, SUM(totalamount) sum
FROM trow
INNER JOIN fact
ON trow.trowid = fact.trowid
INNER JOIN theatre
ON theatre.theatreid = fact.theatreid
GROUP BY name, rowtype
)
SELECT name, rowtype, sum
FROM
(
SELECT name,
rowtype,
sum,
MAX(sum) OVER (PARTITION BY name) maxSum
FROM cte
) t
WHERE t.sum = t.maxSum
I'm trying to display only the largest group in this group by statement;
SELECT COUNT(type) AS booking, type FROM booking b, room r WHERE r.rno = b.rno AND r.hno = b.hno GROUP BY type;
I modified it so we get this query response now you can see group double is larger then family.
BOOKING TYPE
5 double
2 family
I know there is a HAVING keyword you can add in order display only a count compared to a number so I could do COUNT(type) HAVING > 2 or similar but that's not very dynamic and that would only work in this instance because I know the two amounts.
ORDER BY COUNT(type) DESC LIMIT 1
There isn't a having statement that does this. But you can use rownum with a subquery:
select t.*
from (SELECT COUNT(type) AS booking, type
FROM booking b join
room r
on r.rno = b.rno AND r.hno = b.hno
GROUP BY type
order by count(type) desc
) t
where rownum = 1;
Just order your query..
order by booking desc
regards
TRY this
SELECT COUNT(type) AS booking, type FROM booking b, room r WHERE r.rno = b.rno AND r.hno = b.hno ORDER BY type DESC LIMIT 1
I have a simple query which will return either A or B depending on the projected oven out date and time. If the projected oven out date and time is between 6am and 6pm, A should be returned. Otherwise B if time is between 6pm and 6am of the next day. My problem is that I do not know how to display A or B depending on the projected oven out date and time. I am using the query below to get the projected oven out date and time.
SELECT DISTINCT
to_char((ti.txndate + pm.baketime/24),'MM/DD/YYYY HH:MI:SS PM') FCSTDOvenOut
FROM CONTAINER c
JOIN movin movin ON c.containerid = movin.historyid
JOIN product p ON c.productid = p.productid AND p.attr_016 IN ('TEST', 'TR')
JOIN productbase pb ON p.productbaseid = pb.productbaseid
LEFT JOIN otherdb.pkg_main pm ON TRIM(p.brandname) = TRIM(pm.pcode)
LEFT JOIN employee e ON movin.employeeid = e.employeeid
JOIN trackin ti ON c.containerid = ti.historyid AND ti.txndate > movin.txndate
LEFT JOIN employee em ON ti.employeeid = em.employeeid
;
Example:
Thanks for helping everyone.
SELECT DISTINCT
case when
to_char(ti.txndate + pm.baketime/24,'hh24') between '06' and '17' then 'A'
else 'B'
end shift,
....