Oracle SQL: SUM of 'amount_sold' rows for each client - sql

I need to display for each client the total /sum/ amount (from amount_sold) saved as a total_amount field. And in WHERE to set that total_amount is greater than or equal to cust_credit_limit :
total_amount >= cust_credit_limit
//
SELECT CONCAT (CONCAT(cust_first_name,' '),cust_last_name) AS customer_name,
amount_sold,
(CASE WHEN cust_credit_limit<=1500 THEN 'Low limit'
ELSE 'High limit'
END) AS credit_limit_level,
cust_valid
FROM sh.customers JOIN sh.sales
ON customers.cust_id = sales.cust_id
ORDER BY customer_name ASC;
The results now look like this:
But I need only one row for each client with the sum of all amount_sold for this client AS total_amount
**EDIT: I tried as recommended in comments and it worked. But I have another condition - to order the results by 'upper_income_level' and when I add
'lpad( substr(cust_income_level, instr( cust_income_level, '-') + 2 ), 9, '0') AS upper_income_level,'
it appears "not a GROUP BY expression".
'SELECT CONCAT (CONCAT(cust_first_name,' '),cust_last_name) AS customer_name,
lpad( substr(cust_income_level, instr( cust_income_level, '-') + 2 ), 9, '0') AS upper_income_level,
SUM(amount_sold) as total_sold,
(CASE WHEN cust_credit_limit<=1500 THEN 'Low limit'
ELSE 'High limit'
END) AS credit_limit_level,
cust_valid
FROM sh.customers JOIN sh.sales
ON customers.cust_id = sales.cust_id
WHERE cust_valid = 'A' AND cust_income_level LIKE '%-%'
GROUP BY
CONCAT (CONCAT(cust_first_name,' '),cust_last_name),
cust_valid,
cust_credit_limit
HAVING SUM(amount_sold) >= 50*cust_credit_limit
ORDER BY upper_income_level DESC, customer_name ASC;'

Your query shall deal with customers and their total sale. So, select from the customers table and join the aggregated total sale:
select
c.cust_first_name || ' ' || c.cust_last_name as customer_name,
to_number
(
regexp_substr(c.cust_income_level , '[0123456789,]+$'),
'999999999D999',
'nls_numeric_characters = '',.'''
) as upper_income_level,
s.total_sale,
case when c.cust_credit_limit <= 1500
then 'Low limit'
else 'High limit'
end as credit_limit_level,
c.cust_valid
from sh.customers c
join
(
select cust_id, sum(amount_sold) as total_sale
from sh.sales
group by cust_id
) s on s.cust_id = c.cust_id
and s.total_sale >= c.cust_credit_limit
where c.cust_valid = 'A'
and c.cust_income_level like '%-%'
order by upper_income_level desc, customer_name;

Add a SUM and a GROUP BY:
SELECT CONCAT (CONCAT(cust_first_name,' '),cust_last_name) AS customer_name,
SUM(amount_sold) as total_sold,
(CASE WHEN cust_credit_limit<=1500 THEN 'Low limit'
ELSE 'High limit'
END) AS credit_limit_level,
cust_valid
FROM sh.customers JOIN sh.sales
ON customers.cust_id = sales.cust_id
GROUP BY
CONCAT (CONCAT(cust_first_name,' '),cust_last_name),
cust_valid,
cust_credit_limit
HAVING SUM(amount_sold) >= cust_credit_limit
ORDER BY customer_name ASC;
Tips:
You might find that your concat accepts multiple parameters and concats all of them e.g. CONCAT(first_name, ' ', last_name)
Rules of GROUP BY: Anything not contained in a SUM, AVG, or similar aggregation function in your SELECT, must be in the GROUP BY. At the time GROUP BY is done, the aliases in the select list don't exist, so you must instead use the expression that prepares the result (or some child part of it such that the whole expression can be computed from grouped values)
HAVING is like a where clause that is done after a group by. WHERE is done before a group by. HAVING can hence reference grouped and aggregated expressions, but they must be grouped or aggregated

Try to use sum(), group by and having:
SELECT CONCAT (CONCAT(cust_first_name,' '),cust_last_name) AS customer_name,
sum(amount_sold) as total_amount,
(CASE WHEN cust_credit_limit<=1500 THEN 'Low limit'
ELSE 'High limit'
END) AS credit_limit_level,
cust_valid
FROM sh.customers JOIN sh.sales
ON customers.cust_id = sales.cust_id
GROUP BY customer_name, credit_limit_level, cust_valid
HAVING sum(amount_sold)>=max(cust_credit_level)
ORDER BY customer_name ASC;

