Oracle Group by multiple condition - sql

I had a query to group types with sum of prices
SELECT t.type, sum(t.price) AS TOTAL
FROM table1 t
WHERE t.entry_date = & entryDate
GROUP BY t.type;
It works correctly and returns
+-----------+-----------+
| TYPE | TOTAL |
+-----------+-----------+
| | 741,5 |
| type1 | 108,54 |
| type2 | 216,35 |
+-----------+-----------+
In table some of the rows don't have a type name.
I want to add a case to check if exists add its price to type1 so
I tried something like
SELECT (CASE WHEN t.id = k.orderno AND length(k.invoice_no) < 12
THEN 'type1' ELSE t.type END
) AS TYPE,
SUM(t.price) AS TOTAL
FROM table1 t, table2 k
WHERE t.entry_date = & entryDate
GROUP BY TYPE;
It gives
ora-00937 not a single-group group function
I also tried group by when case but it didn't work either.

Without knowing your table definitions and what the columns are used for it is difficult to answer; however, it could be as simple as:
SELECT COALESCE( t.type, t.price ) AS type,
sum(t.price) AS TOTAL
FROM table1 t
WHERE t.entry_date = &entryDate
GROUP BY
t.type;
Or, if you want to join another table then you want to use a join condition (and, preferably, the ANSI join syntax rather the legacy comma join syntax):
SELECT CASE
WHEN COALESCE( LENGTH(k.invoice_no), 0 ) < 12
THEN 'type1' || t.price
ELSE t.type
END AS TYPE,
SUM(t.price) AS TOTAL
FROM table1 t
LEFT OUTER JOIN table2 k
ON ( t.id = k.orderno )
WHERE t.entry_date = &entryDate
GROUP BY
CASE
WHEN COALESCE( LENGTH(k.invoice_no), 0 ) < 12
THEN 'type1' || t.price
ELSE t.type
END;

You need to use the group by accordingly as follows:
select Case When t.id = k.orderno
and length(k.invoice_no) < 12
THEN 'type1'
ELSE t.type
END AS TYPE,
sum(t.price) as TOTAL
from table1 t, table2 k
where t.entry_date = &entryDate
group by Case When t.id = k.orderno
and length(k.invoice_no) < 12
THEN 'type1'
ELSE t.type
END;

Related

Compare the same id with 2 values in string in one table

I have a table like this:
id
status
grade
123
Overall
A
123
Current
B
234
Overall
B
234
Current
D
345
Overall
C
345
Current
A
May I know how can I display how many ids is fitting with the condition:
The grade is sorted like this A > B > C > D > F,
and the Overall grade must be greater than or equal to the Current grade
Is it need to use CASE() to switch the grade to a number first?
e.g. A = 4, B = 3, C = 2, D = 1, F = 0
In the table, there should be 345 is not match the condition. How can I display the tables below:
qty_pass_the_condition
qty_fail_the_condition
total_ids
2
1
3
and\
fail_id
345
Thanks.
As grade is sequential you can do order by desc to make the number. for the first result you can do something like below
select
sum(case when GradeRankO >= GradeRankC then 1 else 0 end) AS
qty_pass_the_condition,
sum(case when GradeRankO < GradeRankC then 1 else 0 end) AS
qty_fail_the_condition,
count(*) AS total_ids
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from YourTbale
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from YourTbale
) as a where Status='Current'
) as c on b.Id=c.Id
For second one you can do below
select
b.Id fail_id
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from Grade
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from Grade
) as a where Status='Current'
) as c on b.Id=c.Id
where GradeRankO < GradeRankC
You can use pretty simple conditional aggregation for this, there is no need for window functions.
A Pass is when the row of Overall has grade which is less than or equal to Current, with "less than" being in A-Z order.
Then aggregate again over the whole table, and qty_pass_the_condition is simply the number of non-nulls in Pass. And qty_fail_the_condition is the inverse of that.
SELECT
qty_pass_the_condition = COUNT(t.Pass),
qty_fail_the_condition = COUNT(*) - COUNT(t.Pass),
total_ids = COUNT(*)
FROM (
SELECT
t.id,
Pass = CASE WHEN MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) <=
MIN(CASE WHEN t.status = 'Current' THEN t.grade END)
THEN 1 END
FROM YourTable t
GROUP BY
t.id
) t;
To query the actual failed IDs, simply use a HAVING clause:
SELECT
t.id
FROM YourTable t
GROUP BY
t.id
HAVING MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) >
MIN(CASE WHEN t.status = 'Current' THEN t.grade END);
db<>fiddle

