SQL Group By with Substr - sql

I am trying to group by with substring and I know I cannot use an alias but even like this, it is not producing any results.
select
substr(cd_orig_bic,5,2) cd,
case
when substr(CD_TXN_TYPE,1,1) = '1' then 'a'
when substr(CD_TXN_TYPE,1,1) = '2' then 'b'
else 'OTHER'
end txn_type,
d_booking,
d_value,
d_execution,
from c.c_t_transaction_queue a join c.c_d_currency b on a.id_currency=b.id_currency
where
d_effective>=to_date('01.01.2017','DD.MM.YYYY')
and
d_effective<=to_date('30.09.2017','DD.MM.YYYY')
and substr(cd_orig_bic,5,2)!='SK'
group by substr(cd_orig_bic,5,2);

You don't have any aggregation function in your query.
Here is how "group by" works:
SELECT sum(column1) -- here is a aggregation function
,column2
FROM table
GROUP BY column2 -- here is the column you want to aggregate on
The result is the sum of column1 for each value of column 2
You can read this article for more information.

Why do you want to group by the data as there is no aggregate function used in the query.
If you want unique values you could have distinct values using distinct keyword.
select distinct
substr(cd_orig_bic,5,2) cd,
case
when substr(CD_TXN_TYPE,1,1) = '1' then 'a'
when substr(CD_TXN_TYPE,1,1) = '2' then 'b'
else 'OTHER'
end txn_type,
d_booking,
d_value,
d_execution,
from c.c_t_transaction_queue a join c.c_d_currency b on a.id_currency=b.id_currency
where
d_effective>=to_date('01.01.2017','DD.MM.YYYY')
and
d_effective<=to_date('30.09.2017','DD.MM.YYYY')
and substr(cd_orig_bic,5,2)!='SK';
Also if you want to have aggregate function you could have above query as inner and use group by in the outer query.
select cd,txn_type,d_booking,sum(d_value) as value,
d_execution from (select
substr(cd_orig_bic,5,2) cd,
case
when substr(CD_TXN_TYPE,1,1) = '1' then 'a'
when substr(CD_TXN_TYPE,1,1) = '2' then 'b'
else 'OTHER'
end txn_type,
d_booking,
d_value,
d_execution,
from c.c_t_transaction_queue a join c.c_d_currency b on a.id_currency=b.id_currency
where
d_effective>=to_date('01.01.2017','DD.MM.YYYY')
and
d_effective<=to_date('30.09.2017','DD.MM.YYYY')
and substr(cd_orig_bic,5,2)!='SK'
) group by cd,txn_type,d_booking,d_execution;

Related

SQL Server : "invalid column name" error after GROUP BY on subquery

