postgresql - get count by value ranges - sql

my data looks like this:
name | value
------------
a | 3.5
a | 13.5
a | 4.9
a | 11
a | 14
b | 2.5
b | 13.6
b | 5.1
b | 12
b | 13.5
I need the count grouped by value ranges:
name | 0-5 | 5-10 | 10-15
-------------------------
a | 2 | 0 | 2
b | 1 | 1 | 3
Any help is appreciated.
Thanks,
grassu

select name,
count(case when value <= 5 then 1 end) as "0-5",
count(case when value > 5 and value <= 10 then 1 end) as "5-10",
count(case when value > 10 and value <= 15 then 1 end) as "10-15"
from the_table
group by name;
With the upcoming version 9.4 this can be written a bit more readable:
select name,
count(*) filter (where amount <= 5) as "0-5",
count(*) filter (where value > 5 and value <= 10) as "5-10",
count(*) filter (where value > 10 and value <= 15) as "10-15"
from the_table
group by name;

Related

SQL transpose based on values in col 1

I have a SQL query that gives me a 2 column result like this:
A | B
-----
2 | 1
3 | 2
3 | 3
3 | 4
3 | 5
4 | 6
4 | 7
4 | 8
4 | 9
I would like to split this up into multiple columns like this. Each for each row in A with a particular value convert that into columns. The maximum number of rows a particular value say x can occur in column A is 4 (if that helps).
A | B1 | B2 | B3 | B4
----------------------------
2 | 1 | NULL | NULL | NULL
3 | 2 | 3 | 4 | 5
4 | 6 | 7 | 8 | 9
I have been stuck trying to do this using pivot for hours. I am now thinking about querying this in python (the client using this) and doing the transformation there (which is easy). But can this be done in SQL? I am using SQL Server 2016.
You can use row_number() and conditional aggregation:
select a,
max(case when seqnum = 1 then b end) as b_1,
max(case when seqnum = 2 then b end) as b_2,
max(case when seqnum = 3 then b end) as b_3,
max(case when seqnum = 4 then b end) as b_4
from (select t.*, row_number() over (partition by a order by b) as seqnum
from t
) t
group by a;

Group by similar number SQL (oracle sql)

I would like to find the number of sales that have a similar purchase value from the following table:
sale_number | value
------------+-------
1 | 10
2 | 11
3 | 21
4 | 30
A vanilla group by statement groups by exact value:
select count(sale_number), value from table group by value
Would give:
count(sale_number) | value
------------+-------
1 | 10
1 | 11
1 | 21
1 | 30
Is it possible to group by inexact numbers with a threshold (say +/- 10%)? Giving the desired result:
count(sale_number) | value
------------+-------
2 | 10
2 | 11
1 | 21
1 | 30
You can do what you want with a correlated subquery:
select t.*,
(select count(*)
from t t2
where t2.value >= t.value * 0.9 and
t2.value <= t.value * 1.1
) as cnt
from t;

Count consecutive days

Please I want to count total consecutive days for an event record and order by this record grouping by actor id . for instance we have. For sqlite
event_id| created_at |actor_id
1 | 2018-07-01| 40 /* this is a consecutive days
1 | 2018-07-02| 40 */
1 | 2018-07-04| 40
1 | 2018-07-05| 40
1 | 2018-07-09| 40
2 | 2018-07-11| 40
2 | 2018-07-12| 40
1 | 2018-07-13| 41
should give me something like
actor_id|streak
40 | 3
41 | 0
You can group by actor_id and sum conditionally if there exists a consecutive day:
select
t.actor_id,
sum(case when exists (
select 1 from tablename
where
actor_id = t.actor_id and
julianday(created_at) - julianday(t.created_at) = 1
) then 1 else 0 end) streak
from tablename t
group by t.actor_id
See the demo.
Or with a self join:
select
t.actor_id,
sum(tt.created_at is not null) streak
from tablename t left join tablename tt
on tt.actor_id = t.actor_id and julianday(tt.created_at) - julianday(t.created_at) = 1
group by t.actor_id
See the demo.
Results:
| actor_id | streak |
| -------- | ------ |
| 40 | 3 |
| 41 | 0 |

SQL Grouping entries with a different value