if you have 2 cutomer with the same name,cust_id in group by is the must.
SELECT customers.cust_id,
CONCAT (CONCAT(cust_first_name,' '),cust_last_name) AS customer_name,
sum(isnull(amount_sold,0)) amount_sold
FROM sh.customers JOIN sh.sales
ON customers.cust_id = sales.cust_id
group by customers.cust_id,CONCAT (CONCAT(cust_first_name,' '),cust_last_name)
having sum(isnull(amount_sold,0)) >= max(cust_credit_limit)
ORDER BY customer_name ASC;

Related

Oracle task issue in having clause

In the Having clause I am trying to compare 2 column because the condition is that CUST_CREDIT_LIMIT need to be multiply by 500 and compare with sum(s.amount_sold). Any ideas how to deal with this problem.
select (c.CUST_FIRST_NAME ||' '||c.CUST_LAST_NAME) as "CUSTOMER_NAME",
to_number(substr(c.CUST_INCOME_LEVEL, -7, 8),'999999999') as "UPPER_INCOM_LEVEL",
sum(s.amount_sold) as "TOTAL_AMOUNT",
(case when c.CUST_CREDIT_LIMIT <=1500 then 'Low limit'
when c.CUST_CREDIT_LIMIT >1500 then 'High limit'
end) as "CREDIT_LIMIT_LEVEL",
c.CUST_VALID
from sh.CUSTOMERS c JOIN sh.SALES s
on s.CUST_ID = c.CUST_ID
where CUST_VALID = 'A'
and CUST_INCOME_LEVEL like '%-%'
GROUP BY c.CUST_FIRST_NAME, c.CUST_LAST_NAME, s.AMOUNT_SOLD, c.CUST_INCOME_LEVEL, c.CUST_CREDIT_LIMIT, c.CUST_VALID
having s.amount_sold > c.CUST_CREDIT_LIMIT*500
It looks like you want to put the aggregation into a sub-query with just the SALES table and then JOIN the result of that to the CUSTOMERS table and can compare the credit limit to the sales amount in the join condition (and remove the GROUP BY and HAVING clauses from the outer query):
SELECT c.cust_first_name || ' ' || c.cust_last_name AS customer_name,
TO_NUMBER(SUBSTR(c.cust_income_level, -7), '9999999') AS upper_income_level,
s.total_amount,
CASE
WHEN c.cust_credit_limit <= 1500
THEN 'Low Limit'
WHEN c.cust_credit_limit > 1500
THEN 'High Limit'
END AS credit_limit_level,
c.cust_valid
FROM sh.customers c
INNER JOIN (
SELECT cust_id,
SUM(amount_sold) AS total_amount
FROM sh.sales
GROUP BY cust_id
) s
ON ( c.cust_id = s.cust_id
AND s.total_amount > c.cust_credit_limit * 500 )
WHERE c.cust_valid = 'A'
AND c.cust_income_level LIKE '%-%' -- Check for a hyphen anywhere
-- AND SUBSTR(c.cust_income_level, -8, 1) = '-' -- Check for a hyphen in a specific place
Just like you said (looks like you're missing the sum aggregate function in your having clause) (also, you should put all non-aggregated columns into the group by clause; yours contains s.amount_sold, while it shouldn't):
group by c.cust_first_name,
c.cust_last_name,
substr(c.cust_income_level, -7, 8),
case when c.cust_credit_limit <= 1500 then 'Low limit'
when c.cust_credit_limit > 1500 then 'High limit'
end,
c.cust_valid
having sum(s.amount_sold) > c.cust_credit_limit * 500

Concat/Union two tables in SQL

