Oracle Create a view replacing ids with names in column (not 1nf) - sql

We have for example this table:
pl_num camp_type products
1 T 1,2,3
2 B 1,3,4
Yeah, I know it's not in 1NF but we need to work with it
because of application loads data in such way.
And we have table DICT_PRODUCT, for example (in reality, there are more than 500 product):
id product_name
1 a
2 b
3 c
4 d
So, what we need is to create view where product_id was replaced by its name in dictionary
---V_TAB1 ---
pl_num camp_type products
1 T 1,b,c
2 B a,c,d

Try this. It will work if products column in TAB1 contain numbers and not any other characters.
WITH prod
AS (SELECT pl_num, camp_type, TO_NUMBER (TRIM (COLUMN_VALUE)) product
FROM Tab1 t, XMLTABLE (t.products))
SELECT prod.pl_num,
prod.camp_type,
LISTAGG (d.product_name, ',') WITHIN GROUP (ORDER BY id) products
FROM prod JOIN dict_product d ON prod.product = d.id
GROUP BY prod.pl_num, prod.camp_type;
DEMO

Try this one:
select distinct *
from (
select t.u_name, u_id, regexp_substr(t.prod,'[^,]+', 1, level) id
from (select prod,u_id, u_name from cmdm.t_prod) t
connect by regexp_substr(prod,'[^,]+',1,level) is not null) ut
inner join cmdm.t_dct dt
on ut.id=dt.id

Related

How to select people who has multiple values

I want to select people who has 2 values (activate & recurring) in the table for example,
table :: tbl_transactions
id
name
action
1
John
activate
2
John
recurring
3
Salah
activate
4
Bill
activate
5
Bill
recurring
6
Bill
recurring
Expected result,
id
name
action
1
John
activate
2
John
recurring
4
Bill
activate
5
Bill
recurring
6
Bill
recurring
Please help. I have been spent for an hour to fix this.
Really thanks.
You can aggregate the action values for each user name and check if the array is 2 long (since you only need 2 actions) and contains ['activate', 'recurring'] (since you only need these values)
SELECT t.id, t.name FROM tbl_transactions t
JOIN LATERAL (
SELECT
name,
ARRAY_AGG(action) AS actions
FROM tbl_transactions
GROUP BY name
) user_actions ON t.name = user_actions.name
AND ARRAY_LENGTH(actions, 1) = 2
AND ARRAY['activate', 'recurring']::VARCHAR[] #> actions
Demo
here is a query for selecting only names appearing twice:
select t.*
from t
join (select name, count(*)
from t
having count(*) = 2
) c on t.name = c.name ;
The query will be as below. Check Sql Fiddle. You can use count distinct action to count distinct action value check it is greater than 1.
select a.* from tbl_transactions a join
(select name, count(*) from tbl_transactions group by name having count(distinct action) > ) b
on a.name = b.name
I would use the bool_or() window function for this:
with complete_check as (
select id, name, action,
bool_or(action = 'recurring') over w as has_recurring,
bool_or(action = 'activate') over w as has_activate
from tbl_transactions
window w as (partition by name)
)
select id, name, action
from complete_check
where has_recurring and has_activate;
db<>fiddle here

How add more rows when find string in column Oracle

Would it be possible to add more rows base on Keyword string in SQL ?
table A
PID PromotionName
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 OUT_EC_D5_V50K_Lunchbox_PROCO
3 OUT_EC_D5_V50K_PROCO
table B
promotion_code itm_name quantity
Lunchbox Item name 1 1
FamilyCare Item name 2 1
FamilyCare Item name 3 1
BUY1FREE6 Item name 4 1
HiSummer Item name 5 1
FamilyCare Item name 6 1
Example:
SELECT * FROM A where pid = '1';
Output of the SQL should be -
PID PromotionName Itm_name quantity
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 FamilyCare Item name 2 1
3 FamilyCare Item name 3 1
4 FamilyCare Item name 6 1
How to find string with keyword 'FamilyCare' in PromotionName of table A base on promotion_code of table B? If it exist it will add more rows in output
Any help with the SQL?
Here is how you can achieve this:
SELECT PID,PromotionName, '' as Itm_name, NULL as quantity
FROM A
WHERE pid = '1'
UNION
SELECT PID, PROMOTION_NAME, Itm_name, quantity
FROM
(SELECT * FROM A inner join B on a.promotionName LIKE '%'||b.promotion_name||'%')
WHERE pid='1'
You have to update your pid in both the places (before and after UNION).
Notice that tables were joined using LIKE operator with % before and after the word. Hence this joins if a part of a string is present in another column.
db<>fiddle link here
An option would be starting to construct a subquery factoring along with joining tables through a.promotionName LIKE '%'||b.promotion_code||'%' condition while filtering by b.promotion_code = 'FamilyCare', then add another query to combine the result sets by UNION ALL, and then enumerate with an id column by ROW_NUMBER() analytic function such as
WITH ab AS
(
SELECT a.*, b.*
FROM a
JOIN b
ON a.promotionName LIKE '%'||b.promotion_code||'%'
WHERE b.promotion_code = 'FamilyCare'
), ab2 AS
(
SELECT promotion_code, itm_name, quantity
FROM ab
UNION ALL
SELECT DISTINCT promotionName, NULL, NULL
FROM ab
)
SELECT ROW_NUMBER() OVER (ORDER BY itm_name NULLS FIRST) AS pid,
a.*
FROM ab2 a
if there's mismatch for the topmost query, then no row will be returned. eg. that query will check for the existence for the literal you provide
Demo

