SQL: count number of times a 'churn' occurs in a table - sql

I have the following SQLite DB table app_vendor:
app | vendor | active
---------------------
123 | 10 | 1
123 | 20 | 0 // + 1 (10 is 1, 20 is 0)
255 | 10 | 1
255 | 20 | 0 // + 1 (10 is 1, 20 is 0)
675 | 10 | 0
675 | 20 | 0 // 0 (10 is 0, 20 is 0)
I would like to run a query that compares, in all occurrences of the same app:
if vendor 10 is active: 1 and vendor 20 is not: 0 and
return a count of how many times that happens throughout this DB table.
In the example above, it happens twice (for app 123 and app 255) so the return value should be 2. I'm new to SQL and I have no idea how to group together by 'app', for example.

If for all apps there is 1 row (top) for vendor 10 and 1 row (top) for vendor 20 then:
SELECT DISTINCT COUNT(*) OVER() counter
FROM app_vendor
WHERE vendor IN (10, 20)
GROUP BY app
HAVING MIN(CASE WHEN vendor = 10 THEN active END) = 1
AND MAX(CASE WHEN vendor = 20 THEN active END) = 0
Another way to do it with INTERSECT:
SELECT COUNT(*) counter
FROM (
SELECT app FROM app_vendor WHERE vendor = 10 AND active = 1
INTERSECT
SELECT app FROM app_vendor WHERE vendor = 20 AND active = 0
)
See the demo.
Results:
counter
2

select count(1)
from (select * from test where vendor = 10 and active = 1) t1
join (select * from test where vendor = 20 and active = 0) t2
on t1.app = t2.app;

One way is to use conditional aggregation
select count(*)
from (select app
from t
group by app
having max(case when vendor=10 and active=1 then 1 end)
+ max(case when vendor=20 and active=0 then 1 end) = 2) t2

Related

finding unique record using min and max in same query from several criteria

I have a common table expression with the following fields
product.identifier, ingredient.identifier, ingredient.cost,
ingredient.isActive, ingredient.isPrimary
I'm trying to find a record based off the following criteria among multiple records
if isActive = 1 and isPrimary = 1, choose that record
if the record with isPrimary = 1 but isActive = 0, choose the record with the highest/max cost where isPrimary = 0 and isActive = 1
if all records from step 2 have the same cost, choose the oldest/min record based off ingredient.Identifier
the logic to find these on their own is simple but combining the logic into one clause is not working as expected. here is the expected output I'm trying to match along with the incorrect SQL
product ingredient cost isActive isPrimary isChosenRecord
-- isActive and isPrimary example
1 10 1.00 1 1 yes
1 11 1.10 1 0 no
2 20 2.00 1 1 yes
2 22 2.15 1 0 no
-- primary record is inactive, choose max cost record
3 30 3.00 0 1 no
3 31 3.10 1 0 no
3 32 3.20 1 0 yes
4 40 4.00 0 1 no
4 41 4.10 1 0 no
4 42 4.20 1 0 yes
-- primary record is inactive, all records have same cost, choose oldest record
5 50 5.00 0 1 no
5 51 5.00 1 0 yes
5 52 5.00 1 0 no
6 60 6.00 0 1 no
6 61 6.00 1 0 yes
6 62 6.00 1 0 no
; with [ActiveRecordsCTE] as
(
select
ProductIdentifier = p.Identifier,
IngredientIdentifier = i.Identifier,
i.Cost, i.isActive, i.isPrimary
from Product p
inner join Ingredient i on i.Identifier = p.Identifier
where i.isActive = 1
),
[CalculatedPrimaryRecords] AS
(
SELECT
r.ProductIdentifier,
r.IngredientIdentifier
FROM ActiveRecordsCTE r
WHERE r.IsPrimary = 1
UNION
-- get the oldest records
SELECT
r.ProductIdentifier,
IngredientIdentifier = min(r.IngredientIdentifier)
FROM
(
-- get most expensive record by cost
SELECT
r.ProductIdentifier,
r.IngredientIdentifier
FROM ActiveRecordsCTE a
CROSS APPLY
(
-- get most expensive record per product
SELECT
r.ProductIdentifier
,MaxAssetValue = MAX(r.Cost)
FROM ActiveRecordsCTE b
WHERE b.IsPrimary = a.IsPrimary
AND a.ProductIdentifier = b.ProductIdentifier
AND a.IngredientIdentifier = b.IngredientIdentifier
GROUP BY b.ProductIdentifier
) ca
WHERE a.IsPrimary = 0
-- exclude records that are included in the statement above
AND a.ProductIdentifier NOT IN
(
SELECT ProductIdentifier
FROM ActiveRecordsCTE
WHERE IsPrimary = 1
)
) sub
GROUP BY sub.ProductIdentifier
)
select * from [CalculatedPrimaryRecords]
Use row_number() for this type of prioritization:
with cte as ( . . . )
select t.*
from (select cte.*,
row_number() over (partition by product
order by (case when isActive = 1 and isPrimary = 1 then 1
when isActive = 0 and isPrimary = 1 then 2
else 3
end),
cost desc,
identifier asc
) as seqnum
from cte
) t
where seqnum = 1;
This makes some assumptions that seem consistent with the question:
isActive and isPrimary only take on the values 0 and 1.
If no records have isPrimary = 1, then you still want a record. (If not, these can easily be filtered out.)
identifier is not defined in your sample data.
EDIT:
If you wanted to be fancy, you could use top (1) with ties:
select top (1) with ties cte.*
from cte
order by row_number() over (partition by product
order by (case when isActive = 1 and isPrimary = 1 then 1
when isActive = 0 and isPrimary = 1 then 2
else 3
end),
cost desc,
identifier asc
);
I actually prefer the row_number() solution because I'm not sure what to do in the case that isPrimary = 0 and it is easier to add logic for that solution to filter out those records.

