SQL Case to Sum - tsql

I am grouping products based on customer, reporting back how many we have in stock and their condition code. The condition code we're using is not what we want to report back, so I am using CASE to change this. There is one product that has a blank value in the condition code. This product is showing twice, once with a condition code of A (quanitity 7100), and once with a condition code of blank (quantity 4). I need to group by using the condition code as well, as we could have the same product in different conditions. For any blanks, I need to include them to be an A. Using CASE to change the blank to an A reports the product twice, one with 7100 and one with 4. I need to sum these two together. Here is the code I am using currently.
SELECT CLIENTNAME, PRODUCT, SUM(QUANTITY) as 'On Hand',
CASE (RESERVED)
WHEN 'B' THEN 'H'
WHEN 'I' THEN 'L'
WHEN '0' THEN 'A'
ELSE RESERVED
END AS 'COND_CD'
FROM binlocat
GROUP BY
CLIENTNAME, PRODUCT, RESERVED
ORDER BY
PRODUCT

Repeat the CASE in the GROUP BY or perform the substitution then sum;
;WITH T(CLIENTNAME, PRODUCT, QUANTITY, RESERVED) AS
(
SELECT CLIENTNAME, PRODUCT, QUANTITY,
CASE (RESERVED)
WHEN 'B' THEN 'H'
WHEN 'I' THEN 'L'
WHEN '0' THEN 'A'
ELSE RESERVED
END
FROM binlocat
)
SELECT CLIENTNAME, PRODUCT, SUM(QUANTITY)
FROM T
GROUP BY CLIENTNAME, PRODUCT, RESERVED
ORDER BY PRODUCT

You can wrap your query like this:
SELECT CLIENTNAME, PRODUCT, COND_CD, SUM(QUANTITY) AS 'On Hand'
FROM
(
SELECT CLIENTNAME, PRODUCT, QUANTITY,
CASE (RESERVED)
WHEN 'B' THEN 'H'
WHEN 'I' THEN 'L'
WHEN '0' THEN 'A'
ELSE RESERVED
END AS 'COND_CD'
FROM binlocat
)
GROUP BY
CLIENTNAME, PRODUCT, COND_CD
ORDER BY
PRODUCT
The inner SELECT creates a table on which the outer select can do the grouping.

Related

How to create a comparison chart with feature count in PostgreSQL?