Select column with maximum value in another column but with aggregate SUM calculation

For each name, I need to output the category with the MAX net revenue and I am not sure how to do this. I have tried a bunch of different approaches, but it basically looks like this:
SELECT Name, Category, MAX(CatNetRev)
FROM (
SELECT Name, Category, SUM(Price*(Shipped-Returned)) AS "CatNetRev"
FROM a WITH (NOLOCK)
INNER JOIN b WITH (NOLOCK) ON b.ID = a.ID
...
-- (bunch of additional joins here, not relevant to question)
WHERE ... -- (not relevant to question)
GROUP BY Name, Category
) a GROUP BY Name;
This currently doesn't work because "Category" is not contained in an aggregate function or Group By (and this is obvious) but other approaches I have tried have failed for different reasons.
Each Name can have a bunch of different Categories, and Names can have the same Categories but the overlap is irrelevant to the question. I need to output just each unique Name that I have (we can assume they are already all unique) along with the "Top Selling Category" based on that Net Revenue calculation.
So for example if I have:
Name:
Category:
"CatNetRev":
A
1
100
A
2
300
A
3
50
B
1
300
B
2
500
C
1
40
C
2
20
C
3
10
I would want to output:
Name:
Category:
A
2
B
2
C
1
What's the best way to go about doing this?
Having to guess at your data schema a bit, as you didn't alias any of your columns, or define what table a vs b really was (as Gordon alluded). I'd use CROSS APPLY to get the max value, then bind the revenues in a WHERE clause, like so.
DECLARE #Revenue TABLE
(
Name VARCHAR(50)
,Category VARCHAR(50)
,NetRevenue DECIMAL(16, 9)
);
INSERT INTO #Revenue
(
Name
,Category
,NetRevenue
)
SELECT Name
,Category
,SUM(a.Price * (b.Shipped - b.Returned)) AS CatNetRev
FROM Item AS a
INNER JOIN ShipmentDetails AS b ON b.ID = a.ID
WHERE 1 = 1
GROUP BY
Name
,Category;
SELECT r.Name
,r.Category
FROM #Revenue AS r
CROSS APPLY (
SELECT MAX(r2.NetRevenue) AS MaxRevenue
FROM #Revenue AS r2
WHERE r.Name = r2.Name
) AS mr
WHERE r.NetRevenue = mr.MaxRevenue;
you can use window functions:
select * from
(
select * , rank() over (partition by Name order by CatNetRev desc) rn
from table
) t
where t.rn = 1

Merge rows by one column

I have a column name and value and I need to write a select to merge all rows with the same name into one row (something like distinct) except when I use distinct then I can't merge/sum value column.
Example:
name value
A 10
B 5
C 20
A 5
C 1
B 5
And the result would be:
A 15
B 10
C 21
This is my select so far, but it is not "merged", it does exactly what my example shows.
select
projects.name,
sum(current_date - (projects_programmers.joined_at))
from projects, projects_programmers
where projects.id = projects_programmers.project_id
group by projects.name, projects_programmers.joined_at
select name, sum(value) value from yourTableName group by name
select distinct
"name", sum("value") over (partition by "name")
from table_name
you can use window function for this

SQL - Count Results of 2 Columns

I have the following table which contains ID's and UserId's.
ID UserID
1111 11
1111 300
1111 51
1122 11
1122 22
1122 3333
1122 45
I'm trying to count the distinct number of 'IDs' so that I get a total, but I also need to get a total of ID's that have also seen the that particular ID as well... To get the ID's, I've had to perform a subquery within another table to get ID's, I then pass this into the main query... Now I just want the results to be displayed as follows.
So I get a Total No for ID and a Total Number for Users ID - Also would like to add another column to get average as well for each ID
TotalID Total_UserID Average
2 7 3.5
If Possible I would also like to get an average as well, but not sure how to calculate that. So I would need to count all the 'UserID's for an ID add them altogether and then find the AVG. (Any Advice on that caluclation would be appreciated.)
Current Query.
SELECT DISTINCT(a.ID)
,COUNT(b.UserID)
FROM a
INNER JOIN b ON someID = someID
WHERE a.ID IN ( SELECT ID FROM c WHERE GROUPID = 9999)
GROUP BY a.ID
Which then Lists all the IDs and COUNT's all the USERID.. I would like a total of both columns. I've tried warpping the query in a
SELECT COUNT(*) FROM (
but this only counts the ID's which is great, but how do I count the USERID column as well
You seem to want this:
SELECT COUNT(DISTINCT a.ID), COUNT(b.UserID),
COUNT(b.UserID) * 1.0 / COUNT(DISTINCT a.ID)
FROM a INNER JOIN
b
ON someID = someID
WHERE a.ID IN ( SELECT ID FROM c WHERE GROUPID = 9999);
Note: DISTINCT is not a function. It applies to the whole row, so it is misleading to put an expression in parentheses after it.
Also, the GROUP BY is unnecessary.
The 1.0 is because SQL Server does integer arithmetic and this is a simple way to convert a number to a decimal form.
You can use
SELECT COUNT(DISTINCT a.ID) ...
to count all distinct values
Read details here
I believe you want this:
select TotalID,
Total_UserID,
sum(Total_UserID+TotalID) as Total,
Total_UserID/TotalID as Average
from (
SELECT (DISTINCT a.ID) as TotalID
,COUNT(b.UserID) as Total_UserID
FROM a
INNER JOIN b ON someID = someID
WHERE a.ID IN ( SELECT ID FROM c WHERE GROUPID = 9999)
) x