Projection/Replication in SQL Query? - sql

My SQL is a bit rusty -- is there a SQL way to project an input table that looks something like this:
Name SlotValue Slots
---- --------- -----
ABC 3 1
ABC 4 2
ABC 6 5
Into a 'projected' result table that looks like this:
Name SlotSum Slot
---- ------- ----
ABC 13 1
ABC 10 2
ABC 6 3
ABC 6 4
ABC 6 5
In other words, the result set should contain a number of rows equal to MAX(Slots), enumerated (Slot) from 1 to MAX(Slots), and Sum for each of these 'slots' should reflect the sum of the SlotValues projected out to the 'Slots' position. for the pathological case:
Name SlotValue Slots
---- --------- -----
ABC 4 3
we should get:
Name SlotSum Slot
---- ------- ----
ABC 4 1
ABC 4 2
ABC 4 3
The summation logic is pretty straightforward -- project each SlotValue out to the number of Slots:
SlotValue SlotValue SlotValue Slot Sum
--------- --------- --------- ---- ---
3 4 6 1 13 (3+4+6)
0 4 6 2 10 (0+4+6)
0 0 6 3 6 (0+0+6)
0 0 6 4 6 (0+0+6)
0 0 6 5 6 (0+0+6)
UPDATE: In the end I used a variant of LOCALGHOST's approach in a stored proc. I was hoping there might be a way to do this without a loop.