I am trying to perform the following query on a SQL Server database table with a GROUP BY on a column which results from a CASE statement done on a subquery:
SELECT
AVG(sales) as avg_sales,
COUNT(*) as total_sales,
CASE
WHEN (pay_method='cc') AND (user_type='subscriber') THEN 'cc-subscribed'
WHEN (pay_method='cash') AND (user_type='subscriber') THEN 'cash-subscribed'
ELSE 'standard'
END as user_payment_type
FROM
(SELECT
column1, column2,
UserType as user_type,
CASE
WHEN column1='something' AND column2='something_else' THEN 'cc'
WHEN column1='something_else' AND column2='something' THEN 'cash'
END as pay_method
FROM MyTable) b
GROUP BY
user_payment_type
The error I am getting is
MSSQLDatabaseException: (207, b"Invalid column name 'user_payment_type'.DB-Lib error message 20018, severity 16:\nGeneral SQL Server error: Check messages from the SQL Server\n")
Note that the column name user_payment_type is unique and does not already exist in MyTable.
SQL Server does not allow the use of this aliased column in the group by clause (others like MySql do allow it) because the group by clause is executed before select.
You have to use that case statement:
group by CASE
WHEN (pay_method='cc') AND (user_type='subscriber') THEN 'cc-subscribed'
WHEN (pay_method='cash') AND (user_type='subscriber') THEN 'cash-subscribed'
ELSE 'standard'
END
Your SELECT and GROUP BY should match. You could avoid duplicating code by using CROSS APPLY:
WITH cte AS (
SELECT column1,
column2,
UserType as user_type,
CASE
WHEN column1='something' AND column2='something_else' THEN 'cc'
WHEN column1='something_else' AND column2='something' THEN 'cash'
END as pay_method
FROM MyTable
)
SELECT AVG(c.sales) as avg_sales,
COUNT(*) as total_sales,
s.user_payment_type
FROM cte c
CROSS APPLY (SELECT CASE
WHEN (pay_method='cc') AND (user_type='subscriber') THEN 'cc-subscribed'
WHEN (pay_method='cash') AND (user_type='subscriber') THEN 'cash-subscribed'
ELSE 'standard' END) s(user_payment_type)
GROUP BY s.user_payment_type
As others have noted, you can't reference column aliases in the group by clause, but should reference the same expression there too.
Note, however, that you're performing two calculations on the same data. You could perform both calculations in the same subquery to make the query shorter and easier to maintain:
SELECT
AVG(sales) as avg_sales,
COUNT(*) as total_sales,
user_payment_type
FROM (
SELECT sales,
CASE
WHEN column1 = 'something' AND
column2 = 'something_else' AND /* These are the conditions for cc */
user_type = 'subscriber'
THEN 'cc-subscribed'
WHEN column1 = 'something_else' AND
column2 = 'something' AND /* conditions for cash */
user_type = 'subscriber'
THEN 'cash-subscribed'
ELSE 'standard'
END as user_payment_type
FROM MyTable
) b
GROUP BY
user_payment_type
A simple way to do this without nested subqueries uses apply:
SELECT v1.user_payment_type,
AVG(t.sales) as avg_sales,
COUNT(*) as total_sales
FROM MyTable t CROSS APPLY
(VALUES (CASE WHEN t.column1 = 'something' AND t.column2 = 'something_else' THEN 'cc'
WHEN t.column1 = 'something_else' AND t.column2 = 'something' THEN 'cash'
END
)
) v(pay_method) CROSS APPLY
(VALUES (CASE WHEN v.pay_method = 'cc' AND t.user_type = 'subscriber' THEN 'cc-subscribed'
WHEN v.pay_method = 'cash' AND t.user_type = 'subscriber' THEN 'cash-subscribed'
ELSE 'standard'
END)
) v1(user_payment_type)
GROUP BY v1.user_payment_type;
This allows you to define interdependent definitions without nesting subqueries or CTEs or repeating definitions.

Want to Return Empty Rows With a Case When Statement