Query to find the count of total IDs, and IDs having null in any column and the percentage of the two (i.e. Count of ID having null /total ID count)

There are two tables.
Tables A has following structure
ID
Flag
Name
1X
1
Y
2Y
0
Null
3Z
1
Null
4A
1
Y
Table B has the following structure
B_ID
City
State
1X
Y
Null
2Y
Null
Null
3Z
Null
Y
4A
Y
Y
I want to get the count of all the IDs and the count of IDs that have Null in any of the columns (name, city, state), for e.g from the tables above only the ID 4A has non null value in all the three columns across both the tables, so the output should be like
Total_Count
Ids having null
Percentage missing
4
3
0.75%
Total_count is 4 as there are total of four IDs, ID having NULL is 3 because there are 3 IDs that have null in any of the three columns (viz. name,city,state), and Percentage missing is just IDs having null / Total_Count.
I tried using a query along the following lines
select (count/total) * 100 pct, count,total
from (select sum(count) count
from(select count(*) count from tableA T1
where T1.name is null
union all
select count(*) count from tableA T1
join tableB T2 on T1.ID = T2.B_ID
where T2.city is null
union all
select count(*) count from tableA T1
join tableB T2 on T1.ID = T2.B_ID
where T2.state is null)),
select count(ID) total from tableA);
But the query is not returning the desired output, can you please suggest me a better way?
Thank You
Use conditional aggregation:
SELECT COUNT(*) Total_Count,
COUNT(CASE WHEN t1.Name IS NULL OR t2.City IS NULL OR t2.State IS NULL THEN 1 END) Ids_having_null,
AVG(CASE WHEN COALESCE(t1.Name, t2.City, t2.State) IS NOT NULL THEN 1 ELSE 0 END) Percentage_missing
FROM Table1 t1 INNER JOIN Table2 t2
ON t2.B_ID = t1.ID;
See the demo.
If you don't know if either table has all the ID's?
Then I suggest a full join with some conditional aggregation in a sub-query.
For example:
select
Total as "Total_Count"
, TotalMissing as "Ids having null"
, (TotalMissing / Total)*100||'%' as "Percentage missing"
from
(
select
count(distinct coalesce(a.ID, b.B_ID)) as Total
, count(distinct case
when a.name is null
or b.city is null
or b.state is null
then coalesce(a.ID, b.B_ID)
end) as TotalMissing
from TableA a
full outer join TableB b
on a.ID = b.B_ID
) q
Total_Count | Ids having null | Percentage missing
----------: | --------------: | :-----------------
4 | 3 | 75%
db<>fiddle here
Try this ->
select total_count, (total_count - cn) as ids_having_null,
(total_count - cn) *100 / total_count as Percentage_missing
FROM
(select count(t1.ID) as cn , t1.total_count
FROM ( select ID,Name, sum(tmp_col) over ( order by tmp_col) as total_count from (select ID,Name, 1 as tmp_col from tableA ) ) t1
JOIN TableB t2
ON t1.ID = t2.B_ID
WHERE t1.Name is not null and t2.City is not null and t2.State is not null );
Based on your requirement for percentage or ratio, you can alter the logic for Percentage_missing column

Sql query like pivot table from vertical list in postgresql effectively