I'm not sure you'll be able to do this in a view. You'd have to use a procedure.
You could make ProjectedTable a temporary/variable table in the procedure as well. I'd be really interested to see how you'd get this into a view because you need to dynamically generate a range of numbers.
declare #maxSlot int
set #maxSlot = select max(slots) from SlotTable
truncate ProjectedTable
while #i > 0
begin
insert into ProjectedTable (
SlotSum
,Slot
) values (
(select sum(slotValue) from SlotTable where slots >= #maxSlot)
,#maxSlot
)
set #maxSlot = #maxSlot - 1
end
select SlotSum, Slot from ProjectedTable

Here you go. This will do up to 100 slots in its current form. You can use your imagination to accommodate more.
DECLARE #SLOT TABLE
(
SlotName varchar(25) NOT NULL,
SlotValue int NOT NULL,
Slot int NOT NULL
)
INSERT INTO #SLOT (SlotName, SlotValue, Slot)
SELECT 'ABC', 3, 1
UNION
SELECT 'ABC', 4, 2
UNION
SELECT 'ABC', 6, 5
SELECT
CASE
WHEN SLOT.SlotName IS NOT NULL THEN SLOT.SlotName
ELSE
COALESCE(
(SELECT TOP 1 SL.SlotName FROM #SLOT AS SL WHERE SL.Slot < SLOT_PROJECT.Slot ORDER BY SL.Slot DESC),
(SELECT TOP 1 SL.SlotName FROM #SLOT AS SL WHERE SL.Slot > SLOT_PROJECT.Slot ORDER BY SL.Slot ASC)
)
END AS SlotName,
(
SELECT
SUM(SLOT10.SlotValue)
FROM
#SLOT AS SLOT10
WHERE
SLOT10.Slot >= SLOT_PROJECT.Slot
) AS SlotSum,
SLOT_PROJECT.Slot
FROM
(
SELECT
(TENS.Seq + ONES.Seq) AS Slot
FROM
(
SELECT 1 AS Seq
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
) AS ONES
CROSS JOIN
(
SELECT 0 AS Seq
UNION ALL
SELECT 10
UNION ALL
SELECT 20
UNION ALL
SELECT 30
UNION ALL
SELECT 40
UNION ALL
SELECT 50
UNION ALL
SELECT 60
UNION ALL
SELECT 70
UNION ALL
SELECT 80
UNION ALL
SELECT 90
) AS TENS
WHERE
(TENS.Seq + ONES.Seq) <= (SELECT MAX(Slot) FROM #SLOT)
) AS SLOT_PROJECT
LEFT JOIN #SLOT AS SLOT ON
SLOT.Slot = SLOT_PROJECT.Slot

Related

Count occurences of values in table, when I treat one value as occurence of all other values

I have a table with one column (just to simplify the problem) with values 0-23 or *.
I want to count occurrences of each value 0-23, but treat * as occurrence of all other values
for example:
column_name
-------------
3
4
5
6
7
*
4
4
3
*
I want to get something like that:
column_name | count
--------------------
1 | 2
2 | 2
3 | 4
4 | 5
5 | 3
6 | 3
7 | 3
.....
I tried experimenting with different count and "group by" methods, but always getting very strange results. Basically the main problem here is to how count rows when I need to have one value in all other groups.
You could use analytic function that counts values where * is replaced by actual value between 0 and 23:
SELECT DISTINCT n.RN "COL_1", Count(REPLACE(t.COL_1, '*', n.RN)) OVER(Partition By n.RN) "CNT"
FROM tbl t
INNER JOIN ( Select To_Char(LEVEL - 1) "RN" From Dual Connect By LEVEL <=24 ) n ON(n.RN = REPLACE(t.COL_1, '*', n.RN))
WHERE n.RN IN(SELECT COL_1 FROM tbl)
ORDER BY To_Number(n.RN)
which with your sample data:
WITH
tbl (COL_1) AS
(
Select '3' From Dual Union All
Select '4' From Dual Union All
Select '5' From Dual Union All
Select '6' From Dual Union All
Select '7' From Dual Union All
Select '*' From Dual Union All
Select '4' From Dual Union All
Select '4' From Dual Union All
Select '3' From Dual Union All
Select '*' From Dual Union All
Select '3' From Dual
)
... results as:
COL_1 CNT
---------------------------------------- ----------
3 5
4 5
5 3
6 3
7 3
... and if you exclude the Where clause you will get all the rows (0 - 23) with number of occurances counted by REPLACE of * with any of the numbers
COL_1 CNT
---------------------------------------- ----------
0 2
1 2
2 2
3 5
4 5
5 3
6 3
7 3
8 2
9 2
10 2
11 2
12 2
13 2
14 2
15 2
16 2
17 2
18 2
19 2
20 2
21 2
22 2
23 2
You can do it using successive WITH's :
First one to calculate number of occurrence of *.
And the second is to calculate number of occurrence of each number.
with cte as (
select count(1) as c
from mytable
where column_name = '*'
),
cte2 as (
select column_name, count(1) as c
from mytable, cte
group by column_name
)
select column_name, cte.c + cte2.c
from cte2, cte;
You can with nested statements too,
SELECT ID,(count_ + (
SELECT COUNT(ID) FROM sql_test_a
WHERE ID = '*')) as count_
FROM (
SELECT ID,COUNT(ID) as count_
FROM sql_test_a WHERE ID != '*' GROUP BY ID);

Oracle SQL - Create identifier for couple of values

I'm struggling with the following problem, I have the follwing data in a table:
Param ID
Param Val
Other Cols
1
15
XXX
1
15
XXX
1
16
XXX
1
16
XXX
2
21
XXX
2
21
XXX
2
22
XXX
2
22
XXX
I would like to select a new colum in order to create 4 sets of data to have all the possible combination between the values of parameter 1 and 2; so I would like to obtain something like this:
Set
Param ID
Param Val
Other Cols
1
1
15
XXX
2
1
15
XXX
3
1
16
XXX
4
1
16
XXX
1
2
21
XXX
3
2
21
XXX
2
2
22
XXX
4
2
22
XXX
So for example for the Set 1 I will have the Couple of values 15 and 21, for the set 2 the values 15 and 22 etc etc.
I tried using different analytic functions, but I was not able to have what I need.
Thanks in advance.
Despite a good hint on the MODEL clause, I guess I will go for a solution combining cross join and unpivot, maybe it is not the best, but it fit my needs.
WITH tbl AS
(
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual )
SELECT *
FROM (
SELECT ROWNUM AS SET_ID,
id1,
id4
FROM (
SELECT
CASE a.id
WHEN 1 THEN a.val
ELSE 0
END AS id1,
CASE a.id
WHEN 2 THEN a.val
ELSE 0
END AS id2,
CASE b.id
WHEN 1 THEN b.val
ELSE 0
END AS id3,
CASE b.id
WHEN 2 THEN b.val
ELSE 0
END AS id4
FROM tbl a,
tbl b)
WHERE id2 = 0
AND id3 = 0) UNPIVOT (VAL FOR ID IN (id1 AS '1',
id4 AS '2'))
that results in :
SET_ID
ID
VAL
1
1
15
1
2
21
2
1
15
2
2
22
3
1
16
3
2
21
4
1
16
4
2
22
tried with all the combination of source data, and it seems work :)
Sample data:
WITH
tbl AS
(
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual
)
UPDATED AFTER THE COMMENT
If you want the combinations then you will have to use the MODEL clause. To do that you should prepare the data (cte named grid) a bit so you could do the addressing to the particular data and manage all the combinations you want. It looks like here:
grid AS
( Select Distinct
Sum(1) OVER(Partition By ID Order By ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "IDS_TOTAL_ORDER",
Sum(1) OVER(Partition By VAL Order By VAL ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "VALS_TOTAL_ORDER",
ID "ID",
VAL "VAL"
From
tbl
Order By ID
)
Select SET_ID, ID, VAL
From ( Select 0 "SET_ID", IDS_TOTAL_ORDER, VALS_TOTAL_ORDER, ID "ID", VAL "VAL"
From grid
)
MODEL
Dimension By (ID, IDS_TOTAL_ORDER, VALS_TOTAL_ORDER)
Measures(SET_ID, VAL)
RULES
(
SET_ID[1, ANY, ANY] = CV(IDS_TOTAL_ORDER),
SET_ID[2, 1, 1] = CV(IDS_TOTAL_ORDER),
SET_ID[2, 2, 2] = CV(IDS_TOTAL_ORDER) + 1,
SET_ID[2, 3, 1] = CV(IDS_TOTAL_ORDER) - 1,
SET_ID[2, 4, 2] = CV(IDS_TOTAL_ORDER)
)
Order By ID, IDS_TOTAL_ORDER
This way you can get any combination. Here is your result:
SET_ID
ID
VAL
1
1
15
2
1
15
3
1
16
4
1
16
1
2
21
3
2
21
2
2
22
4
2
22
More about MODEL clause: https://www.oracle.com/webfolder/technetwork/tutorials/obe/db/10g/r2/prod/bidw/sqlmodel/sqlmodel_otn.htm
Sets now looks like here:
SET_ID
IDS_VALS
1
1/15, 2/21
2
1/15, 2/22
3
1/16, 2/21
4
1/16, 2/22
Regards...

Counting Rows under a Specific Header Row

I am trying to count the number of rows under specific "header rows" - for example, I have a table that looks like this:
Row # | Description | Repair_Code | Data Type
1 | FRONT LAMP | (null) | Header
2 | left head lamp | 1235 | Database
3 | right head lamp | 1236 | Database
4 | ROOF | (null) | Header
5 | headliner | 1567 | Database
6 | WHEELS | (null) | Header
7 | right wheel | 1145 | Database
Rows 1, 4 and 6 are header rows (categories) and the others are descriptors under each of those categories. The Data Type column denotes if the row is a header or not.
I want to be able to count the number of rows under the header rows to return something that looks like:
Header | Occurrences
FRONT LAMP | 2
ROOF | 1
WHEELS | 1
Thank you for the help!
Data model looks wrong. If that's some kind of a hierarchy, table should have yet another column which represents a "parent row#".
The way it is now, it's kind of questionable whether you can - or can not - do what you wanted. The only thing you can rely on is row#, which is sequential in your example. If that's not the case, then you have a problem.
So: if you use a lead analytic function for all header rows, then you could do something like this (sample data in rows #1 - 7; query that might help begins at line #8):
SQL> with test (rn, description, code) as
2 (select 1, 'front lamp' , null from dual union all
3 select 2, 'left head lamp' , 1235 from dual union all
4 select 3, 'right head lamp', 1236 from dual union all
5 select 4, 'roof' , null from dual union all
6 select 5, 'headliner' , 1567 from dual
7 ),
8 hdr as
9 -- header rows
10 (select rn,
11 description,
12 lead(rn) over (order by rn) next_rn
13 from test
14 where code is null
15 )
16 select h.description,
17 count(*)
18 from hdr h join test t on t.rn > h.rn
19 and (t.rn < h.next_rn or h.next_rn is null)
20 group by h.description;
DESCRIPTION COUNT(*)
--------------- ----------
front lamp 2
roof 1
SQL>
If data model was different (note parent_rn column), then you wouldn't depend on sequential row# values, but
SQL> with test (rn, description, code, parent_rn) as
2 (select 0, 'items' , null, null from dual union all
3 select 1, 'front lamp' , null, 0 from dual union all
4 select 2, 'left head lamp' , 1235, 1 from dual union all
5 select 3, 'right head lamp', 1236, 1 from dual union all
6 select 4, 'roof' , null, 0 from dual union all
7 select 5, 'headliner' , 1567, 4 from dual
8 ),
9 calc as
10 (select parent_rn,
11 sum(case when code is null then 0 else 1 end) cnt
12 from test
13 connect by prior rn = parent_rn
14 start with parent_rn is null
15 group by parent_rn
16 )
17 select t.description,
18 c.cnt
19 from test t join calc c on c.parent_rn = t.rn
20 where nvl(c.parent_rn, 0) <> 0;
DESCRIPTION CNT
--------------- ----------
front lamp 2
roof 1
SQL>
I would approach this using window functions. Assign a group to each header by doing a cumulative count of the NULL values of repair_code. Then aggregate:
select max(case when repair_code is null then description end) as description,
count(repair_code) as cnt
from (select t.*,
sum(case when repair_code is null then 1 else 0 end) over (order by row#) as grp
from t
) t
group by grp
order by min(row#);
Here is a db<>fiddle.

how to select set of records is ID is present in one of them

Here is the table where ORGID/USERID makes unique combination:
ORGID USERID
1 1
1 2
1 3
1 4
2 1
2 5
2 6
2 7
3 9
3 10
3 11
I need to select all records (organizations and users) wherever USERID 1 is present. So USERID=1 is present in ORGID 1 and 2 so then select all users for these organizations including user 1 itself, i.e.
ORGID USERID
1 1
1 2
1 3
1 4
2 1
2 5
2 6
2 7
Is it possible to do it with one SQL query rather than SELECT *.. WHERE USERID IN (SELECT...
You could use exists:
select *
from mytable t
where exists (select 1 from mytable t1 where t1.orgid = t.orgid and t1.userid = 1)
Another option is window functions. In Postgres:
select *
from (
select t.*,
bool_or(userid = 1) over(partition by orgid) has_user_1
from mytable t
) t
where has_user_1
Or a more generic approach, that uses portable expressions:
select *
from (
select t.*,
max(case when userid = 1 then 1 else 0 end) over(partition by orgid) has_user_1
from mytable t
) t
where has_user_1 = 1
Yes, you can do it with a single select statement - no in or exists conditions, no analytic or aggregate functions in a subquery, etc. Why you want to do it that way is not clear; in any case, it is possible that the solution below is also more efficient than the alternatives. You will have to test on your real-life data to see if that is true.
The solution below has two potential disadvantages: it only works in Oracle (it uses a proprietary extension of SQL, the match_recognize clause); and it only works in Oracle 12.1 or higher.
with
my_table(orgid, userid) as (
select 1, 1 from dual union all
select 1, 2 from dual union all
select 1, 3 from dual union all
select 1, 4 from dual union all
select 2, 1 from dual union all
select 2, 5 from dual union all
select 2, 6 from dual union all
select 2, 7 from dual union all
select 3, 9 from dual union all
select 3, 10 from dual union all
select 3, 11 from dual
)
-- End of SIMULATED data (for testing), not part of the solution.
-- In real life you don't need the WITH clause; reference your actual table.
select *
from my_table
match_recognize(
partition by orgid
all rows per match
pattern (x* a x*)
define a as userid = 1
);
Output:
ORGID USERID
---------- ----------
1 1
1 2
1 3
1 4
2 1
2 5
2 7
2 6
You can use exists:
select ou.*
from orguser ou
where exists (select 1
from orguser ou ou2
where ou2.orgid = ou.orgid and ou2.userid = 1
);
Apart from Exists and windows function, you can use IN as follows:
select *
from your_table t
where t.orgid in (select t1.orgid from your_table t1 where t1.userid = 1)

where condition scenario in oracle

i have requirement in where condition
if my id is in 1 then it should check id 4,5,6,7 or it should check value which is in id
this id i will pass as parameter to query
select * from table_a where id
Help me in this
You can use the below
select * from individual ip
where (
( :p_prs_nat = 219 and ip.prs_nationality_id in (231,259,343) )
or (:p_prs_nat <> 219 and :p_prs_nat=ip.prs_nationality_id
))
where ip.prs_nationality_id =case when :p_prs_nat in( 219) then it shud check (231,259,343) else :p_prs_nat end how to achieve this functionality
You cannot directly use IN while returning the result in the THEN clause of CASE expression. However, you could first check the condition itself using AND operator and return TRUE whenever it matches.
For example,
SQL> WITH DATA AS
2 (
3 SELECT 1 ID, 'X' STR FROM DUAL UNION ALL
4 SELECT 2 ID, 'A' STR FROM DUAL UNION ALL
5 SELECT 3 ID ,'P' STR FROM DUAL UNION ALL
6 SELECT 4 ID ,'Q' STR FROM DUAL
7 )
8 SELECT *
9 FROM DATA
10 WHERE (
11 CASE
12 WHEN ID = 1
13 AND str IN ('A','Y','Z')
14 THEN 1
15 WHEN ID <> 1
16 THEN 1
17 END ) =1
18 /
ID S
---------- -
2 A
3 P
4 Q
SQL>
So, you did not get the row with ID = 1,since it did not match the condition AND str IN ('A','Y','Z').
If it would match, it will return those matching rows too:
SQL> WITH DATA AS
2 (
3 SELECT 1 ID, 'X' STR FROM DUAL UNION ALL
4 SELECT 2 ID, 'A' STR FROM DUAL UNION ALL
5 SELECT 3 ID ,'P' STR FROM DUAL UNION ALL
6 SELECT 4 ID ,'Q' STR FROM DUAL
7 )
8 SELECT *
9 FROM DATA
10 WHERE (
11 CASE
12 WHEN ID = 1
13 AND str IN ('X','Y','Z')
14 THEN 1
15 WHEN ID <> 1
16 THEN 1
17 END ) =1
18 /
ID S
---------- -
1 X
2 A
3 P
4 Q
SQL>