Put together two selects into one - sql

Could you help me put the second select into first one? I need calculate rate of type in first select. Second select works good.
First select:
WITH "global" AS (
SELECT
m.id
,json_build_array(
ce.payload->>'Name',
ce.payload->>'Date',
ce.payload->>’Type,
ce.payload->>’Rate’,
row_number() over (partition by m.id order by ce.payload->>’Date’ desc)) as "value"
FROM public."events" ce
LEFT OUTER JOIN "external"."mapping" m
ON ce.id=m.id
WHERE ce.type IN ('cs_calls','pc_calls')
AND coalesce(ce.payload ->> 'Name', '')!=''
AND m.id IS NOT NULL
)
SELECT
id,
value
FROM “global”
Second select:
select
id,
cast(issue as float)/cast(total_count as float) as Rate
from (select
id,
sum(case when type='Issue' then 1 else 0 end) as issue,
count(*) total_count
from events
GROUP BY id)

If Id is the way to join this tables then you can try the following
select
g.id,
g.value,
((issue * 1.0) / total_count) as Rate
from
(
select
id,
sum(case when type='Issue' then 1 else 0 end) as issue,
count(*) total_count
from events
group by
id
) e
join global g
on e.id = g.id

Related

select value based on max of other column

I have a few questions about a table I'm trying to make in Postgres.
The following table is my input:
id
area
count
function
1
100
20
living
1
200
30
industry
2
400
10
living
2
400
10
industry
2
400
20
education
3
150
1
industry
3
150
1
education
I want to group by id and get the dominant function based on max area. With summing up the rows for area and count. When area is equal it should be based on max count, when area and count is equal it should be based on prior function (i still have to decide if education is prior to industry or vice versa). So the result should be:
id
area
count
function
1
300
50
industry
2
1200
40
education
3
300
2
industry
I tried a lot of things and maybe it's easy, but i don't get it. Can someone help to get the right SQL?
One method uses row_number() and conditional aggregation:
select id, sum(area), sum(count),
max(function) over (filter where seqnum = 1) as function
from (select t.*,
row_number() over (partition by id order by area desc) as seqnum
from t
) t
group by id;
Another method uses ``distinct on`:
select id, sum(area) over (partition by id) as area,
sum(count) over (partition by id) as count,
function
from t
order by id, area desc;
Use a scalar sub-query for "function".
select t.id, sum(t.area), sum(t.count),
(
select "function"
from the_table
where id = t.id
order by area desc, count desc, "function" desc
limit 1
) as "function"
from the_table as t
group by t.id order by t.id;
SQL Fiddle
you can use sum as window function:
select distinct on (t.id)
id,
sum(area) over (partition by id) as area,
sum(count) over (partition by id) as count,
( select function from tbl_test where tbl_test.id = t.id order by count desc limit 1 ) as function
from tbl_test t
This is how you get the function for each group based on id:
select id, function
from yourtable yt1
left join yourtable yt2
on yt1.id = yt2.id and yt1.area < yt2.area
where yt2.area.id is null;
(we ensure that no yt2 exists that would be of the same id but of higher areay)
This would work nicely, but you might have several max areas with different values. To cope with this isue, let's ensure that exactly one is chosen:
select id, max(function) as function
from yourtable yt1
left join yourtable yt2
on yt1.id = yt2.id and yt1.area < yt2.area
where yt2.area.id is null
group by id;
Now, let's join this to our main table;
select yourtable.id, sum(yourtable.area), sum(yourtable.count), t.function
from yourtable
join (
select id, max(function) as function
from yourtable yt1
left join yourtable yt2
on yt1.id = yt2.id and yt1.area < yt2.area
where yt2.area.id is null
group by id
) t
on yourtable.id = t.id
group by yourtable.id;

Take precedence on a specific value from a table

For each person's distinct record that has a toyota,
only take toyota and filter out that person's other cars
else bring all cars.
The actual script will not match my logic above. I was trying to simplify my question by using random names and car brands, but the objective was the same since I wanted to get a specific address code and filter out the rest if it did exist for other vendor names (see below). Thank you, GMB.
GPMEM.dbo.PM00200 a -- Vendor Master
LEFT JOIN GPMEM.dbo.PM30200 b -- Historical/Paid Transactions
ON a.VENDORID = b.VENDORID
LEFT JOIN GPMEM.dbo.PM20000 c -- Open/Posted Transactions
ON a.VENDORID = c.VENDORID
LEFT JOIN (
SELECT d.*,
rank() over(
partition by d.VENDORID
order by case when d.ADRSCODE = 'ACH' THEN 0 ELSE 1 END
)rn
FROM GPMEM.dbo.PM00300 d
) d -- Vendor Address Master
ON a.VENDORID = d.VENDORID
WHERE
d.rn = 1
You can use window functions:
select colA, colB
from (
select
t.*,
rank() over(
partition by colA
order by case when colB = 'Toyota' then 0 else 1 end
) rn
from mytable t
) t
where rn = 1
The trick likes in the order by clause in the over() clause of window function rank(): if a person has a Toyota, it will be ranked first, and their (possible) other cars will be ranked second. If it has no Toyota, all their car will be ranked first.
You can do this with filtering logic:
select t.*
from t
where t.colb = 'toyota' or
not exists (select 1 from t t2 where t2.cola = t.cola and t2.colb = 'toyota');
If I were to use window functions for this, I would simply count the toyotas:
select t.*
from (select t.*,
sum(case when colb = 'toyota' then 1 else 0 end) over (partition by cola) as num_toyotas
from t
) t
where colb = 'toyota' or num_toyotas = 0;

How to streamline mssql query that includes calculation

I have 3 tables containing data that I am attempting to get counts and then do calculations. I have a working query but it repetitious.
SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as 'Num_To_Go',
(SELECT COUNT(*) from place_been where people.person_id=person_id) as 'Num_Visited',
((​SELECT​ COUNT(*)​ ​FROM​ place_been ​WHERE​ people.person_id=person_id)​ /​ (​SELECT COUNT(*) ​FROM​ place_to_go ​WHERE​ people.person_id=person_id))​ ​*​ 100 ​AS​ ​'Perc_Visited'
FROM people;
What I'm trying accomplish is to not have the repeated sub queries for the percentage calculation. Any changes I make to that end in syntax errors and it's getting quite frustrating.
Thought I may have been able to use
SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as 'Num_To_Go',
(SELECT COUNT(*) from place_been where people.person_id=person_id) as 'Num_Visited',
(CONVERT(DECIMAL(3,0), 'Num_To_Go'))/(CONVERT(DECIMAL(3,0), 'Num_Visited')​) ​*​ 100 ​AS​ ​'Perc_Visited'
FROM people;
But that ends in an error converting data type varchar to numeric
Any pointers would be very much appreciated.
I would use APPLY :
SELECT person_id, Num_To_Go, Num_Visited, (Num_To_Go * 1.0 / Num_Visited) * 100 AS Perc_Visited
FROM people p OUTER APPLY
( SELECT COUNT(*) AS Num_To_Go
FROM place_to_go pg
WHERE P.person_id = pg.person_id
) pg OUTER APPLY
( SELECT COUNT(*) AS Num_Visited
FROM place_been pb
WHERE p.person_id = pb.person_id
) pb;
You can try using subquery
select *, (CONVERT(DECIMAL(3,0), Num_To_Go))/(CONVERT(DECIMAL(3,0), Num_Visited)​) ​*​ 100.00 ​AS​ ​'Perc_Visited'
from
(
SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as 'Num_To_Go',
(SELECT COUNT(*) from place_been where people.person_id=person_id) as 'Num_Visited',
FROM people
)A
This is a bit of a stab in the dark, however, perhaps:
SELECT p.person_id,
COUNT(DISTINCT p2g.{id_column}) AS NumToGo,
COUNT(DISTINCT pb.{id_column}) AS NNumVisited,
((COUNT(DISTINCT pb.{id_column}) * 1.0) / COUNT(DISTINCT p2g.{id_column})) * 100 AS Perc_Visited --* 1.0 due to integer math. I.e. 99/100 = 0
FROM people p
LEFT JOIN place_to_go p2g ON p.person_id = p2g.person_id
LEFT JOIN place_been pb ON p.person_id = pb.person_id
GROUP BY person_id;
Here's how I'd tackle it: Runnable Example
select ppl.person_id
, coalesce(ptg.cnt,0) as 'Num_To_Go'
, coalesce(pb.cnt,0) as 'Num_Visited'
, case
when coalesce(ptg.cnt,0) = 0 then 100 --avoid /0 error ; if there are no places to go let's say we've been to them all
else 100.0 * coalesce(pb.cnt,0) / ptg.cnt
end 'Perc_Visited'
from people ppl
left outer join (select person_id, count(1) cnt from place_to_go group by person_id) ptg on ptg.person_id = ppl.person_id
left outer join (select person_id, count(1) cnt from place_been group by person_id) pb on pb.person_id = ppl.person_id
I've moved the queries to get counts into subqueries under the FROM clause; so you get a count per person once for each of the tables (place_to_go, place_been), then reuse those results any time you require them.
I join those subqueries using the person_id field. I've used left outer joins so that even if a person doesn't have any records in either table, we still see that person in the results.
I use coalesce(cnt,0) to ensure that should there be no records associated with a person we see 0 instead of null.
I stuck a case statement around the logic to calculate a percentage, since division is involved and potentially the divisor may be 0, resulting in a divide-by-zero error. This case statement ensures that in such situations we return 100%; and only use the calculation where we're safe from this exception.
Finally, I stuck 100.0 * in instead of 100 * to ensure our solution can return non-integer results; i.e. so we're not truncated to 0 decimal places.
However, there's also an issue with your design. It assumes that every place you've been to is listed in the places to go table. If that assumption's true, you're better off having one table for places_to_go with a field to flag whether or not you've been. That way you enforce that rule in your code, improve performance, and reduce space.
i.e. Runnable Example
create table places_to_go
(
place_id bigint not null foreign key references places(place_id)
, person_id bigint not null foreign key references people(person_id)
, have_been bit not null default (0)
--& indexes / primary key field for this table / whatever else as required
)
select ppl.person_id
, coalesce(ptg.cnt_to_go,0) as 'Num_To_Go'
, coalesce(ptg.cnt_have_been,0) as 'Num_Visited'
, case
when coalesce(ptg.cnt_to_go,0) = 0 then null --avoid /0 error ;
else 100.0 * ptg.cnt_have_been / ptg.cnt_to_go
end 'Perc_Visited'
from people ppl
left outer join
(
select person_id
, count(1) cnt_to_go
, count(case when have_been = 1 then 1 end) cnt_have_been
from place_to_go
group by person_id
) ptg
on ptg.person_id = ppl.person_id
Wrap your query up in a derived table. Do the final calculation on its result:
select person_id, [Num_To_Go], [Num_Visited],
[Num_To_Go] * 100.0 / [Num_Visited]​ ​AS​ ​[Perc_Visited]
from
(
SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as [Num_To_Go],
(SELECT COUNT(*) from place_been where people.person_id=person_id) as [Num_Visited]
FROM people
) dt
Or have a CTE (common table expression):
with cte as
(
SELECT person_id,
(SELECT COUNT(*) from place_to_go where people.person_id=person_id) as [Num_To_Go],
(SELECT COUNT(*) from place_been where people.person_id=person_id) as [Num_Visited]
FROM people
)
select person_id, [Num_To_Go], [Num_Visited],
[Num_To_Go] * 100.0 [Num_Visited] ​AS​ [Perc_Visited]
from cte

Use 'group by' on one column but get data of multiple columns

How can you manage to make a 'group by' on one column and still get data (the real data, not a 'sum') on the others?
Let me show an example of what I'd like to do:
Suppose Table A, with index on 'Group', a simple select * from A gives:
Group Album
---------------- ---------------
ABBA Waterloo
AC/DC Back in Black
ABBA Voulez-vous
ABBA Super Trooper
Imagine Dragons Night Visions
AC/DC Highway to Hell
ABBA The Visitors
I'd like to have the end result as following (knowing that I cannot have more than 4 albums for a group ... for now I guess):
Group Album1 Album2 Album3 Album4
---------------- --------------- --------------- --------------- ---------------
ABBA Waterloo Voulez-vous Super Trooper The visitors
AC/DC Back in Black Highway to Hell Null Null
Imagine Dragons Night Visions Null Null Null
So far, the closest I've come to make what I want is something like the following:
select tab4.GROUP,
tab1.ALBUM as PN1,
tab2.ALBUM as PN2,
tab3.ALBUM as PN3,
tab4.ALBUM as PN4
from
(
select A.GROUP, A.ALBUM
from A
where A.ROWID in
(select max(ROWID) from A
where GROUP in (select GROUP from A A group by A.GROUP having count(*) <= 4)
group by GROUP
)
) tab4
left join
(
select A.GROUP, A.ALBUM
from A A
where A.ROWID in
(select max(ROWID) from A
where GROUP in (select GROUP from A A group by A.GROUP having count(*) <= 3)
group by GROUP
)
) tab3 on tab4.GROUP = tab3.GROUP
left join
(
select A.GROUP, A.ALBUM
from A A
where A.ROWID in
(select max(ROWID) from A
where GROUP in (select GROUP from A A group by A.GROUP having count(*) <= 2)
group by GROUP
)
)tab2 on tab4.GROUP = tab2.GROUP
left join
(
select A.GROUP, A.ALBUM
from A A
where A.ROWID in
(select max(ROWID) from A
where GROUP in (select GROUP from A A group by A.GROUP having count(*) <= 1)
group by GROUP
)
) tab1 on tab4.GROUP = tab1.GROUP;
I know why the SQL request above is wrong: the max(rowid) will remain the same whatever condition on having count(*) is thrown.
There could be some pivotto be used, but I sincerely don't see how can it be used as I have only one table and need to get all data.
As furter precision, I don't need the have the result table in a spcific order and I can limit myself to 4 albums because I know each 'Group' won't have more than that ... but I'd appreciate something generic.
EDIT: Ok, seems I have forgotten to clarify that I'm on Oracle 10g (damn this legacy code ^^) so newer functions like PIVOT won't work.
Also, I'm not looking for a string aggregation like LISTAGG but really for separate columns.
#Alex Poole got it right: I was not only missing the equivalent of PIVOT code in 10g, but also ROW_NUMBER().
So the answer to my problem becomes as following:
select
tab1.group_name,
MAX(CASE WHEN tab1.rank_number = 1 THEN tab1.album_name ELSE NULL END) AS ALBUM_1,
MAX(CASE WHEN tab1.rank_number = 2 THEN tab1.album_name ELSE NULL END) AS ALBUM_2,
MAX(CASE WHEN tab1.rank_number = 3 THEN tab1.album_name ELSE NULL END) AS ALBUM_3,
MAX(CASE WHEN tab1.rank_number = 4 THEN tab1.album_name ELSE NULL END) AS ALBUM_4
from (
select group_name, album_name,
row_number() over (partition by group_name order by album_name) as rank_number
from tablea
) tab1
group by tab1.group_name;
Not sure if my title is the best for the kind of problem I had, guess I'll keep it as it is since it revolves around group by as well.
I believe this should work on 10i:
with r as (
select
group_,
album,
row_number() over (partition by group_ order by album) r
from
tq84_table_a
)
select
r.group_,
max(case when r.r=1 then r.album end) album1,
max(case when r.r=2 then r.album end) album2,
max(case when r.r=3 then r.album end) album3,
max(case when r.r=4 then r.album end) album4
from
r
group by
r.group_;
I don't have a 10i installation at hand, right now, so I can't test it.

Retrieving Top 10 rows and sum all others in row 11

I have the following query that retrieve the number of users per country;
SELECT C.CountryID AS CountryID,
C.CountryName AS Country,
Count(FirstName) AS Origin
FROM Users AS U
INNER JOIN Country AS C ON C.CountryID = U.CountryOfOrgin
GROUP BY CASE C.CountryName,
C.CountryID
What I need is a way to get the top 10 and then sum all other users in a single row. I know how to get the top 10 but I`m stuck on getting the remaining in a single row. Is there a simple way to do it?
For example if the above query returns 17 records the top ten are displayed and a sum of the users from the 7 remaining country should appear on row 11. On that row 11 the countryid would be 0 and countryname Others
Thanks for your help!
You did not specify how you are ranking the top 10 so I'm assuming the highest counts are ranked higher?
With TopItems As
(
SELECT C.CountryID AS CountryID
, C.CountryName AS Country
, Count(FirstName) AS Origin
, ROW_NUMBER() OVER( ORDER BY Count(FirstName) DESC ) As Num
FROM Users AS U
JOIN Country AS C
ON C.CountryID = U.CountryOfOrgin
GROUP BY C.CountryName, C.CountryID
)
Select CountryId, Country, Origin
From TopItems
Where Num <= 10
Union ALL
Select 0, 'Others', Sum(Origin)
From TopItems
Where Num > 10
Something like this:
SELECT
-- show them
ROW_NUMBER() OVER (ORDER BY CASE WHEN country_code = 'Others' THEN 1 ELSE 0 END, SUM(n) DESC) AS nr,
countryID,
SUM(n)
FROM (
-- change name for some countries
SELECT
CASE WHEN nr >= 11 THEN 'Others' ELSE countryID END AS countryID,
n
-- select all countries
FROM (
SELECT
-- store number to recognize position
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) AS nr,
countries.countryID,
COUNT(*) AS n
FROM
countries WITH (NOLOCK)
JOIN
users WITH (NOLOCK)
ON
users.countryID = countries.countryID
GROUP BY
countries.countryID
) AS x
) AS y
GROUP BY
countryID
ORDER BY
-- show Others as last one
CASE WHEN countryID = 'Others' THEN 1 ELSE 0 END,
SUM(n) DESC
works for me.