Limit sql query to only rows where aggregate of col = 0

I'm looking at auction data in the following form:
product auction_id bid_value is_winning_bid reject_reason
iPhone 123 5 1 0
iPhone 123 3 0 1
iPhone 123 2 0 200
iPad 456 1 0 1
iPad 456 10 0 1
iPhone 789 2 0 200
iPhone 999 10 0 1
I want to count the number of auctions where one of the bids had a reject_reason = 200 and none of the bids has a is_winning_bid = 1, grouped by product.The data set is large so I don't want to have the output grouped by auction_id.
The table above should result in:
product total_auctions count_unfilled_auctions count_unfilled_auctions_200
iPhone 3 2 1
iPad 1 1 0
I think you just need two levels of aggregation, one at the auction level and one at the product level:
select product, count(*) as total_auctions,
sum(1 - has_winning_bid) as unfulfilled_auctions,
sum(case when cnt_reject_200 > 0 and has_winning_bid = 1 then 1 else 0 end) as unfulfilled_auctions_200
from (select auction_id, product,
max(is_winning_bid) as has_winning_bid,
sum(case when reject_reason = 200 then 1 else 0 end) as cnt_reject_200
from auctiondata ad
group by auction_id, product
) ad
group by product;

Create View with cumulative counts for a fixed set of value-range

I want to create BQ View on this table:
mytable
id | value
s1 | 21
s2 | 31
s3 | 71
the View need to count each of the above row(id), in each of the 10 fixed milestones when value <= milestoneValue
Resulting view with 10 milestone rows for mytable will be:
milestoneValue | count
100 | 3
90 | 3 (all s1 s2 s3)
80 | 3
70 | 2 (s1,s2)
60..
30 | 1
20 | 1
10 | 1
I did not find any suitable function to compute this. I could add the 10 binary flag as columns on the raw data in mytable, that I could SUM, but do not see a way to transform that to a 10 row milestone View.
I tried:
SELECT id, value,
IF(value <= 10 , 1, 0) as M10,
IF(value <= 20 , 1, 0) as M20,
...
IF(value <= 90 , 1, 0) as M90,
IF(value <= 100 , 1, 0) as M100
FROM mytable ;
Help appreciated,
Thanks
SELECT bucket, SUM(word_count>bucket)
FROM [publicdata:samples.shakespeare] a
CROSS JOIN (
SELECT bucket FROM (SELECT 10 bucket), (SELECT 20 bucket), (SELECT 30 bucket), (SELECT 40 bucket)
) b
GROUP BY bucket

Update row based on value of multiple other rows in Oracle SQL