Lets say I'm using 2 case when statements to group my data, like in the below example:
select case
when group1 = 'A' then 'Large'
when group1 = 'B' then 'Medium'
else 'Small'
end as 'Order Size'
,case
when method = 'Delivery' then 'Delivery'
else 'Pick-up'
end as 'Distribution Method'
,count(distinct(OrderIDs))
from OrderTable
GROUP BY
select case
when group1 = 'A' then 'Large'
when group1 = 'B' then 'Medium'
else 'Small'
end
,case
when method = 'Delivery' then 'Delivery'
else 'Pick-up'
end
Lets also say that there were no "Large" deliveries that were "Pick-Up'. Currently, this query will not return a row with Large,PickUp category.
Is there a way to have a row returned with 0’s if there is nothing that meets the multiple case when criteria?
Use a cross join to generate the rows and left join to bring in the data:
select os.OrderSize, coalesce(d.DistributionMethod, 'Pick-Up') as
count(*)
from (select 'Large' as OrderSize union all
select 'Medium' as OrderSize union all
select 'Small' as OrderSize
) os cross join
(select 'Delivery' as DistributionMethod union all
select 'Pick-Up' as DistributionMethod
) d left join
OrderTable ot
on ( (ot.group1 = 'A' and os.OrderSize = 'Large') or
(ot.group1 = 'B' and os.OrderSize = 'Medium') or
(ot.group1 not in ('A', 'B') and os.OrderSize = 'Small')
) and
ot.method = d.DistributionMethod
group by os.OrderSize, coalesce(d.DistributionMethod, 'Pick-Up');
Not all databases support the creation of a table of constants using this syntax, but there is generally some syntax that does this.
You could select a recordset that contains the required values and then left join your grouped recordset from there. Following is an example for SQL Server where you would join your results to [Groupings].[OrderSize] and [Groupings].[DistributionMethod]:
SELECT *
FROM (
SELECT *
FROM (
SELECT 'Large' AS [OrderSize]
UNION
SELECT 'Medium' AS [OrderSize]
UNION
SELECT 'Small' AS [OrderSize]
) AS [OrderSizes]
CROSS JOIN (
SELECT 'Delivery' AS [DistributionMethod]
UNION
SELECT 'Pick-up' AS [DistributionMethod]
) AS [DistributionMethods]
) AS [Groupings]
LEFT JOIN ...

SQL Select a specific value in the group

I have this following table
Dept---------- Sub_Dept---- Dept Type
Sales.............Advertising........A
Sales.............Marketing......... B
Sales.............Analytics.......... C
Operations.....IT..................... C
Operations.....Settlement........C
And the result should be if a department got a department type as A then change all record of that department to A, else keep it same
Dept---------- Sub_Dept---- Dept Type
Sales.............Advertising........A
Sales.............Marketing......... A
Sales.............Analytics.......... A
Operations.....IT..................... C
Operations.....Settlement........C
Anybody can give a suggestion on this? I thought of using the GROUP BY but have to output the Sub Department as well
Thanks a lot
I would do:
update t
set depttype = 'a'
where exists (select 1 from t t2 where t2.dept = t.dept and t2.dept = 'a') and
t.dept <> 'a';
If you just want a select, then do:
select t.*,
(case when sum(case when depttype = 'a' then 1 else 0 end) over (partition by dept) > 1
then 'a'
else depttype
end) as new_depttype
from t;
Use below query
select a11.dept, a12.Sub_Dept, (case when a12.min_dep_type='A' then 'A' else a11.dep_type) as dep_type
from tab a11
JOIN (select dept, min(dep_type) min_dep_type from tab group by dept) a12
on a11.dept = a12.dept
Try this:
update table
set depttype= case when dept in (select dept from table where depttype='a') then 'a' else depttype end
This should work:
select a.dept, a.sub_dept,
case when b.dept is not null then 'A' else dept_type end as dept_type
from aTable a
left join(
select distinct Dept from aTable where dept_type = 'A'
)
b on b.dept = a.dept
You could use analytic functions to check whether exists the specific value in the group.
Try below query:
SELECT t.Dept,
t.Sub_Dept,
NVL(MIN(CASE WHEN t.Dept_Type = 'A'
THEN Dept_Type END) OVER (PARTITION BY t.Dept), t.Dept_Type) AS Dept_Type
FROM table_1 t
Using the analytic function MIN(), you can search for the value of 'A' (if it does exist inside the group). MIN works for non-null values only, so if you don't have any 'A' in the group, the result will be NULL.
At this point, you can use NVL to choose whether to print the value found in the group or the actual dept_type of the row.

Can Oracle PL/SQL CASE statement include a SELECT query?