I have OpenStreetMap data loaded to a PostgreSQL table. A hstore type column contains all of the tags. I would like to make a comparison chart to see how many records has name, name:en, name:bg tags for example. The result I would like to see is something like this:
I can achieve this manually using this query:
SELECT 1 AS id, '+' AS name, NULL AS "name:en", NULL AS "name:bg", count(*) FROM public.ways WHERE exist(tags,'name') UNION
SELECT 2 AS id, NULL AS name, '+' AS "name:en", NULL AS "name:bg", count(*) FROM public.ways WHERE exist(tags,'name:en') UNION
SELECT 3 AS id, NULL AS name, NULL AS "name:en", '+' AS "name:bg", count(*) FROM public.ways WHERE exist(tags,'name:bg') UNION
SELECT 4 AS id, '+' AS name, '+' AS "name:en", NULL AS "name:bg", count(*) FROM public.ways WHERE exist(tags,'name') AND exist(tags,'name:en') UNION
SELECT 5 AS id, '+' AS name, NULL AS "name:en", '+' AS "name:bg", count(*) FROM public.ways WHERE exist(tags,'name') AND exist(tags,'name:bg') UNION
SELECT 6 AS id, '+' AS name, '-' AS "name:en", NULL AS "name:bg", count(*) FROM public.ways WHERE exist(tags,'name') AND NOT exist(tags,'name:en') UNION
SELECT 7 AS id, '-' AS name, '+' AS "name:en", NULL AS "name:bg", count(*) FROM public.ways WHERE NOT exist(tags,'name') AND exist(tags,'name:en')
ORDER BY id
I consider this unnecessarily long and overcomplicated, plus I have to do it manually. I know there are some possibilities using the crosstab function, but I couldn't get it working. Based on the answer to this question I was able to create something like this:
SELECT * FROM crosstab(
'SELECT tags::text~''"name"=>".*"'' as a, tags::text~''"name:en"=>".*"'' as b, tags::text~''"name_int"=>".*"'' as c FROM public.ways')
AS ct (name boolean,"name:en" boolean, "name:bg" boolean)
GROUP BY name,"name:en","name:bg"
My problem is that I cannot seem to add a count column to this, and that it does not contain options where only one of the three condition is taken into account.
Any idea how could I solve this problem, or any direction where should I start?
Example data lines:
1 "name"=>"dm"
2 "name"=>"Ешекчи дере", "name:en"=>"Khatak Dere River"
3 "name:en"=>"Sushitsa"
4 "name"=>"Слънчева", "name:bg"=>"Слънчева", "name:en"=>"Slantcheva"
Hello look if its works for you , it is possible to generate a join from a emulated table from a select to group the values :
SELECT row_number() OVER() AS id ,COUNT(*) AS count , COALESCE(a.tags , '')||COALESCE(b.tags,'')||COALESCE(c.tags ,'') AS tagcombination,
CASE WHEN COALESCE(a.tags , '')||COALESCE(b.tags,'')||COALESCE(c.tags ,'')="name:en" THEN '+'
WHEN COALESCE(a.tags , '')||COALESCE(b.tags,'')||COALESCE(c.tags ,'') = 'name:en' THEN '+' END AS name
FROM public.ways AS a
LEFT JOIN (SELECT DISTINCT tags FROM public.ways WHERE tags = 'name' ) AS b ON a.tags = b.tags
LEFT JOIN (SELECT DISTINCT tags FROM public.ways WHERE tags IN('name:en', 'name:bg' ) ) AS c ON a.tags = c.tags
JOIN (SELECT generate_series )
GROUP BY tagcombination
--WHERE a.tags IS NOT NULL
--ORDER BY name
The name column could be translated into numbers from the tagscombination and even be ordered later if it fits better your relatory.
Need to do the test and use a predicate to filter if there is more values possibilities than you want to count in the table also.

How to perform Grouping equivalent like Informatica?

I've an Informatica function which I want to convert into query to be getting used in Spring Batch code.
I've a table EMPLOYEE table having 15 fields (all I want in select) and Informatica has function Router which creates group based on STATUS_CD = 'A' and default (means all other records should go here - where status is other than A).
How can we do in Postgres?
I've all the employees and I want to check based using combination of EMPLOYEE_CD, EMPLOYEE_ID is unique and I want to simply return the count of it.
Query1
SELECT EMPLOYEE_CD AS EMPLOYEE_CD,
EMPLOYEE_ID AS EMPLOYEE_ID,
COUNT (*) AS CNT
FROM EMPLOYEE
GROUP BY EMPLOYEE_CD, EMPLOYEE_ID
HAVING COUNT (*) > 1;
Query 2
SELECT EMPLOYEE_ID, EMPLOYEE_NAME, EMPLOYEE_EMAIL, EMPLOYEE_PHONE, EMPLOYEE_ADDRESS, (Create Count Field here)
FROM EMPLOYEE
Query 3 - I need to group (which is my original question) or Create Columns ACTIVE, NON_ACTIVE columns as a part of query results where EMPLOYEE_STAT_CD = 'A', ACTIVE column value should say YES and EMPLOYEE_STAT_CD other than A, NON_ACTIVE should say Yes.
How can merge Query1 and Query 2 and Query 3 into single query ?
if I understood the question, your code is something like:
SELECT EMPLOYEE_ID, EMPLOYEE_NAME, EMPLOYEE_EMAIL, EMPLOYEE_PHONE, EMPLOYEE_ADDRESS,
COUNT(*)OVER(PARTITION BY EMPLOYEE_CD, EMPLOYEE_ID) AS counter_from_sql1,
CASE WHEN EMPLOYEE_STAT_CD = 'A' THEN 'YES' ELSE NULL END AS ACTIVE,
CASE WHEN EMPLOYEE_STAT_CD <> 'A' THEN 'YES' ELSE NULL END AS NON_ACTIVE
FROM EMPLOYEE;
or
SELECT * FROM (
SELECT EMPLOYEE_ID, EMPLOYEE_NAME, EMPLOYEE_EMAIL, EMPLOYEE_PHONE, EMPLOYEE_ADDRESS,
COUNT(*)OVER(PARTITION BY EMPLOYEE_CD, EMPLOYEE_ID) AS counter_from_sql1,
CASE WHEN EMPLOYEE_STAT_CD = 'A' THEN 'YES' ELSE NULL END AS ACTIVE,
CASE WHEN EMPLOYEE_STAT_CD <> 'A' THEN 'YES' ELSE NULL END AS NON_ACTIVE
FROM EMPLOYEE
) z
WHERE counter_from_sql1 > 1;

