Difference within the same column - difference

So I know this probably looks a little ridiculous, but I am pretty much making a query that makes a query. The issue is I need the part that says
((transactions.amt_paid WHERE item_no = #item_no - SUM(transactions.amt_paid WHERE item_no IN (SELECT item_no = #items))
to show me the total, but how do I do a Sum here? Hope that makes sense.
DECLARE #aracct VARCHAR(12)
SET #aracct = '49947368'
DECLARE #item_no VARCHAR(12)
SET #item_no = '2081'
SELECT tran_id, *
FROM transactions
WHERE account = #aracct AND item_no = #item_no
DECLARE #itemtable TABLE (items VARCHAR(6))
INSERT INTO #itemtable VALUES ('2735')
INSERT INTO #itemtable VALUES ('2737')
SELECT 'UPDATE transactions' + CHAR(13) + CHAR(9) + 'SET amt_paid = ' + '''' + (transactions.amt_paid WHERE item_no = #item_no - SUM(transactions.amt_paid WHERE item_no IN (SELECT item_no = #items)) +
'''' + ' WHERE aracct = ' + '''' + (SELECT #aracct) + '''' + ' AND item_no = ' + '''' + (SELECT #item_no) + ''''

(in the following, looking at your syntax, I'm assuming you're on Sql Server)
What you are asking is called dynamic sql, and you can definitely do it.
"Making a query that makes a query" is quite common in advanced use cases.
Your requirement, however, is not an advanced use case.
I am assuming you want to SUM the rows where item_no is equal to #item_no and you want to subtract where item_no is IN #items.
Correct me if I'm wrong.
So, let's tackle this first task, before.
Select SUM(CASE
When (item_no = #item_no) THEN amt_paid
When (item_no IN (SELECT items FROM #itemtable)) THEN -amt_paid END) TheAmount
FROM transactions
WHERE (item_no = #item_no) OR (item_no IN (SELECT items FROM #itemtable))
YAY! Now, if this works as expected, we have a query that returns our value.
And we can do:
UPDATE transactions
SET amt_paid = (... the previous query ...)
WHERE aracct = #aracct AND item_no = #item_no
Done, without dynamic query.
If you need to print the query, the previous SELECT can be used to give a value to another variable and then generate the query.
I can elaborate further, if you really need the query.
Edit: well, it's not easy to help without some extra information, but probably Sql is complaining about the Select inside the max(...)
Try to rewrite the Select in this way:
Select SUM(CASE
When (T.item_no = #item_no) THEN T.amt_paid
When (IT.items IS NOT NULL) THEN -amt_paid END) TheAmount
FROM transactions T
LEFT JOIN #itemtable IT ON IT.items = T.item_no
WHERE (item_no = #item_no) OR (item_no IN (SELECT items FROM #itemtable))
If you still have problems, please detail your error.

Related

execute immediate update returns NULL whilst the non string query works fine

I have an update query which works fine. once I put this in my PLSQL block it returns NULL and there is no error. Prior to this part of the block I insert a default value into the target table, therefore I know the update is actually updating the fields to NULL. Since this query is counting the rows of each column group by another column, then there is always a value for it.and I get the correct count from the query outside of the string..
when I place the query in my plsql Block I use the Execute immediate and the query would be a string in which my_table_name and my_column_names and Table_1 are variable.
I searched alot and found things like I should commit and etc. But problem still there.
for example:
Need help in execute immediate update query
update Table_FINAL r
set column_1 =
(
select max(totalcount)
from (
select 'my_table_name' as table_name, 'my_collumn_name' as column_name, column_3, count(*) as totalcount
from my_table_name a
where exists (select 1 from Table_2 where Table_2.column_x = a.column_x)
group by column_3
) s
where r.column_3 = s.column_3
)
;
and here the string:
execute immediate 'update Table_FINAL r
set column_1 =
(
select max(totalcount)
from (
select ''' || my_table_name || ''' as table_name, ''' || my_collumn_name || ''' as column_name, column_3, count(*) as totalcount
from ' || my_table_name || ' a
where exists (select 1 from Table_2 where Table_2.column_x = a.column_x)
group by column_3
) s
where r.column_3 = s.column_3
)'
;
it updates column_1 to NULL
If your dynamic SQL returns no rows, totalcount will be null, so max(totalcount) will be null and in that circumstance column_1 will be updated to null.
There are a couple of obvious solutions:
only execute the update if there's a value: ... where r.column_3 = s.column_3 and totalcount is not null.
handle the null : select max(nvl(totalcount,0)) …
Now, you assert that the update query "works fine". Does it work fine for all values you pass as my_table_name? Another reason why dynamic SQL is hard is that we can't look at the source code and know for certain what it's going to do at runtime. So you need to so some debugging. Run the dynamic SELECT statement without the update and see what you're actually executing:
execute immediate ' select max(totalcount) from (
select ''' || my_table_name || ''' as table_name, ''' || my_collumn_name || ''' as column_name, column_3, count(*) as totalcount
from ' || my_table_name || ' a
where exists (select 1 from Table_2 where Table_2.column_x = a.column_x)
group by column_3)' into l_total_count;
dbms_ouput.put_line(my_table_name ||'.'|| my_collumn_name ||' max(totalcount) = ' || l_total_count);
Remember to enable SERVEROUTPUT in whatever client you're using.
the update string is inside a loop and it seems that in each loop round it indeed does the correct update but then ir updates the rest to NULL … But I do not fully get what I should do since I hvae hundreds of fields.. (sic)
I guessed you were calling this code from a loop. So, it's not that you don't overwrite the "correct update" with null but that you don't want to over-write any value with any subsequent value. I can offer a few suggestions but really this is your data model and your business logic, so only you can decide the correct way to handle this.
Aggregate totalcount like this: set column_1 = column_1 + total_count. For this to work you'll need to apply nvl(max(totalcount),0).
Add a column to TABLE_FINAL to store values of my_collumn_value. Reference that column in the UPDATE statement's WHERE clause. (Obviously this supposes a completely different result set from the previous suggestion). You may need a column for values of my_table_name too.

Using top N Employees

I need a query to get Top N employees working on every project in a specific month based on working hours. And I need the same query for all time (without specifying a month).
I get two hints first Hint: Use Substitution variables to prompt the user to enter values for N and a month.
Second Hint: Use the rank analytical function. If two employees tie they should get the same rank.
Just right now i have this not completed solution, and not sure if i should complete it:
SELECT BSR_PROJ.PROJECT_NAME,
BSR_TM.FNAME || ' ' || BSR_TM.MNAME || ' ' || BSR_TM.LNAME EMPLOYEE_NAME,
BSR_TRD.WORK_ITEM_DATE,
RANK() OVER (PARTITION BY BSR_PROJ.PROJECT_NAME ORDER BY BSR_TRD.WORK_ITEM_DATE ASC) EMPRANK
FROM BSR_TEAM_REPORT_DETAILS BSR_TRD,
BSR_PROJECTS BSR_PROJ,
BSR_TEAM_MEMBERS BSR_TM
WHERE BSR_TRD.BSR_TEAM_RES_ID = BSR_TM.ID
AND BSR_TRD.BSR_PRJ_ID = BSR_PROJ.ID
;
You need to include the month in the analytic function, as rank is calculated per project per month; truncating the date with a month format mask is an easy way to achieve this.
You also need to include that truncated month in the projection, so you can filter on it. I have chosen to present the month in the format 2015-12. You may wish to show it differently.
The query you have will generate the whole set of ranks for all employees across all assignments. You need an outer query to apply the filtering requirements. My solution uses SQL Plus substitution variables rather than bind variables, but the principle is the same:
select distinct project_name
, employee_name
from (
SELECT BSR_PROJ.PROJECT_NAME,
BSR_TM.FNAME || ' ' || BSR_TM.MNAME || ' ' || BSR_TM.LNAME EMPLOYEE_NAME,
to_char(trunc(bsr_trd.work_item_date, 'MM'), 'yyyy-mm') as project_month,
RANK() OVER (PARTITION BY BSR_PROJ.PROJECT_NAME,
trunc(bsr_trd.work_item_date, 'MM')
ORDER BY BSR_TRD.WORK_ITEM_DATE ASC) EMPRANK
FROM BSR_TEAM_REPORT_DETAILS BSR_TRD,
BSR_PROJECTS BSR_PROJ,
BSR_TEAM_MEMBERS BSR_TM
WHERE BSR_TRD.BSR_TEAM_RES_ID = BSR_TM.ID
AND BSR_TRD.BSR_PRJ_ID = BSR_PROJ.ID
)
where project_month = '&proj_month'
and emprank <= &rnk;
The answer i found as below :
SELECT *
FROM (SELECT RANK ()
OVER (PARTITION BY PROJ_NAME ORDER BY WORKING_HOURS DESC)
AS EMPRANK,
PROJ_NAME,
EMPLOYEE_NAME,
WORKING_HOURS
FROM ( SELECT BSR_PROJ.PROJECT_NAME AS PROJ_NAME,
BSR_TM.FNAME
|| ' '
|| BSR_TM.MNAME
|| ' '
|| BSR_TM.LNAME
EMPLOYEE_NAME,
SUM (WORK_ITEM_CONSUMED_HOURS) AS WORKING_HOURS
FROM BSR_TEAM_REPORT_DETAILS BSR_TRD,
BSR_PROJECTS BSR_PROJ,
BSR_TEAM_MEMBERS BSR_TM
WHERE BSR_TRD.BSR_TEAM_RES_ID = BSR_TM.ID
AND BSR_TRD.BSR_PRJ_ID = BSR_PROJ.ID
AND TO_CHAR (TRUNC (BSR_TRD.WORK_ITEM_DATE, 'MM'),
'YYYY-MM') = '&PROJ_MONTH'
GROUP BY BSR_PROJ.PROJECT_NAME,
BSR_TM.FNAME
|| ' '
|| BSR_TM.MNAME
|| ' '
|| BSR_TM.LNAME)) INNER_TABLE
WHERE EMPRANK <= &RNK;

Quering all columns of single table in where condition for same input data

If we want to fetch information based on condition on single column, we do like this
SELECT * FROM contact WHERE firstName = 'james'
IF we want to put conditions on multiple columns, we do this
SELECT * FROM contact WHERE firstName = 'james' OR lastName = 'james' OR businessName = 'james'
But What if we have more than 50 columns.
Is there better way other than WHERE Condition with OR Keyword?
The approach should not involve writing all column names.
There is a way to do this in MySql as shown here.
If you're wanting to search all VARCHAR2 columns, then the following script ought to help:
set pages 0;
set lines 200
select case when rn = 1 and rn_desc = 1 then 'select * from '||table_name||' where '||column_name||' = ''james'';'
when rn = 1 then 'select * from '||table_name||' where '||column_name||' = ''james'''
when rn_desc = 1 then ' and '||column_name||' = ''james'';'
else ' and '||column_name||' = ''james'''
end sql_stmt
from (select table_name,
column_name,
column_id,
row_number() over (partition by table_name order by column_id) rn,
row_number() over (partition by table_name order by column_id desc) rn_desc
from user_tab_columns
where data_type in ('VARCHAR2')
-- and table_name in (<list of tables>) -- uncomment and amend as appropriate!
)
order by table_name, column_id;
If you only want to search specific tables, you would have to put a filter in for the table_names you're after.
Running the above as a script will give you a script containing multiple queries that you can then run
There is no way you can avoid writing all the column names,
But you can use an IN condition to make writing this a bit shorter:
SELECT *
FROM contact
WHERE 'james' in (firstName, lastName, businessName)

Exists query with not equal running extremly slow

I am trying to modify a query that someone else wrote which is taking a really long time to run. The problem has to do with the <> portion of the exists query. Any idea how this can be changed to run quicker?
SELECT m.level4 center, cc.description, m.employeename, m.empno,
TO_DATE (ct.tsdate, 'dd-mon-yyyy') tsdate, ct.starttime, ct.endtime,
ct.DURATION,
NVL (DECODE (ct.paycode, ' ', 'REG', ct.paycode), 'REG') paycode,
ct.titlecode, ct.costcenter, m.tsgroup
FROM clairvia_text ct, MASTER m, costcenteroutbound cc
WHERE ct.recordtype = '1'
AND ct.empno = m.empno
AND m.level4 = cc.center
AND EXISTS (
SELECT ct1.recordtype,ct1.empno,ct1.tsdate,ct1.processdate
FROM clairvia_text ct1
WHERE ct.recordtype = ct1.recordtype
AND ct.empno = ct1.empno
AND ct.tsdate = ct1.tsdate
AND ct.processdate = ct1.processdate
group by ct1.recordtype,ct1.empno,ct1.tsdate,ct1.processdate
having count(*) < 2)
Oracle can be finnicky with exists statements and subqueries. A couple of things to try:
Change the exists to an "in"
Change the exists to a group by statement with a "having count(1) > 1". This could even be changed into a join.
I'm assuming indexes are not an issue.
You can use analytic function count here to eliminate duplicated rows.
select * from (
SELECT m.level4 center, cc.description, m.employeename, m.empno,
TO_DATE (ct.tsdate, 'dd-mon-yyyy') tsdate, ct.starttime, ct.endtime,
ct.DURATION,
NVL (DECODE (ct.paycode, ' ', 'REG', ct.paycode), 'REG') paycode,
ct.titlecode, ct.costcenter, m.tsgroup,
count(1) over (partition by ct.recordtype,ct.empno,ct.tsdate,ct.processdate
order by null) cnt
FROM clairvia_text ct, MASTER m, costcenteroutbound cc
WHERE ct.recordtype = '1'
AND ct.empno = m.empno
AND m.level4 = cc.center
) where cnt=1
I do not have your structures and data, so I run similar queries with all_tab_cols and first query took ~500s on my laptop and second query ~2s.
-- slow test
select count(1)
from all_tab_cols atc
where exists (
select 1
from all_tab_cols atc1
where atc1.column_name = atc.column_name
group by column_name
having count(1) = 1)
-- fast test
select count(1)
from (
select column_name,
count(1) over (partition by atc.column_name order by null) cnt
from all_tab_cols atc
)
where cnt = 1

Given a pk value, is there a way to find which tables have a relationship with that specific pk?

Let's say that i have a given value of a PK column:
10
Is there a query to find all tables which have a relationship with that specific pk value?
Example for single column PK.
PK constraint name can be derived from DBA_CONSTRAINTS (USER_CONSTRAINTS, ALL_CONTRAINTS).
The code can be extended to multi-column PKs using collection for PK values
declare
p_pk_name varchar2(30):= '<PK constraint_name>';
p_pk_value varchar2(500):='<PK value>';
l_cnt int;
begin
for x in
(
select t.owner, t.table_name, cc.column_name
from dba_constraints c,
dba_tables t,
dba_cons_columns cc
where c.r_constraint_name = p_pk_name
and t.owner = c.owner
and t.table_name = c.table_name
and cc.owner = c.owner
and cc.constraint_name = c.constraint_name
)
loop
execute immediate 'select count(1) from '||x.owner||'.'||x.table_name|| ' where '||x.column_name||' = :p_pk_value and rownum <= 1' into l_cnt using p_pk_value ;
if l_cnt <> 0 then
dbms_output.put_line(x.owner||'.'||x.table_name);
end if;
end loop;
end;
Not a single query, but you could write a PL/SQL block to do it. You'd start with a query on USER_CONSTRAINTS (or ALL_CONSTRAINTS or DBA_CONSTRAINTS) to find the tables with foreign keys that reference the given primary key. For each, you'd create and execute a dynamic SQL query to see if there are any rows with the particular PK value you're interested in.
Making it work for multi-column keys would be an additional challenge.
Here's how it would look in SQL Server:
DECLARE #keyColumn VARCHAR(100)
DECLARE #keyValue INT
SET #keyColumn = 'SiteID'
SET #keyValue = 400
SELECT 'SELECT ''' + TABLE_NAME + ''' AS TABLENAME, "' + #keyColumn
+ '" FROM "' + TABLE_NAME + '" WHERE "' + #keyColumn + '" = '
+ CONVERT(VARCHAR(100), #keyValue) + ' UNION ALL'
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
WHERE KCU.COLUMN_NAME = #keyColumn
I know Oracle has INFORMATION_SCHEMA, so something similar should work.
I tried with SQLFiddle to adapt it but failed. Perhaps someone else here can edit my answer so it works with Oracle.
Here's what the generated SQL looks like, in part:
SELECT 'StockInventory' AS TABLENAME, "SiteID" FROM "StockInventory" WHERE "SiteID" = 400 UNION ALL
SELECT 'SiteDomains' AS TABLENAME, "SiteID" FROM "SiteDomains" WHERE "SiteID" = 400 UNION ALL

Resources