The sql query below creates two tables, tab1 with 3 columns (quarter, region and datasum) and tab2 with 2 columns (quarter and datasum).
I want to stack the values from tab1 and tab2 together (just like pd.concat([tab1, tab2]) in pandas/python). For that I need to create a new column in tab2 called region and insert that in the same position as the corresponding column in tab1. And after that I think I need to use UNION_ALL.
In tab2 I would like the value of the column region to be 'all' for every instance.
How could I achieve this?
I tried to use ALTER TABLE and ADD but I don't get that to work for me. Help would be much appreciated.
I work in SQL Oracle.
with base1 as(
select substr(municip,1,2) as region, data, age,
case when substr(time,6,2) in ('01','02','03') then substr(time, 1,4) || '_1'
when substr(time,6,2) in ('04','05','06') then substr(time, 1,4) || '_2'
when substr(time,6,2) in ('07','08','09') then substr(time, 1,4) || '_3'
else substr(time, 1,4) || '_4' end quarter
from sql_v1
where time >= '2021-01' and
),
base2 as(select data, age,
case when substr(time,6,2) in ('01','02','03') then substr(time, 1,4) || '_1'
when substr(time,6,2) in ('04','05','06') then substr(time, 1,4) || '_2'
when substr(time,6,2) in ('07','08','09') then substr(time, 1,4) || '_3'
else substr(time, 1,4) || '_4' end quarter
from sql_v1
where time >= '2021-01'),
tab1 as (select quarter, region,
sum (case when age between '16' and '64' then kvar else 0 end) datasum
from base
group by quarter, region
order by quarter, region),
tab2 as (select quarter,
sum (case when age between '16' and '64' then kvar else 0 end) datasum
from riket
group by quarter
order by quarter)
...select * from tab_union
It appears that you're using strings for storing dates??? Don't do that :(
If you use native datetime datatypes then you can extract the quarter using things like TO_CHAR(datetime_column, 'Q')
For now, however, using the horrendous datatypes, you can restructure your query to use ROLLUP in your GROUP BY...
WITH
formatted AS
(
SELECT
SUBSTR(municip,1,2) AS region,
SUBSTR(time, 1,4) || CASE WHEN SUBSTR(time,6,2) IN ('01','02','03') THEN '_1'
WHEN SUBSTR(time,6,2) IN ('04','05','06') THEN '_2'
WHEN SUBSTR(time,6,2) IN ('07','08','09') THEN '_3'
ELSE '_4' END AS quarter,
age,
kvar
FROM
sql_v1
WHERE
time >= '2021-01'
)
SELECT
region,
COALESCE(quarter, 'All'),
SUM(CASE WHEN age BETWEEN 16 AND 64 THEN kvar ELSE 0 END)
FROM
formatted
GROUP BY
region, ROLLUP(quarter)
ORDER BY
region,
GROUPING(quarter),
quarter
Demo : https://dbfiddle.uk/?rdbms=oracle_21&fiddle=ab27d71ac81bb9bc7e5e06e7f5a44ba9
Or, using DATE datatype : https://dbfiddle.uk/?rdbms=oracle_21&fiddle=1544406dc2b6669bc62ed02a6155b1c2

Trouble with SQL request

I just want to output only those records, that for the same name customer_name have cust_valid= 'I' and cust_valid='A'
I tried to do this, but the rezult for cust_valid have only records 'A' enter code here
SELECT c.cust_first_name ||' '|| c.cust_last_name AS CUSTOMER_NAME,
to_number(SUBSTR(c.cust_income_level, INSTR(c.cust_income_level, '-')+2), '999999') as UPPER_INCOME_LEVEL,
sum(s.amount_sold) as TOTAL_AMOUNT,
(CASE WHEN c.cust_credit_limit <= 1500 THEN 'Low_limit'
ELSE 'High_limit'
END) credit_limit_level,
c.cust_valid
FROM SH.customers c
JOIN sh.sales s on c.cust_id = s.cust_id
WHERE c.cust_valid = 'A' AND c.cust_income_level like '%-%'
GROUP BY c.cust_first_name, c.cust_last_name, c.cust_income_level, c.cust_credit_limit, c.cust_valid
HAVING SUM(s.amount_sold) > (c.cust_credit_limit * 50)
ORDER BY UPPER_INCOME_LEVEL DESC, CUSTOMER_NAME;
You need to use the EXISTS clause in WHERE condition as follows:
and exists (select 1
from sh.customers cin
where cin.cust_id = c.cust_id
and c.c.cust_valid = 'I'
)
If I followed you correctly, you can implement this filtering by modifying your having clause:
HAVING
SUM(s.amount_sold) > (c.cust_credit_limit * 50)
AND MAX(CASE WHEN cust_valid= 'I' THEN 1 ELSE 0 END) = 1
AND MAX(CASE WHEN cust_valid= 'A' THEN 1 ELSE 0 END) = 1
and cust_valid='A'
I tried, but no result.
I dont think I showed the condition correct.
I have to explain...
Modify the query to display the name of the client (CUSTOMER_NAME)
for which there are rows with CUST_VALID = ā€˜Aā€™ and rows with
CUST_VALID = ā€˜Iā€™ as a separate result.

SQL - Trying to find customer by their shopping channel

Hello I am trying to find the customers who just shop online and just shop in store and the customers who shop both online and in store. So when I add them up they should be equal to my total customers.
I am trying to find the new and returning customer by their shopping channel. I need a sql to give me all the new customer and returning customers who have shopped in store, and then in a separate table all the new/returning customers who have shopped only online and then people who have shopped both online and in store (crossover customers). So that when I add off them together they should be equal to my total customers in each category (new and returning).
It should look like below:
how data should look like
I have created a sample database as well. I am also trying to break the customer by new and returning customers and later by their age range.
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=96a7b85c8ca0da7f7c40f20205964d9b
these are some of the queries which I have tried: Below is the one which shows me the new and the returning customers who have only bough online:
SELECT
DECODE(is_new, 1, 'New Customers', 'Returning Customers') type_of_customer,
COUNT(distinct individual_id) count_of_customers,
SUM(count_of_transactions) count_of_transactions,
SUM(sum_of_quantity) sum_of_quantity
FROM (
SELECT
individual_id,
SUM(dollar_value_us),
sum(quantity) sum_of_quantity,
count(distinct transaction_number) count_of_transactions,
CASE WHEN MIN(txn_date) = min_txn_date THEN 1 ELSE 0 END is_new
FROM (
SELECT
a.individual_id,
a.dollar_value_us,
a.txn_date,
a.quantity,
a.transaction_number,
b.gender,
b.age,
MIN(a.txn_date) OVER(PARTITION BY a.individual_id) min_txn_date,
A.TRANTYPE
FROM transaction_detail_mv a
join gender_details b on a.individual_id = b.individual_id
WHERE
a.brand_org_code = 'BRAND'
AND a.is_merch = 1
AND a.currency_code = 'USD'
AND a.line_item_amt_type_cd = 'S'
AND a.individual_id not in (select individual_id from transaction_detail_mv where trantype = 'POS' )
)
WHERE
txn_date >= TO_DATE('10-02-2019', 'DD-MM-YYYY')
AND txn_date < TO_DATE('17-02-2019', 'DD-MM-YYYY')
GROUP BY
individual_id,
min_txn_date
)
GROUP BY is_new
and to find the new and returnign customers who buy form POS is bewow:
SELECT
DECODE(is_new, 1, 'New Customers', 'Returning Customers') type_of_customer,
COUNT(distinct individual_id) count_of_customers,
SUM(count_of_transactions) count_of_transactions,
SUM(sum_of_quantity) sum_of_quantity
FROM (
SELECT
individual_id,
SUM(dollar_value_us),
sum(quantity) sum_of_quantity,
count(distinct transaction_number) count_of_transactions,
CASE WHEN MIN(txn_date) = min_txn_date THEN 1 ELSE 0 END is_new
FROM (
SELECT
a.individual_id,
a.dollar_value_us,
a.txn_date,
a.quantity,
a.transaction_number,
b.gender,
b.age,
MIN(a.txn_date) OVER(PARTITION BY a.individual_id) min_txn_date,
A.TRANTYPE
FROM transaction_detail_mv a
join gender_details b on a.individual_id = b.individual_id
WHERE
a.brand_org_code = 'BRAND'
AND a.is_merch = 1
AND a.currency_code = 'USD'
AND a.line_item_amt_type_cd = 'S'
AND a.individual_id not in (select individual_id from transaction_detail_mv where trantype = 'ONLINE' )
)
WHERE
txn_date >= TO_DATE('10-02-2019', 'DD-MM-YYYY')
AND txn_date < TO_DATE('17-02-2019', 'DD-MM-YYYY')
GROUP BY
individual_id,
min_txn_date
)
GROUP BY is_new
I am trying to find new and old customers who have shopped both online and in POS. Please HELP !
You are almost there. Try this:
SELECT
DECODE(is_new, 1, 'New Customers', 'Returning Customers') type_of_customer,
COUNT(distinct individual_id) count_of_customers,
SUM(count_of_transactions) count_of_transactions,
SUM(sum_of_quantity) sum_of_quantity
FROM (
SELECT
individual_id,
SUM(dollar_value_us),
sum(quantity) sum_of_quantity,
count(distinct transaction_number) count_of_transactions,
CASE WHEN MIN(txn_date) = min_txn_date THEN 1 ELSE 0 END is_new
FROM (
SELECT
a.individual_id,
a.dollar_value_us,
a.txn_date,
a.quantity,
a.transaction_number,
b.gender,
b.age,
MIN(a.txn_date) OVER(PARTITION BY a.individual_id) min_txn_date,
A.TRANTYPE
FROM transaction_detail_mv a
join gender_details b on a.individual_id = b.individual_id
WHERE
a.brand_org_code = 'BRAND'
AND a.is_merch = 1
AND a.currency_code = 'USD'
AND a.line_item_amt_type_cd = 'S'
AND a.individual_id not in (select individual_id from transaction_detail_mv where ((trantype = 'ONLINE') OR (trantype = 'POS') )
)
WHERE
txn_date >= TO_DATE('10-02-2019', 'DD-MM-YYYY')
AND txn_date < TO_DATE('17-02-2019', 'DD-MM-YYYY')
GROUP BY
individual_id,
min_txn_date
)
GROUP BY is_new

How should I combine these SQL queries: self-join or use temporary tables?

I'm creating a stored procedure pulling aggregate sum values from a few different tables. Separately, the queries are simplistic with different filters.
The queries need to be joined together and are as follows:
select distinct(bus_name), sum(act) as 'totrev', sum(budget) as 'budget rev'
from finance
where year = '2011'
and type_desc = 'rev'
group by bus_code, bus_name
order by bus_name asc
select distinct(bus_name), sum(act) as 'totalexp', sum(budget) as 'budget exp'
from finance
where year = '2011'
and type_desc = 'exp'
group by bus_code, bus_name
order by bus_name asc
select distinct(bus_name), sum(end_balance) as 'total assets'
from Balance
where year = '2011'
and type_desc = 'assets'
group by bus_code, bus_name
order by bus_name asc
select distinct(bus_name), sum(end_balance) as 'Cash'
from Balance
where year = '2011'
and type_desc = 'equity'
group by bus_code, bus_name
order by bus_name asc
select bus_code, bus_name, count(bus_code) as '#of bldgs'
from building
group by bus_code, bus_name
order by bus_name asc
I'm looking to merge/join all the columns to be viewed essentially in one table.
finance_table
columns = bus_code, bus_name, # of bldgs, tot_rev, budget_rev, totalexp, budget exp, total assets, cash
Try something like this by using nested queries:
SELECT T5.bus_code, T5.bus_name, T5.[# of bldgs], T1.tot_rev, T1.budget_rev, T2.totalexp, T2.[budget exp], T3.[total assets], T4.cash
FROM
(
select distinct(bus_name), sum(act) as 'totrev', sum(budget) as 'budget rev'
from finance
where year = '2011'
and type_desc = 'rev'
group by bus_code, bus_name
order by bus_name asc
) T1 INNER JOIN
(
select distinct(bus_name), sum(act) as 'totalexp', sum(budget) as 'budget exp'
from finance
where year = '2011'
and type_desc = 'exp'
group by bus_code, bus_name
order by bus_name asc
) T2 ON T1.bus_name = T2.bus_name
INNER JOIN
(
select distinct(bus_name), sum(end_balance) as 'total assets'
from Balance
where year = '2011'
and type_desc = 'assets'
group by bus_code, bus_name
order by bus_name asc
) T3 ON T2.bus_name = T3.bus_name
INNER JOIN
(
select distinct(bus_name), sum(end_balance) as 'Cash'
from Balance
where year = '2011'
and type_desc = 'equity'
group by bus_code, bus_name
order by bus_name asc
) T4 ON T3.bus_name = T4.bus_name
INNER JOIN
(
select bus_code, bus_name, count(bus_code) as '#of bldgs'
from building
group by bus_code, bus_name
order by bus_name asc
) T5 ON T4.bus_name = T5.bus_name
I assume inner joins, but you may need to use outer joins if some of these won't have an entry for a particular business. But the general technique would be the same.
If your SQL supports CASE expressions, you can use them to create "virtual" fields for each type, and then sum these up.
select bus_code, bus_name
,sum(case when type_desc = 'rev' then act else 0 end) as 'totrev'
,sum(case when type_desc = 'rev' then budgetelse 0 end) as 'budget rev'
,sum(case when type_desc = 'exp' then act else 0 end) as 'totexp'
,sum(case when type_desc = 'exp' then budgetelse 0 end) as 'budget exp'
... ... etc.
from finance
where year = '2011'
group by bus_code, bus_name
order by bus_name asc
The last (building) table can simple be joined to this one, on bus-code