How to optimize CASE statement in SELECT query which has same check condition for three different columns

I have below select statement to fetch the data having CASE statement.
SELECT PK_ID
,MGR_ID
,EMP_ID
,CASE
WHEN msts.MGR_ID is null AND msts.EMP_ID is not null THEN
(SELECT 'A' from dual)
ELSE
(SELECT 'B' from dual)
END FIRST_COL
,CASE
WHEN msts.MGR_ID is null AND msts.EMP_ID is not null THEN
(SELECT 'P' from dual)
ELSE
(SELECT 'Q' from dual)
END SECOND_COL
,CASE
WHEN msts.MGR_ID is null AND msts.EMP_ID is not null THEN
(SELECT 'X' from dual)
ELIE
(SELECT 'Y' from dual)
END THIRID_COL
from m_sel_tabs msts
here, As i know that we can check condition with mulitiple columns but the result will be only one for CASE statement.
so my question is here that as my check CASE statement is the same for all three columns (FIRST_COL, SECOND_COL, THIRID_COL), hence is there any other way to write this query in an optimized way.
Thanks in advance.
Maybe I'm wrong, but - when saying that you have "the same 3 CASE statements" - well, you don't. Those are 3 different CASE statements. An expression is the same, yes, but you're selecting 3 different columns, with different results (A, B, P, Q, X, Y - in your example).
You could create a function which would "hide" code you currently use and make the SELECT prettier, such as
select
pk_id, ...,
f_col(1, MGR_ID, EMP_ID) first_col,
f_col(2, MGR_ID, EMP_ID) second_col,
f_col(3, MGR_ID, EMP_ID) third_col
from m_sel_tabs
but - at the end - it would be just the same (or, possibly somewhat worse because of context switching) as you'd have to put all that code somewhere (into the function, right?).

PostgreSQL crosstab() alternative with CASE and aggregates