I want to write an sql query like pivot table from vertical list. Let's see it on an example:
My table data:
Table1 -> Metadata:
id text
1 t1
2 t2
3 t3
Table2 -> Report:
date category revenue metadata_id
2020-01-01 TRIAL 1 1
2020-01-01 PURCHASE 1.2 2
2020-01-03 SUBSCRIPTION 1.4 3
2020-01-03 PURCHASE 1.1 3
...
In here, I want to create an sql query to get resul from specific range and ids filter like:
Request:
start-date: 2020-01-01
end-date: 2020-01-30
ids: 1,2....100
Expected result:
id text category_trial category_purchase category_SUBSCRIPTION
1 t1 1 0 0
2 t2 0 1.2 0
3 t3 0 1.1 1.4
In here, I wrote an sql like below:
select
m.id,
m.text,
t1.rev as category_trial,
t2.rev as category_purchase,
t3.rev as category_SUBSCRIPTION
from metadata m
left join
(
select
metadata_id,
sum(revenue) as rev
from report where category = 'TRIAL' and report_date between '2020-01-01' and '2020-01-30'
group by metadata_id
) t1 on t1.metadata_id = m.id
left join
(
select
metadata_id,
sum(revenue) as rev
from report where category = 'PURCHASE' and report_date between '2020-01-01' and '2020-01-30'
group by metadata_id
) t2 on t2.metadata_id = m.id
left join
(
select
metadata_id,
sum(revenue) as rev
from report where category = 'SUBSCRIPTION' and report_date between '2020-01-01' and '2020-01-30'
group by metadata_id
) t3 on t3.metadata_id = m.id
...
In here I have more than 7 categories.
My problem in here, this sql is working but the performance is not enough. Is there any suggestion to improve performance of it?
Note: I wrote it in postgresql and I use indexing.
I do not use Postgres often, but this query should be faster:
select id, text,
sum(case category when 'TRIAL' then revenue else 0 end) cat_tri,
sum(case category when 'PURCHASE' then revenue else 0 end) cat_pur,
sum(case category when 'SUBSCRIPTION' then revenue else 0 end) cat_sub
from (
select id, text, category, revenue
from metadata m join report r on m.id = r.metadata_id
where date_ between '2020-01-01' and '2020-01-30'
and id between 1 and 100 ) t
group by id, text
order by id
dbfiddle
Result as expected, only one join, filtering and grouping.

Getting the wrong values after SUM() query

I'm executing the following query:
SELECT
MAX(table1.id) as id,
clients.Username as account,
table1.clientid,
SUM(table1.symbols) as symbols,
SUM(table1.tickets) as tickets,
SUM(table1.cash) as cash,
(SUM(CASE WHEN table2.memo = 'Withdraw' THEN amount ELSE 0 END)) AS withdraw,
(SUM(CASE WHEN table2.memo = 'Depos' THEN amount ELSE 0 END)) AS depos,
FROM
table1
LEFT JOIN
(
clients
LEFT JOIN
table2
ON
clients.Fidx = table2.clientid
AND
table2.date >= '01-09-2016'
AND
table2.date <= '01-09-2017'
)
ON
clients.Fidx = table1.clientid
WHERE
table1.tradedate >= '01-09-2016'
AND
table1.tradedate <= '01-09-2017'
GROUP BY
table1.clientid, clients.Username, table2.clientid
ORDER BY
clients.Username;
And I want to get a simple result table combined of three tables:
+---------+--------+---------+
| account |withdraw| depos |
+---------+--------+---------+
| adaf | 300 | 0 |
| rich | 1000 | 355 |
| call | 0 | 45 |
| alen | 0 | 0 |
| courney| 0 | 106 |
| warren | 0 | 0 |
+---------+--------+---------+
What's the problem? - I'm getting the wrong values in the result table. Exactly in withdraw and depos. They're in 4 times more, than they should be. For example, for some client SUM(depos) should be 500, but in my result table this value gets 2000. I guess, the problem is in a GROUP BY method, cause when I'm executing the following query, the result seems to look OK:
SELECT clientid, SUM(case when memo = 'Withdraw' then amount else 0 end) as withdraw, SUM(case when memo = 'Depos' then amount else 0 end) as depos
from clients
LEFT JOIN
table2
ON
clients.Fidx = table2.clientid
WHERE table2.date >= '01-09-2016' and table2.date<='01-09-2017' GROUP BY clientid ORDER BY clientid;
What can be a reason of such a wrong result? I'm in trouble and need your help, guys.
You pretty much answered this yourself with the bottom query. You need to do the summing before the join, in a derived table or subquery, just like you have in the bottom query. This will ensure you join on a many-to-one relationship, instead of a many-to-many, which must be causing your current duplication (or 'multiplied' sums).
SELECT
MAX(table1.id) as id,
clients.Username as account,
table1.clientid,
SUM(table1.symbols) as symbols,
SUM(table1.tickets) as tickets,
SUM(table1.cash) as cash,
withdraw,
depos,
FROM
table1
LEFT JOIN
(SELECT clientid,
SUM(case when memo = 'Withdraw' then amount else 0 end) as withdraw,
SUM(case when memo = 'Depos' then amount else 0 end) as depos
FROM clients
LEFT JOIN table2 ON clients.Fidx = table2.clientid
AND table2.date >= '01-09-2016'
AND table2.date<='01-09-2017'
GROUP BY clientid) clients
ON
clients.clientID = table1.clientid
WHERE
table1.tradedate >= '01-09-2016'
AND
table1.tradedate <= '01-09-2017'
GROUP BY
table1.clientid, clients.Username, table2.clientid, clients.depos, clients.withdraw
ORDER BY
clients.Username;
Further example:
Table1
id | someInfo
1 | a
1 | b
1 | c
Table2
id | value
1 | 5
1 | 10
This query:
SELECT t1.id, SUM(t2.Value)
FROM table1 t1
JOIN table2 t2 on t1.id = t2.id --This will be many-to-many
GROUP BY t1.id
Will result in this:
Results
id | value
1 | 45 --sum of 45 because the `table2` values are triplicated from the join
Where this query:
SELECT DISTINCT t1.id, Value
FROM table1 t1
JOIN (SELECT id, SUM(Value) value
FROM table2
GROUP BY id) t2 on t1.id = t2.id --This will be many-to-one
Will result in this:
Results
id | value
1 | 15 --sum of 15 because the `table2` values are not triplicated from the join
Aggregate before joining:
select
t1.id,
c.Username as account,
c.clientid,
t1.symbols,
t1.tickets,
t1.cash,
coalesce(t2.withdraw, 0) as withdraw,
coalesce(t2.depos, 0) as depos
from clients c
join
(
select
clientid,
max(id) as id,
sum(symbols) as symbols,
sum(tickets) as tickets,
sum(cash) as cash
from table1
where date >= '20160901' and date <= '20170901'
group by clientid
) t1 on t1.clientid = c.fidx
left join
(
select
clientid,
sum(case when memo = 'Withdraw' then amount end) as withdraw,
sum(case when memo = 'Depos' then amount end) as depos
from table2
where date >= '20160901' and date <= '20170901'
group by clientid
) t2 on t2.clientid = c.fidx;

