SQL Group By On Output From User Defined Function - sql
Is it possible, in Oracle, to group data on the output of a user defined function? I get errors when I try to, and it best illustrated by the below example:
I am trying to interrogate results in table structure similar to below:
id | data
1000 | {abc=123, def=234, ghi=111, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=123, def=234, ghi=222, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=123, def=434, ghi=333, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=123, def=434, ghi=444, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=123, def=634, ghi=555, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=923, def=634, ghi=666, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=923, def=434, ghi=777, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=923, def=434, ghi=888, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=923, def=234, ghi=999, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
1000 | {abc=923, def=234, ghi=000, jkl=456, mno=567, pqr=678, stu=789, vwx=890, yza=901}
There are other columns, just not shown. The id column can have different values, but in this example, does not. In the data column, only the fields abc, def, and ghi differ, all the others are the same. Again this is only illustrative for this data example.
I have written a function to extract the value assigned to fields in the data column, and it is used in the following query:
select id
,extract_data(data,abc) as abc
,extract_data(data,def) as def
from table
giving results:
id | abc | def
1000 | 123 | 234
1000 | 123 | 234
1000 | 123 | 434
1000 | 123 | 434
1000 | 123 | 634
1000 | 923 | 634
1000 | 923 | 434
1000 | 923 | 434
1000 | 923 | 234
1000 | 923 | 234
For reporting purposes, I would like to be able to display the amount of each type of record. There are 6 types in the above example, and ideally the output would be:
id | abc | def | count
1000 | 123 | 234 | 2
1000 | 123 | 434 | 2
1000 | 123 | 634 | 1
1000 | 923 | 634 | 1
1000 | 923 | 434 | 2
1000 | 923 | 234 | 2
I expected to achieve this by writing SQL like so (and I'm convinced I have done so in the past):
select id
,extract_data(data,abc) as abc
,extract_data(data,def) as def
,count(1)
from table
group by id
,abc
,def
This however, will not work. Oracle is giving me an error of:
ORA-00904: "ABC": invalid identifier
00904. 00000 - "%s: invalid identifier"
From my initial research on "the google", I have seen that I should perhaps be grouping on the column I am passing into my user defined function. This would be due to SQL requiring all columns not part of an aggregate function needing to be part of the group by clause.
This will work for some records, however in my data example, the field ghi in the data column is different for every record , thus making the data column unique, and ruining the group by clause, as a count of 1 is given for each record.
I've used sybase and db2 in the past, and (setting myself up for a fall here...) I'm pretty sure in both that I was able to group by on the output of a user defined function.
I thought that there might be an issue with the naming of the columns and how they can be referenced by the group by? Referencing by column number hasn't worked.
I've tried various combinations of what I have, and can't get it to work, so I'd appreciate any insight you guys out there could give.
If you need any more information I'll edit as required or clarify in the comments.
Thanks,
GC.
You should be able to group by the functions themselves, not by the aliases
select id
,extract_data(data,abc) as abc
,extract_data(data,def) as def
,count(*)
from table
group by id
,extract_data(data,abc)
,extract_data(data,def)
Note that this does not generally involve executing the function multiple times. You can see that yourself with a simple function that increments a counter in a package every time it is called
SQL> ed
Wrote file afiedt.buf
1 create or replace package pkg_counter
2 as
3 g_cnt integer := 0;
4* end;
SQL> /
Package created.
SQL> create or replace function f1( p_arg in number )
2 return number
3 is
4 begin
5 pkg_counter.g_cnt := pkg_counter.g_cnt + 1;
6 return mod( p_arg, 2 );
7 end;
8 /
Function created.
There are 16 rows in the EMP table
SQL> select count(*) from emp;
COUNT(*)
----------
16
so when we execute a query that involves grouping by the function call, we hope to see the function executed only 16 times. And that is, in fact, what we see.
SQL> select deptno,
2 f1( empno ),
3 count(*)
4 from emp
5 group by deptno,
6 f1( empno );
DEPTNO F1(EMPNO) COUNT(*)
---------- ---------- ----------
1 1
30 0 4
20 1 1
10 0 2
30 1 2
20 0 4
10 1 1
0 1
8 rows selected.
SQL> begin
2 dbms_output.put_line( pkg_counter.g_cnt );
3 end;
4 /
16
PL/SQL procedure successfully completed.
Try this:
select id, abc, def, count(1)
from
(
select
id,
extract_data(data,abc) as abc,
extract_data(data,def) as def
from table
)
group by id, abc, def
Have you tried:
SELECT
id,
extract_data(data, abc) as abc,
extract_data(data, def) as def,
COUNT(1)
FROM
table
GROUP BY
id,
extract_data(data, abc)
extract_data(data, def)
Related
How to find the mismatch rows in sql - postgresl?
I have 2 tables. I want to find the mismatch row Input Table name: sale_order header_id name date_col total_amt 45 apple 2021-01-26 19:55:33.350589 100 32 grape 2021-01-27 19:55:33.350589 200 22 plums 2021-01-27 17:55:33.350589 250 30 lemon 2021-01-28 19:55:33.350589 400 Table name: bill_order id product_id date_col total_amt 1 45 2021-01-26 19:55:33.350589 200 2 32 2021-01-27 19:55:33.350589 200 3 22 2021-01-27 17:55:33.350589 500 4 30 2021-01-28 19:55:33.350589 400 Output: Mismatch (Wrong values in total_amt) select sale_order.header_id ,sale_order.total_amt,bill_order.total_amt FROM sale_order INNER JOIN bill_order ON sale_order.total_amt != bill_order.total_amt` and header_id = product_id Expected Output: header_id total_amt total_amt 45 100 200 22 250 500 I want to find the mismatch total_amt. And want to display I want to find the mismatch total_amt. And want to display I want to find the mismatch total_amt. And want to display
You just need to use aliases for recognize columns with same title from different tables: select sale_order.header_id, sale_order.total_amt sale_amt, bill_order.total_amt bill_amt from sale_order join bill_order ON sale_order.total_amt != bill_order.total_amt and header_id = product_id ; Result: +===========+==========+==========+ | header_id | sale_amt | bill_amt | +===========+==========+==========+ | 45 | 100 | 200 | +-----------+----------+----------+ | 22 | 250 | 500 | +-----------+----------+----------+ share SQL query
select a.header_id, a.total_amt, b.total_amt from sale_order a inner join bill_order b on a.header_id = b.product_id where a.total_amt != b.total_amt; Not tested but should work.
select from table with between
please, help advice. I have a table. id|score_max|score_min| segment --|---------|---------|-------- 1 |264 | |girl 2 |263 | 250 |girl+ 3 |249 | 240 |girl 4 | | 239 |girl It is not necessary to obtain a value depending on the value of the score. But it can be null. For example, 260 is value from other table select segment from mytable where score_max<260 and score_min>260 Output: 2 |263 | 250 |girl+ but if value =200, sql is not correct How to make a request correctly?
For this sample data that makes more sense: id|score_max|score_min| segment --|---------|---------|-------- 1 | | 264 |girl 2 |263 | 250 |girl+ 3 |249 | 240 |girl 4 |239 | |girl you can get the result that you want like this: select * from tablename where (? >= score_min or score_min is null) and (? <= score_max or score_max is null) Replace ? with the value that you search for. See the demo.
SQL: how to separate combined row into individual rows
I have a database table like this: id | check_number | amount 1 | 1001]1002]1003 | 200]300]100 2 | 2001]2002 | 500]1000 3 | 3002]3004]3005]3007 | 100]300]600]200 I want to separate the records into something like this: id | check_number | amount 1 | 1001 | 200 2 | 1002 | 300 3 | 1003 | 100 . | . | . . | . | . . | . | . How do I do this just using SQL in Oracle and SQL Server? Thanks, Milo
In Oracle Only, using the CONNECT BY LEVEL method (see here), with several caveats: select rownum, id, substr(']'||check_number||']' ,instr(']'||check_number||']',']',1,level)+1 ,instr(']'||check_number||']',']',1,level+1) - instr(']'||check_number||']',']',1,level) - 1) C1VALUE, substr(']'||amount||']' ,instr(']'||amount||']',']',1,level)+1 ,instr(']'||amount||']',']',1,level+1) - instr(']'||amount||']',']',1,level) - 1) C2VALUE from table connect by id = prior id and prior dbms_random.value is not null and level <= length(check_number) - length(replace(check_number,']')) + 1 ROWNUM ID C1VALUE C2VALUE 1 1 1001 200 2 1 1002 300 3 1 1003 100 4 2 2001 500 5 2 2002 1000 6 3 3002 100 7 3 3004 300 8 3 3005 600 9 3 3007 200 Essentially we blow out the query using the hierarchical functions of oracle and then only get the substrings for the data in each "column" of data inside the check_number and amount columns. Major Caveat: The data to be transformed must have the same number of "data elements" in both columns, since we use the first column to "count" the number of items to be transformed. I have tested this on 11gR2. YMMV depending on DMBS version as well. Note the need to use the "PRIOR" operator, which prevents oracle from going into an infinite connect by loop.
Combine rows adding specific columns
I have a table similar to the following: employee_id | totalWorkHours | projectID 1 20 123 1 20 321 2 15 222 2 25 333 3 10 434 3 12 343 Is it possible to combine rows based on employee_id, but add totalWorkHours into an actual total for an employee and present in a result set without modifying the table? So the results would be something like: employee_id | actualTotalWorkHours 1 40 2 40 3 22 Or is this something better done with the raw result set? Any help is much appreciated.
Select employee_id, Sum(totalWorkHours) As actualWorkHours From YourTableName Group By employee_id Order By employee_id
a Rollup query with some logical netting using Oracle SQL
I have a table "AuctionResults" like below Auction Action Shares ProfitperShare ------------------------------------------- Round1 BUY 6 200 Round2 BUY 5 100 Round2 SELL -2 50 Round3 SELL -5 80 Now I need to aggregate results by every auction with BUYS after netting out SELLS in subsequent rounds on a "First Come First Net basis" so in Round1 I bought 6 Shares and then sold 2 in Round2 and rest "4" in Round3 with a total NET profit of 6 * 200-2 * 50-4 * 80 = 780 and in Round2 I bought 5 shares and sold "1" in Round3(because earlier "4" belonged to Round1) with a NET Profit of 5 * 100-1 * 80 = 420 ...so the Resulting Output should look like: Auction NetProfit ------------------ Round1 780 Round2 420 Can we do this using just Oracle SQL(10g) and not PL-SQL Thanks in advance
I know this is an old question and won't be of use to the original poster, but I wanted to take a stab at this because it was an interesting question. I didn't test it out enough, so I would expect this still needs to be corrected and tuned. But I believe the approach is legitimate. I would not recommend using a query like this in a product because it would be difficult to maintain or understand (and I don't believe this is really scalable). You would be much better off creating some alternate data structures. Having said that, this is what I ran in Postgresql 9.1: WITH x AS ( SELECT round, action ,ABS(shares) AS shares ,profitpershare ,COALESCE( SUM(shares) OVER(ORDER BY round, action ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) , 0) AS previous_net_shares ,COALESCE( ABS( SUM(CASE WHEN action = 'SELL' THEN shares ELSE 0 END) OVER(ORDER BY round, action ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) ), 0 ) AS previous_sells FROM AuctionResults ORDER BY 1,2 ) SELECT round, shares * profitpershare - deduction AS net FROM ( SELECT buy.round, buy.shares, buy.profitpershare ,SUM( LEAST( LEAST( sell.shares, GREATEST(buy.shares - (sell.previous_sells - buy.previous_sells), 0) ,GREATEST(sell.shares + (sell.previous_sells - buy.previous_sells) - buy.previous_net_shares, 0) ) ) * sell.profitpershare ) AS deduction FROM x buy ,x sell WHERE sell.round > buy.round AND buy.action = 'BUY' AND sell.action = 'SELL' GROUP BY buy.round, buy.shares, buy.profitpershare ) AS y And the result: round | net -------+----- 1 | 780 2 | 420 (2 rows) To break it down into pieces, I started with this data set: CREATE TABLE AuctionResults( round int, action varchar(4), shares int, profitpershare int); INSERT INTO AuctionResults VALUES(1, 'BUY', 6, 200); INSERT INTO AuctionResults VALUES(2, 'BUY', 5, 100); INSERT INTO AuctionResults VALUES(2, 'SELL',-2, 50); INSERT INTO AuctionResults VALUES(3, 'SELL',-5, 80); INSERT INTO AuctionResults VALUES(4, 'SELL', -4, 150); select * from auctionresults; round | action | shares | profitpershare -------+--------+--------+---------------- 1 | BUY | 6 | 200 2 | BUY | 5 | 100 2 | SELL | -2 | 50 3 | SELL | -5 | 80 4 | SELL | -4 | 150 (5 rows) The query in the "WITH" clause adds some running totals to the table. "previous_net_shares" indicates how many shares are available to sell before the current record. This also tells me how many 'SELL' shares I need to skip before I can start allocating it to this 'BUY'. "previous_sells" is a running count of the number of "SELL" shares encountered, so the difference between two "previous_sells" indicates the number of 'SELL' shares used in that time. round | action | shares | profitpershare | previous_net_shares | previous_sells -------+--------+--------+----------------+---------------------+---------------- 1 | BUY | 6 | 200 | 0 | 0 2 | BUY | 5 | 100 | 6 | 0 2 | SELL | 2 | 50 | 11 | 0 3 | SELL | 5 | 80 | 9 | 2 4 | SELL | 4 | 150 | 4 | 7 (5 rows) With this table, we can do a self-join where each "BUY" record is associated with each future "SELL" record. The result would look like this: SELECT buy.round, buy.shares, buy.profitpershare ,sell.round AS sellRound, sell.shares AS sellShares, sell.profitpershare AS sellProfitpershare FROM x buy ,x sell WHERE sell.round > buy.round AND buy.action = 'BUY' AND sell.action = 'SELL' round | shares | profitpershare | sellround | sellshares | sellprofitpershare -------+--------+----------------+-----------+------------+-------------------- 1 | 6 | 200 | 2 | 2 | 50 1 | 6 | 200 | 3 | 5 | 80 1 | 6 | 200 | 4 | 4 | 150 2 | 5 | 100 | 3 | 5 | 80 2 | 5 | 100 | 4 | 4 | 150 (5 rows) And then comes the crazy part that tries to calculate the number of shares available to sell in the order vs the number over share not yet sold yet for a buy. Here are some notes to help follow that. The "greatest"calls with "0" are just saying we can't allocate any shares if we are in the negative. -- allocated sells sell.previous_sells - buy.previous_sells -- shares yet to sell for this buy, if < 0 then 0 GREATEST(buy.shares - (sell.previous_sells - buy.previous_sells), 0) -- number of sell shares that need to be skipped buy.previous_net_shares Thanks to David for his assistance