I want to create a pivot table view showing month on month sum of bookings for every travel_mode.
Table bookings:
timestamp
, bookings
, provider_id
Table providers:
provider_id
, travel_mode
Pivot table function and crosstab functions are not to be used to do this. So I am trying to use JOIN and CASE. Following is the query:
SELECT b.month,
(CASE WHEN p.travel_mode=train then b.amount end)train,
(CASE WHEN p.travel_mode=bus then b.amount end)bus,
(CASE WHEN p.travel_mode=air then b.amount end)air
FROM
(SELECT to_char(date_,month) as month, travel_mode, sum(bookings) as amount
from bookings as b
join providers as p
on b.provider_id=p.provider_id
group by b.month, p.travel_mode)
group by b.month;
However I am getting an error which says:
subquery in FROM must have an alias LINE 6:
And when I add an alias it throws an error saying:
column p.travel_mode must appear in the GROUP BY clause or be used in an aggregate function
LINE 2:
The final result should be something like this
Month Air Bus Train
01 Amount(air) Amount(Bus) Amount(train)
I have a feeling it is a minor error somewhere but I am unable to figure it out at all.
P.S. I had to remove all quotations in the question as it was not allowing me to post this. But those are being taken care of in the actual query.
Multiple problems. The missing table alias is just one of them. This query should work:
SELECT month
, sum(CASE WHEN travel_mode = 'train' THEN amount END) AS train
, sum(CASE WHEN travel_mode = 'bus' THEN amount END) AS bus
, sum(CASE WHEN travel_mode = 'air' THEN amount END) AS air
FROM (
SELECT to_char(timestamp, 'MM') AS month, travel_mode, sum(bookings) AS amount
FROM bookings b
JOIN providers p USING (provider_id)
GROUP BY month, p.travel_mode
) sub
GROUP BY month;
Missing single quotes for string literals. (You seem to have removed those being under the wrong impression you couldn't post quotations.)
Missing table alias for the subquery - just like the 1st error message says.
In the outer query, table names (or aliases) of underlying tables in the subquery are not visible. Only the table alias of the subquery is. Since there is only one subquery, you don't need table-qualification at all there.
month is an output column name (not in the underlying table), so the table qualification b.month was wrong, too.
You seem to want 2-digit numbers for months. Use the template pattern 'MM' instead of 'month' with to_char().
The aggregation in the outer query does not work like you had it - just like your 2nd error message says. You have to wrap the outer CASE expression in a aggregate function. You might as well use min() or max() in this case, because there are never more than one rows after the subquery.
Still unclear where date_ is coming from? You mean timestamp? (which is not a good identifier).
But you don't need the subquery to begin with and can simplify to:
SELECT to_char(timestamp, 'MM') AS month
, sum(CASE WHEN p.travel_mode = 'train' THEN b.bookings END) AS train
, sum(CASE WHEN p.travel_mode = 'bus' THEN b.bookings END) AS bus
, sum(CASE WHEN p.travel_mode = 'air' THEN b.bookings END) AS air
FROM bookings b
JOIN providers p USING (provider_id)
GROUP BY 1;
For best performance you should still use crosstab(), though:
PostgreSQL Crosstab Query
You have to name the subquery as the error message says:
SELECT b.month,
(CASE WHEN p.travel_mode=train then b.amount end)train,
(CASE WHEN p.travel_mode=bus then b.amount end)bus,
(CASE WHEN p.travel_mode=air then b.amount end)air
FROM
(SELECT to_char(date_,month) as month, travel_mode, sum(bookings) as amount
from bookings as b
join providers as p
on b.provider_id=p.provider_id
group by b.month, p.travel_mode)
**as foo** group by b.month;
Remove the stars to make it work.

Unable to get Percentile_Cont() to work in Postgresql

I am trying to calculate a percentile using the percentile_cont() function in PostgreSQL using common table expressions. The goal is find the top 1% of accounts regards to their balances (called amount here). My logic is to find the 99th percentile which will return those whose account balances are greater than 99% of their peers (and thus finding the 1 percenters)
Here is my query
--ranking subquery works fine
with ranking as(
select a.lname,sum(c.amount) as networth from customer a
inner join
account b on a.customerid=b.customerid
inner join
transaction c on b.accountid=c.accountid
group by a.lname order by sum(c.amount)
)
select lname, networth, percentile_cont(0.99) within group
order by networth over (partition by lname) from ranking ;
I keeping getting the following error.
ERROR: syntax error at or near "order"
LINE 2: ...ame, networth, percentile_cont(0.99) within group order by n..
I am thinking that perhaps I forgot a closing brace etc. but I can't seem to figure out where. I know it could be something with the order keyword but I am not sure what to do. Can you please help me to fix this error?
This tripped me up, too.
It turns out percentile_cont is not supported in postgres 9.3, only in 9.4+.
https://www.postgresql.org/docs/9.4/static/release-9-4.html
So you have to use something like this:
with ordered_purchases as (
select
price,
row_number() over (order by price) as row_id,
(select count(1) from purchases) as ct
from purchases
)
select avg(price) as median
from ordered_purchases
where row_id between ct/2.0 and ct/2.0 + 1
That query care of https://www.periscopedata.com/blog/medians-in-sql (section: "Median on Postgres")
You are missing the brackets in the within group (order by x) part.
Try this:
with ranking
as (
select a.lname,
sum(c.amount) as networth
from customer a
inner join account b on a.customerid = b.customerid
inner join transaction c on b.accountid = c.accountid
group by a.lname
order by networth
)
select lname,
networth,
percentile_cont(0.99) within group (
order by networth
) over (partition by lname)
from ranking;
I want to point out that you don't need a subquery for this:
select c.lname, sum(t.amount) as networth,
percentile_cont(0.99) within group (order by sum(t.amount)) over (partition by lname)
from customer c inner join
account a
on c.customerid = a.customerid inner join
transaction t
on a.accountid = t.accountid
group by c.lname
order by networth;
Also, when using table aliases (which should be always), table abbreviations are much easier to follow than arbitrary letters.