I want to find the rows which are similar to each other, and update a field if a row has any similar row. My table looks like this:
OrderID | Price | Minimum Number | Maximum Number | Volume | Similar
1 45 2 10 250 0
2 46 2 10 250 0
3 60 2 10 250 0
"Similar" in this context means that the rows that have same Maximum Number, Minimum Number, and Volume. Prices can be different, but the difference can be at most 2.
In this example, orders with OrderID of 1 and 2 are similar, but 3 has no similar row (since even if it has same Minimum Number, Maximum Number, and Volume, but its price is not within 2 units from orders 1 and 2).
Then, I want to update the filed "Similar" for orders 1 and 2 from the default value (0) to 1. So, the output for the example above would be:
OrderID | Price | Minimum Number | Maximum Number | Volume | Similar
1 45 2 10 250 1
2 46 2 10 250 1
3 60 2 10 250 0
Here is one method that is ANSI standard SQL that will work in most databases, including Oracle. It implements the logic that you set out using a correlated subquery:
update table t
set similar = 1
where exists (select 1
from table t2
where t2.minimum = t.minimum and
t2.maximum = t.maximum and
t2.volume = t.volume and
abs(t2.price - t.price) <= 2 and
t2.OrderId <> t.OrderId
);
EDIT:
It occurs to me that the "similar" field might be the minimum OrderId of the similar fields. You can extend the above idea to:
update table t
set similar = (select min(orderId)
from table t2
where t2.minimum = t.minimum and
t2.maximum = t.maximum and
t2.volume = t.volume and
abs(t2.price - t.price) <= 2 and
t2.OrderId <> t.OrderId
)
where exists (select 1
from table t2
where t2.minimum = t.minimum and
t2.maximum = t.maximum and
t2.volume = t.volume and
abs(t2.price - t.price) <= 2 and
t2.OrderId <> t.OrderId
);
Although if this were the case, the default value should be NULL and not 0.

Grouping sub query in one row

ClientID Amount flag
MMC 600 1
MMC 700 1
FDN 800 1
FDN 350 2
FDN 700 1
Using sql server,Below query I am getting 2 rows fro FDN. I just would like to combine Client values in one row.
Output should be like
Client gtcount, totalAmountGreaterThan500 lscount,AmountLessThan500
MMC 2 1300 0 0
FDN 2 1500 1 350
SELECT
f.ClientID,f.flag,
case when flag = 1 then count(*) END as gtcount,
SUM(CASE WHEN flag = 1 THEN Amount END) AS totalAmountGreaterThan500,
case when flag = 2 then count(*) END as lscount,
SUM(CASE WHEN Flag = 2 THEN Amount END) AS AmountLessThan500,
from
( select ClientID, Amount,flag from #myTable)f
group by ClientID,f.flag
Try
SELECT ClientID,
SUM(CASE WHEN flag = 1 THEN 1 ELSE 0 END) AS gtcount,
SUM(CASE WHEN flag = 1 THEN Amount ELSE 0 END) AS totalAmountGreaterThan500,
SUM(CASE WHEN flag = 2 THEN 1 ELSE 0 END) AS lscount,
SUM(CASE WHEN Flag = 2 THEN Amount ELSE 0 END) AS AmountLessThan500
FROM Table1
GROUP BY ClientID
Output:
| CLIENTID | GTCOUNT | TOTALAMOUNTGREATERTHAN500 | LSCOUNT | AMOUNTLESSTHAN500 |
|----------|---------|---------------------------|---------|-------------------|
| FDN | 2 | 1500 | 1 | 350 |
| MMC | 2 | 1300 | 0 | 0 |
Here is SQLFiddle demo
Looks like your desired output is off -- there aren't any mmc records less than 500. You can accomplish this using sum with case for each of your fields, removing flag from the group by:
SELECT
ClientID,
SUM(CASE WHEN flag = 1 THEN 1 END) as gtcount,
SUM(CASE WHEN flag = 1 THEN Amount END) AS totalAmountGreaterThan500,
SUM(CASE WHEN flag = 2 THEN 1 END) as ltcount,
SUM(CASE WHEN Flag = 2 THEN Amount END) AS AmountLessThan500
from myTable
group by ClientID
SQL Fiddle Demo
On a different note, not sure why you need the Flag field. If it's just being used to denote less than records, just add the logic to the query:
SUM(CASE WHEN Amount <= 500 Then ...)