I'm trying to do something similar to this:
CASE
WHEN number IN (1,2,3) THEN 'Y' ELSE 'N' END;
Instead I want to have a query in the place of the list, like so:
CASE
WHEN number IN (SELECT num_val FROM some_table) THEN 'Y' ELSE 'N' END;
I can't seem to get this to work. Also, here is an example of the query.
SELECT number, (CASE
WHEN number IN (SELECT num_val FROM some_table) THEN 'Y' ELSE 'N' END) AS YES_NO
FROM some_other_table;
Yes, it's possible. See an example below that would do what you are intending. The difference is that it uses EXISTS instead of IN.
SELECT a.number,
(CASE WHEN EXISTS (SELECT null FROM some_table b where b.num_val = a.number)
THEN 'Y'
ELSE 'N'
END) AS YES_NO
FROM some_other_table a;
EDIT:
I confess: I like the answers given by the others better personally.
However, there will be a difference between this query and the others depending on your data.
If for a value number in the table some_other_table you can have many matching entries of num_val in the table some_table, then the other answers will return duplicate rows. This query will not.
That said, if you take the left join queries given by the others, and add a group by, then you won't get the duplicates.
I suggest using an OUTER JOIN instead of trying to use a subquery in a CASE expression:
SELECT t.NUMBER,
CASE
WHEN s.NUM_VAL IS NOT NULL THEN 'Y'
ELSE 'N'
END AS YES_NO
FROM SOME_OTHER_TABLE t
LEFT OUTER JOIN SOME_TABLE s
ON s.NUM_VAL = t.NUMBER
Best of luck.
Seems like you just need to join the tables and do a decode.
with x as
(
select 1 as num from dual
union
select 2 as num from dual
union
select 3 as num from dual
),
y as
(
select 1 as num from dual
union
select 2 as num from dual
union
select 4 as num from dual
)
select x.num, decode(y.num, null, 'N','Y') as yes_no
from x
left outer join y on (x.num = y.num)
Output:
NUM YES_NO
1 Y
2 Y
3 N
You can use subquery in case statement:
select
case dummy
when 'X' then (select 'TRUE' from dual)
else 'FALSE'
end TEST
from dual;
TEST
TRUE
select
case (select 'XXX' from dual)
when 'XXX' then 'TRUE'
else 'FALSE'
end TEST
from dual;
TEST
TRUE

A better way of writing this particular SELECT query in a VIEW

I would like to know if there is a better way of writing the SELECT clause in the query below. There are three possible cases for status. It can either have a value of 'A' or 'N' or null.
I think that placing the AND status = 'A' in the where clause is redundant when you are comparing the subTable values to a value of 'A'. Is there any way that this could be rewritten more efficiently? I feel like restricting the status in the WHERE clause is always going to give you an 'A' and hence pointless to do the CASE statement.
ALTER VIEW dbo.st_review_status_vw AS
(
SELECT c.st_id, c.ms_price_comp_fy,
CASE
WHEN (SELECT status FROM
(SELECT st_id, status, ms_price_comp_fy
FROM ms_price_comp
WHERE st_id = c.st_id
AND ms_price_comp_fy = c.ms_price_comp_fy
AND status = 'A'
GROUP BY st_id, status, ms_price_comp_fy)
AS subTable) = 'A' THEN 'C'
ELSE 'I'
END AS status,
MAX(date_approved) AS date_completed
FROM ms_price_comp AS c
GROUP BY c.st_id, c.ms_price_comp_fy
)
try this
ALTER VIEW dbo.st_review_status_vw AS (
SELECT c.st_id, c.ms_price_comp_fy,
CASE WHEN MIN(status) = 'A' THEN 'C'
ELSE 'I'
END AS status,
MAX(date_approved) AS date_completed
FROM ms_price_comp AS c
GROUP BY c.st_id, c.ms_price_comp_fy
)
This looks to be the same to me
ALTER VIEW dbo.st_review_status_vw AS (
SELECT
c.st_id
, c.ms_price_comp_fy
, CASE
WHEN MIN(status) = 'A' THEN 'C'
ELSE 'I'
END AS status
, MAX(date_approved) AS date_completed
FROM ms_price_comp AS c
GROUP BY c.st_id, c.ms_price_comp_fy
)