only using select in sql instead of group by

I have this table:
supplier | product | qty
--------------------------
s1 | p1 | 300
s1 | p2 | 90
s2 | p3 | 89
I want to find suppliers with more than 2 products.
But only with select and where, no group by. Any suggestion?
Why would you want not to use group by is beyond me, but this might work:
SELECT Supplier FROM table outer WHERE
(
select count(Products) from table inner
where inner.Supplier = outer.Supplier
) > 2
Please bear in mind, that group by is made for stuff like that and should be used.
;WITH
sequenced_data AS
(
SELECT
supplier,
ROW_NUMBER() OVER (PARTITION BY supplier ORDER BY product) AS supplier_product_ordinal
FROM
YourTable
)
SELECT
supplier
FROM
sequenced_data
WHERE
supplier_product_ordinal = 3
But I'd expect it to be slower than using GROUP BY.
SELECT DISTINCT
supplier
FROM
yourTable
WHERE
EXISTS (SELECT * FROM yourTable AS lookup WHERE supplier = yourTable.supplier AND product < yourTable.product)
AND EXISTS (SELECT * FROM yourTable AS lookup WHERE supplier = yourTable.supplier AND product > yourTable.product);
In the usual parts and suppliers database, this relvar is named SP:
SELECT DISTINCT T1.SNO
FROM SP AS T1
JOIN SP AS T2
ON T1.SNO = T2.SNO
AND T2.PNO <> T1.PNO
JOIN SP AS T3
ON T1.SNO = T3.SNO
AND T3.PNO <> T1.PNO
AND T3.PNO <> T2.PNO;
Noting that you can use HAVING without GROUP BY:
SELECT DISTINCT T1.SNO
FROM SP AS T1
WHERE EXISTS (
SELECT 1
FROM SP AS T2
WHERE T2.SNO = T1.SNO
HAVING COUNT(*) > 2
);
;WITH T AS
(
SELECT *,
COUNT(*) OVER (PARTITION BY S) AS Cnt
FROM YourTable
)
SELECT DISTINCT S
FROM T
WHERE Cnt > 2
with subquery:
select distinct supplier
from table a
where (select count(*)
from table b
where b.supplier = a.supplier and b.product <> a.product
) > 1