Let's assume I have a report that displays an ID and VALUE from different tables
| ID | VALUE |
|----|-------|
1 | 1 | 1 |
2 | 1 | 0 |
3 | 1 | 1 |
4 | 2 | 0 |
5 | 2 | 0 |
My goal is to display this table with grouped IDs and VALUEs. My rule to grouping VALUEs would be "If VALUE contains atleast one '1' then display '1' otherwise display '0'".
My current SQL is (simplified)
SELECT
TABLE_A.ID,
CASE
WHEN TABLE_B.VALUE = 1 OR TABLE_C.VALUE NOT IN (0,1,2,3)
THEN 1
ELSE 0
END AS VALUE
FROM TABLE_A, TABLE_B, TABLE_C
GROUP BY
TABLE_A.ID
(CASE
WHEN TABLE_B.VALUE = 1 OR TABLE_C.VALUE NOT IN (0,1,2,3)
THEN 1
ELSE 0
END)
The output is following
| ID | VALUE |
|----|-------|
1 | 1 | 1 |
2 | 1 | 0 |
3 | 2 | 0 |
Which is half way to the output I want
| ID | VALUE |
|----|-------|
1 | 1 | 1 |
2 | 2 | 0 |
So my Question is: How do I extend my current SQL (or change it completely) to get my desired output?
If you are having only 0 and 1 as distinct values in FOREIGN_VALUE column then using max() function as mentioned by HoneyBadger in the comment will fulfill your requirement.
SELECT
ID,
MAX(FOREIGN_VALUE) AS VALUE
FROM (SELECT
ID,
CASE WHEN FOREIGN_VALUE = 1
THEN 1
ELSE 0
END AS FOREIGN_VALUE
FROM TABLE,
FOREIGN_TABLE)
GROUP BY
ID;
Assuming value is always 0 or 1, you can do:
select id, max(value) as value
from t
group by id;
If value can take on other values:
select id,
max(case when value = 1 then 1 else 0 end) as value
from t
group by id;

SQL Select records excluding some statuses

I'm totally stuck on how to create this select. I need to select from the status table only those order_id's which to not have status 2.
Here is the table:
+----+---------+---------+--
| id | order_id| status |
+----+---------+---------+--
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 3 | 1 |
| 1 | 3 | 3 |
| 2 | 4 | 2 |
| 3 | 4 | 1 |
| 4 | 4 | 2 |
| 5 | 5 | 3 |
+----+---------+----------+--
So he select result will be only order_id = 5
Please help!
If you want to include orders with status 1 and exclude those with status 3, then you can use a similar idea:
having sum(case when status_id = 1 then 1 else 0 end) > 0 and
sum(case when status_id = 3 then 1 else 0 end) = 0
EDIT: I like to EXCLUDE those order_id's:
- Which has only status 1 (not status 2)
- and
- which has status 3
Lets have table like this:
id--order-id-Prod---Status
------------------------------
1 1 a 1
6 1 b 2
7 1 a 2
8 1 b 1
9 2 a 1
10 3 a 1
11 3 b 1
12 3 a 2
13 3 b 2
14 4 a 1
15 4 b 1
16 5 a 1
17 5 b 1
18 5 a 2
19 5 b 2
20 5 a 3
21 5 b 3
Select should show only order_id "5"
This is an example of a set-within-sets query:
select order_id
from t
group by order_id
having sum(case when status = 2 then 1 else 0 end) = 0
The having clause counts the number of rows with a status of 2. The = 0 finds the orders with no matches.
EDIT:
If you want to include orders with status 1 and exclude those with status 3, then you can use a similar idea:
having sum(case when status_id = 1 then 1 else 0 end) > 0 and
sum(case when status_id = 3 then 1 else 0 end) = 0
Here's one way.
Select * from TableName
where Order_ID not in (Select order_ID from tableName where status=2)
Another way would be to use the not exists clause.
Another way is to use EXCEPT:
SELECT order_id
FROM StatusTable
EXCEPT
SELECT order_id
FROM StatusTable
WHERE status = 2;
It works in SQL-Server and Postgres (and in Oracle if you replace the EXCEPT with MINUS.)
I think this works, one query to select all ids, one to get those with a status of 2 and left joining on order_id and picking those with null order_id in the list of orders with a status of 2.
select
all_ids.order_id
from
(
select distinct
order_id
from status
) all_ids
left join
(
select
order_id
from status
where status = 2
) two_ids
on all_ids.order_id = two_ids.order_id
where two_ids.order_id is null