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
Related
I have this query, where I created two columns for 'female' and 'male'
SELECT
ID,
UserName,
concat(FirstName,' ',LastName) as Name,
iif(substring(ID,10,1) % 2 =0,'Female', 'Male') as Gender
INTO NewUsers1
FROM Users
Now, I want to get the Average age for both genders. I want to separate this into two columns named ' gender' and 'average age' with one row for 'female average age' and one row for 'male average age'
How can I do this?
The age is seen from the column ID, where the number is the date of birth.
ID
500603-4268 <-- birth year is 3rd June 1950.
500607-6521 <-- birth year is 7th June 1950.
530407-7989 <-- birth year is 4th April 1953.
530720-7675
540430-4887
This gives you average ages in two columns without loosing other columns:
select
*
, avg(case when Gender = 'Male' then null else age end) over(partition by Gender) avgAge_Female
, avg(case when Gender = 'Female' then null else age end) over(partition by Gender) avgAge_Male
from
(
select
f1.*
, datediff(
year
, cast(
concat(
concat('19', left(f1.vl, 2))
, '-'
, right(left(f1.vl, 4), 2)
, '-'
, right(f1.vl, 2)
)
as date)
, getdate()
) age
from
(
select
ID,
UserName,
concat(FirstName,' ',LastName) as Name,
left(ID, charindex('-', ID) - 1) vl,
iif(substring(ID,10,1) % 2 =0,'Female', 'Male') as Gender
from Users
) f1
) g1
Edit: Combined into one column version:
Note: I've used subqueries to make it easy to understand so it is not an optimized query.
select
h1.ID
, h1.UserName
, h1.Name
, h1.Gender
, isnull(h1.avgAge_Male, h1.avgAge_Female) avgAge_MaleOrFemale
from
(
select
*
, avg(case when Gender = 'Male' then null else age end) over(partition by Gender) avgAge_Female
, avg(case when Gender = 'Female' then null else age end) over(partition by Gender) avgAge_Male
from
(
select
f1.*
, datediff(
year
, cast(
concat(
concat('19', left(f1.vl, 2))
, '-'
, right(left(f1.vl, 4), 2)
, '-'
, right(f1.vl, 2)
)
as date)
, getdate()
) age
from
(
select
ID,
UserName,
concat(FirstName,' ',LastName) as Name,
left(ID, charindex('-', ID) - 1) vl,
iif(substring(ID,10,1) % 2 =0,'Female', 'Male') as Gender
from Users
) f1
) g1
) h1
You can use conditional aggregation:
SELECT AVG(CASE WHEN substring(ID, 10, 1) % 2 = 0 THEN AGE END) as AVG_AGE_FEMALE,
AVG(CASE WHEN substring(ID, 10, 1) % 2 = 1 THEN AGE END) as AVG_AGE_MALE
FROM Users;
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;
I am trying to turn some parts of my rows into columns. To my knowledge, I am only able to use a pivot with an aggregate function,but I would just be pivoting text. For each client I have up to 4 rows grouped by a DLSEQUENCE field. Instead of having the 4 rows, I would like everything to be on 1 row.
SELECT CASE
WHEN Sched_time BETWEEN TRUNC(SCHED_TIME) + INTERVAL '8' HOUR + INTERVAL '30' MINUTE
AND TRUNC(SCHED_TIME) + INTERVAL '14' HOUR + INTERVAL '45' MINUTE AND
TO_CHAR(SCHED_TIME, 'DY') IN ('MON', 'TUE', 'WED', 'THU', 'FRI')
THEN 'ABC'
ELSE 'DEF'
END AS Organization,
Client_Last_Name,
Client_First_Name,
Sched_Time,
Field_Name,
CASE
WHEN Recoded_Response = '1' THEN 'Yes'
WHEN Recoded_Response = '2' THEN 'No'
ELSE Recoded_Response
END AS Responses,
Dlsequence
FROM DAILY_LOG_CUSTOM_DATA
WHERE SERVICE_NAME = 'Medical'
AND FIELD_CATEGORY = 'Background Information'
AND Field_Name IN
(
'Restraint?',
'History',
'Findings',
'Treatment'
)
AND Sched_Time >= TO_DATE('2020-03-01 01:00:00', 'YYYY/MM/DD HH:MI:SS')
AND Sched_Time < TO_DATE('2020-03-31 12:59:00', 'YYYY/MM/DD HH:MI:SS')
Order BY Dlsequence
Here is my table:
I would like the response fields that go with ('Restraint?','History','Findings','Treatment') to have their own column for each DLSEQUENCE field.
The following should do what you had in mind:
SELECT DLSEQUENCE,
ORGANIZATION,
CLIENT_LAST_NAME,
CLIENT_FIRST_NAME,
SCHED_TIME,
LISTAGG("Restraint?", ',') WITHIN GROUP (ORDER BY DLSEQUENCE) AS "Restraint?",
LISTAGG("Findings", ',') WITHIN GROUP (ORDER BY DLSEQUENCE) AS "Findings",
LISTAGG("History", ',') WITHIN GROUP (ORDER BY DLSEQUENCE) AS "History",
LISTAGG("Treatment", ',') WITHIN GROUP (ORDER BY DLSEQUENCE) AS "Treatment"
FROM (SELECT DLSEQUENCE,
ORGANIZATION,
CLIENT_LAST_NAME,
CLIENT_FIRST_NAME,
SCHED_TIME,
CASE
WHEN FIELD_NAME = 'Restraint?' THEN RESPONSES
ELSE NULL
END AS "Restraint?",
CASE
WHEN FIELD_NAME = 'Findings' THEN RESPONSES
ELSE NULL
END AS "Findings",
CASE
WHEN FIELD_NAME = 'History' THEN RESPONSES
ELSE NULL
END AS "History",
CASE
WHEN FIELD_NAME = 'Treatment' THEN RESPONSES
ELSE NULL
END AS "Treatment"
FROM YOUR_TABLE)
GROUP BY DLSEQUENCE,
ORGANIZATION,
CLIENT_LAST_NAME,
CLIENT_FIRST_NAME,
SCHED_TIME
db<>fiddle here
INSERT OVERWRITE TABLE test_month
PARTITION (dt= LAST_DAY('${CURRENT_DATE}'))
SELECT '${CURRENT_DATE}', LAST_DAY('${CURRENT_DATE}');
Current date is first day of the month. I want to achieve something like above. It is not working. This will be HiveQL used in oozie.
Their may be couple ways, here is one way
select order_date, date_sub(concat(
(case
WHEN MONTH(order_date) = '12' THEN concat( (YEAR(order_date) +1) , '-01')
WHEN MONTH(order_date) >= '10' THEN concat( (YEAR(order_date)) , '-', (MONTH(order_date) +1))
WHEN MONTH(order_date) >= '1' THEN concat( (YEAR(order_date)) , '-0', (MONTH(order_date) +1))
ELSE 'XX' END) ,'-01' ) ,1)
Basically I want to be able to categorize the column selected into different range like so:
if the range > than 0 return the value of the column.
if the range < than 0 return 'Expired'.
Here is the code:
SELECT
PACKAGE_ID,
PACKAGE_NAME,
ORG_ID,
ORG_NAME,
VALID_FROM,
VALID_TO,
DAYS_LEFT,
TOTAL,
BALANCE,
THRESHOLD,
REPORTID
FROM
( SELECT
B.ORG_ID,
B.ORG_NAME,
A.PACKAGE_ID,
A.NAME AS PACKAGE_NAME,
A.BALANCE AS BALANCE,
A.VALID_FROM,
A.VALID_TO,
C.REPORT_CODE,
Floor(A.VALID_TO - (SELECT current_date FROM dual)) AS DAYS_LEFT,
X.TOTAL AS TOTAL,
CASE X.TOTAL WHEN 0 THEN 0 ELSE A.BALANCE / X.TOTAL* 100 END AS THRESHOLD,
'MIS0014' AS REPORTID
FROM
PREPAID_PACKAGE A
JOIN MST_ORGANISATION B ON B.ORG_ID = A.ORG_ID
JOIN PREPAID_REPORT C ON C.PACKAGE_ID = A.PACKAGE_ID
JOIN (
SELECT
SUM(Z.TOPUP_VALUE) as TOTAL,
Y.ORG_ID AS ORG_ID,
Y.PACKAGE_ID AS PACKAGE_ID
from PREPAID_VALUE Z
JOIN PREPAID_PACKAGE Y on Y.PACKAGE_ID =Z.PACKAGE_ID
GROUP BY Y.ORG_ID, Y.PACKAGE_ID
) X ON X.ORG_ID=A.ORG_ID AND X.PACKAGE_ID=A.PACKAGE_ID
ORDER BY
ORG_CODE,
PACKAGE_ID,
PACKAGE_NAME,
C.REPORT_CODE) S
WHERE
REPORTID='MIS0014'
AND ORG_ID = 1
AND VALID_FROM >= TO_DATE('01/02/2007', 'dd/mm/yyyy')
AND VALID_TO < TO_DATE('01/02/2013', 'dd/mm/yyyy')
AND THRESHOLD < 20
AND DAYS_LEFT < 13
GROUP BY
PACKAGE_ID,
PACKAGE_NAME,
ORG_ID,
ORG_NAME,
DAYS_LEFT,
VALID_FROM,
VALID_TO,
REPORTID,
TOTAL,
BALANCE,
THRESHOLD,
REPORTID
I'd like to change the following statement :
Floor(A.VALID_TO - (SELECT current_date FROM dual)) AS DAYS_LEFT,
to the following:
when Floor(A.VALID_TO - (SELECT current_date FROM dual)) > 0 then Floor(A.VALID_TO - (SELECT current_date FROM dual)) else 'Expired' end AS DAYS_LEFT,
or something similar,
Is there any keyword/function to allow me to do it?
Since you've already got an inline query why not just do it in your main SELECT.
e.g.
SELECT
PACKAGE_ID,
PACKAGE_NAME,
ORG_ID,
ORG_NAME,
VALID_FROM,
VALID_TO,
TOTAL,
BALANCE,
THRESHOLD,
REPORTID,
CASE
WHEN DAYS_LEFT > 0 THEN DAYS_LEFT -- Convert it to a Character of course
ELSE 'Expired'
END as DAYS_LEFT
You seem to have it almost correct already... but here's the correct syntax:
CASE WHEN FLOOR(A.VALID_TO - SYSDATE) > 0
THEN FLOOR(A.VALID_TO - SYSDATE)
ELSE 'Expired'
END AS DAYS_LEFT,
In case Oracle complains you about not matching datatypes, it may mean that you need to cast the FLOOR(A.VALID_TO - SYSDATE) to varchar2 by wrapping it inside a CAST function: CAST(FLOOR(A.VALID_TO - SYSDATE) AS VARCHAR2(5)) - but that may not be necessary at all.