SQL select display row data as columns for varchar items - sql

This is the query I am using to generate my table:
SELECT cu.idchanneluser AS Approver,
mcr.idrule AS Rule,
mrd.idseq AS Seq,
mcr.idcust AS CustID,
cu.iduser AS USERID
FROM mstchanneluser cu,
mstcatruledetail mrd,
mstcatrule mcr
WHERE idchannel='01'
AND mrd.idlist=cu.iduser
AND mrd.idrule=mcr.idrule
AND mcr.nbrauth='2'
AND mcr.isautoauth='N'
and this the snapshot of result:
but I want result like this:
FirstAuthorizer SecondAuthorzier Rule
rohitcorp ajitcorp 3090
CORPTEST TESTCORP 8634
ABHIMAKER CORPTEST 11705
I tried this query:
SELECT CASE WHEN idseq = '0' THEN idchanneluser ELSE NULL END AS Approver,
case when idseq = '1' THEN idchanneluser ELSE NULL END AS secondApprover,
cu.idchanneluser AS Approver,
mcr.idrule AS Rule,
mrd.idseq AS Seq,
mcr.idcust AS CustID,
cu.iduser AS USERID
FROM mstchanneluser cu,
mstcatruledetail mrd,
mstcatrule mcr
WHERE idchannel='01'
AND mrd.idlist=cu.iduser
AND mrd.idrule=mcr.idrule
AND mcr.nbrauth='2'
AND mcr.isautoauth='N'
and it will be returning me this result check the snapshot.

this is derived from your second query, you just need to use MAX and group them by mcr.idrule
SELECT MAX(CASE WHEN idseq = '0' THEN idchanneluser ELSE NULL END) AS Approver
, MAX(CASE WHEN idseq = '1' THEN idchanneluser ELSE NULL END) AS secondApprover
, mcr.idrule AS RULE
FROM mstchanneluser cu
, mstcatruledetail mrd
, mstcatrule mcr
WHERE idchannel = '01'
AND mrd.idlist = cu.iduser
AND mrd.idrule = mcr.idrule
AND mcr.nbrauth = '2'
AND mcr.isautoauth = 'N'
GROUP BY mcr.idrule

Another way in oracle with Analytical function lead lag
select tab.y FirstAuthorizer,tab.x SecondAuthorzier,tab.rule from
(
select lead(appprover,0) over (partition by rule order by seq) x ,
lag(appprover,1) over (partition by rule order by seq) y,
rule
from tbl ) tab
where tab.y is not null;

Related

SQL to return 1 or 0 depending on values in a column's audit trail

If I were to have a table such as the one below:
id_
last_updated_by
1
robot
1
human
1
robot
2
robot
3
robot
3
human
Using SQL, how could I group by the ID and create a new column to indicate whether a human has ever updated the record like this:
id_
last_updated_by
updated_by_human
1
robot
1
2
robot
0
3
robot
1
UPDATE
I'm currently doing the following, though I'm not sure how efficient this is. Selecting the latest record and then merging it with my calculated column via a sub-select.
SELECT MAIN.TRANSACTION_ID,
MAIN.CREATED_DATE
MAIN.CREATED_BY_USER_ID,
MAIN.OWNER_USER_ID,
STP.TOUCHED_BY_HUMAN
FROM (
SELECT TRANSACTION_ID,
CREATED_DATE
CREATED_BY_USER_ID_
OWNER_USER_ID_
FROM TABLE_NAME
WHERE CREATED_DATE >= CAST('{start_date} 00:00:00' AS TIMESTAMP)
AND CREATED_DATE <= CAST('{end_date} 23:59:59' AS TIMESTAMP)
QUALIFY row_number() OVER (partition by TRANSACTION_ID order by End_Dt desc) = 1
) MAIN
LEFT JOIN (
SELECT TRANSACTION_ID,
CASE
WHEN CREATED_BY_USER_ID IN ('ROBOT', 'MACHINE') OR
CREATED_BY_USER_ID LIKE 'N%' OR
CREATED_BY_USER_ID IS NULL
THEN 0
ELSE 1 END AS CREATED_BY_HUMAN,
CASE
WHEN OWNER_USER_ID IN ('ROBOT', 'MACHINE') OR
OWNER_USER_ID LIKE 'N%' OR
OWNER_USER_ID IS NULL
THEN 0
ELSE 1 END AS OWNED_BY_HUMAN,
CASE
WHEN CREATED_BY_HUMAN = 0 AND
OWNED_BY_HUMAN = 0
THEN 0
ELSE 1 END AS TOUCHED_BY_HUMAN_
FROM TABLE_NAME
WHERE CREATED_DATE >= CAST('{start_date} 00:00:00' AS TIMESTAMP)
AND CREATED_DATE <= CAST('{end_date} 23:59:59' AS TIMESTAMP)
QUALIFY row_number() OVER (partition by TRANSACTION_ID order by TOUCHED_BY_HUMAN_ desc) = 1
) STP
ON MAIN.TRANSACTION_ID = STP.TRANSACTION_ID
If I'm following your problem, then something like this should work.
SELECT
t.*
,CASE WHEN a.id IS NOT NULL THEN 1 ELSE 0 END AS updated_by_human
FROM table t
LEFT JOIN (SELECT DISTINCT id FROM table WHERE last_updated_by = 'human') a ON t.id = a.id
That takes care of the updated_by_human field, but if you also need to reduce the records in table (only keeping a subset) then you need more information to do that.
Exists clauses are usually not that performant but if your data isn't big this should work.
select id_,
IF (EXISTS (SELECT 1 FROM table_name t2 WHERE t2.last_updated_by = 'human' and t2.id_ = t1.id_), 1, 0) AS updated_by_human
from table_name t1;
here is another way
SELECT *
FROM table_name t1
GROUP BY ti.id_
HAVING COUNT(*) > 0
AND MAX(CASE t1.last_updated_by WHEN 'human' THEN 1 ELSE 0 END) = 1;
Since you didn't specified which column is used to determine this record is the newest record added by a given id, I assume that there will be a column to track the insert/modify timestamp (which is pretty standard table design), let's put it is last_updated_timestamp (if you don't have any, then I still insist you to have one as an auditing trail without timestamp does not make sense)
Given your table name is updating_trail
SELECT updating_trail.*, last_update_trail.modified_by_human
FROM updating_trail
INNER JOIN (
-- determine the id_, the lastest modified_timestamp, and a flag check to determine if there is any record with last_update_by is 'human' -> if yes then give 1
SELECT updating_trail.id_, MAX(last_update_timestamp) AS most_recent_update_ts, MAX(CASE WHEN updating_trail.last_updated_by = 'human' THEN 1 ELSE 0 END) AS modified_by_human
FROM updating_trail
GROUP BY updating_trail.id_
) last_update_trail
ON updating_trail.id_ = last_update_trail.id_ AND updating_trail.last_update_timestamp = last_update_trail.most_recent_update_ts;
Give
id_
last_updated_by
last_update_timestamp
modified_by_human
1
robot
2021-10-19T20:00:00.000Z
1
2
robot
2021-10-19T17:00:00.000Z
0
3
robot
2021-10-19T16:00:00.000Z
1
Check out this sample db fiddle I created for you
This is a 1:1 translation of your query to conditional aggregation:
SELECT TRANSACTION_ID,
CREATED_DATE,
CREATED_BY_USER_ID,
OWNER_USER_ID,
Max(CASE
WHEN CREATED_BY_USER_ID IN ('ROBOT', 'MACHINE') OR
CREATED_BY_USER_ID LIKE 'N%' OR
CREATED_BY_USER_ID IS NULL
THEN 0
ELSE 1
END) Over (PARTITION BY TRANSACTION_ID) AS CREATED_BY_HUMAN
FROM Table_Name
WHERE CREATED_DATE >= Cast('{start_date} 00:00:00' AS TIMESTAMP)
AND CREATED_DATE <= Cast('{end_date} 23:59:59' AS TIMESTAMP)
QUALIFY Row_Number() Over (PARTITION BY TRANSACTION_ID ORDER BY End_Dt DESC) = 1

Oracle SQL -- Pick Max Value from RN Function but update all fields with that value

My query is as follows
SELECT HEADER_TABLE.SEGMENT1,
LINES_TABLE.LINE_NUM,
CASE
WHEN ( HEADER_TABLE.REVISION_NUM = '0'
AND HEADER_TABLE.PRINT_COUNT = '0')
THEN
'Unavailable'
ELSE
NVL (ACK_TABLE.ACK_TYPE, 'Absent')
END
AS X_ACK_TYPE,
ACK_TABLE.GXS_DATE
FROM HEADER_TABLE,
LINES_TABLE,
(SELECT po_number,
po_line_number,
gxs_date,
po_ack_filename,
ack_type
FROM (SELECT po_number,
po_line_number,
gxs_date,
po_ack_filename,
ack_type,
ROW_NUMBER ()
OVER (PARTITION BY po_number ORDER BY gxs_date DESC)
rn
FROM xxcmst_po_ack_from_gxs_stg)
WHERE rn = 1) ACK_TABLE,
(SELECT PO_NUMBER FROM XXCMST.XXCMST_ACTION_TABLE_ACKNOWLEDGEMENT) ACTION_TABLE
WHERE HEADER_TABLE.PO_HEADER_ID = LINES_TABLE.PO_HEADER_ID
AND HEADER_TABLE.SEGMENT1 = ACK_TABLE.PO_NUMBER(+)
AND HEADER_TABLE.SEGMENT1 = ACTION_TABLE.PO_NUMBER(+)
AND LINES_TABLE.LINE_NUM = ACK_TABLE.PO_LINE_NUMBER(+)
AND HEADER_TABLE.SEGMENT1 = '100';
This is giving me 6 records with 1 GXS_DATE and X_ACK_TYPE = 'Absent'. The RN function is needed here to pull 1 record only from the subquery but the requirement is to have all the 6 records have the same date and ACK_TYPE which is not happening. How can I achieve this? Please refer to the below screenshot and I need X_ACK_TYPE = AK for all the 6 LINE_NUMs and GXS_DATE = 3/6/2020 for all these 6 records.
My current data screenshot here
Instead of
ACK_TABLE.GXS_DATE
in SELECT clause use the LAG function as follows:
CASE WHEN ACK_TABLE.GXS_DATE IS NOT NULL
THEN ACK_TABLE.GXS_DATE
ELSE LAG(ACK_TABLE.GXS_DATE IGNORE NULLS)
OVER (PARTITION BY HEADER_TABLE.SEGMENT1 ORDER BY LINES_TABLE.LINE_NUM )
END AS GXS_DATE
or If there will be always one value of ACK_TABLE.GXS_DATE exists per HEADER_TABLE.SEGMENT1 then you can simply write it as
MIN(ACK_TABLE.GXS_DATE)
OVER (PARTITION BY HEADER_TABLE.SEGMENT1) AS GXS_DATE
-- Update --
for ACK_TYPE, You need to apply the same logic in ELSE portion of your CASE statement from the original query as follows:
Replace this:
ELSE
NVL (ACK_TABLE.ACK_TYPE, 'Absent')
END
With this:
ELSE
NVL (MIN(ACK_TABLE.ACK_TYPE)
OVER (PARTITION BY HEADER_TABLE.SEGMENT1), 'Absent')
END

SQL Select a specific value in the group

I have this following table
Dept---------- Sub_Dept---- Dept Type
Sales.............Advertising........A
Sales.............Marketing......... B
Sales.............Analytics.......... C
Operations.....IT..................... C
Operations.....Settlement........C
And the result should be if a department got a department type as A then change all record of that department to A, else keep it same
Dept---------- Sub_Dept---- Dept Type
Sales.............Advertising........A
Sales.............Marketing......... A
Sales.............Analytics.......... A
Operations.....IT..................... C
Operations.....Settlement........C
Anybody can give a suggestion on this? I thought of using the GROUP BY but have to output the Sub Department as well
Thanks a lot
I would do:
update t
set depttype = 'a'
where exists (select 1 from t t2 where t2.dept = t.dept and t2.dept = 'a') and
t.dept <> 'a';
If you just want a select, then do:
select t.*,
(case when sum(case when depttype = 'a' then 1 else 0 end) over (partition by dept) > 1
then 'a'
else depttype
end) as new_depttype
from t;
Use below query
select a11.dept, a12.Sub_Dept, (case when a12.min_dep_type='A' then 'A' else a11.dep_type) as dep_type
from tab a11
JOIN (select dept, min(dep_type) min_dep_type from tab group by dept) a12
on a11.dept = a12.dept
Try this:
update table
set depttype= case when dept in (select dept from table where depttype='a') then 'a' else depttype end
This should work:
select a.dept, a.sub_dept,
case when b.dept is not null then 'A' else dept_type end as dept_type
from aTable a
left join(
select distinct Dept from aTable where dept_type = 'A'
)
b on b.dept = a.dept
You could use analytic functions to check whether exists the specific value in the group.
Try below query:
SELECT t.Dept,
t.Sub_Dept,
NVL(MIN(CASE WHEN t.Dept_Type = 'A'
THEN Dept_Type END) OVER (PARTITION BY t.Dept), t.Dept_Type) AS Dept_Type
FROM table_1 t
Using the analytic function MIN(), you can search for the value of 'A' (if it does exist inside the group). MIN works for non-null values only, so if you don't have any 'A' in the group, the result will be NULL.
At this point, you can use NVL to choose whether to print the value found in the group or the actual dept_type of the row.

Subqueries in MSSQL producing NULL values

I am trying to determine my store only accounts revenue from the database, to do this I need to look through all account numbers with revenue against a 'store' description who do NOT appear in a list of accounts with an 'online' description which I have tried todo in the subquery below. The query runs however it just returns NULL values in my store_only_revenue column. Any guidance on what to do from here would be appreciated. Am I approaching the problem in a good way? Or is there a better solution:
SELECT
town,
financial_pd as month,
SUM(CASE WHEN [Descr] = 'online' THEN Net_Revenue ELSE 0 END) as online_revenue,
SUM(CASE WHEN [Descr] = 'store' THEN Net_Revenue ELSE 0 END) as store_revenue,
COUNT(DISTINCT CASE WHEN [Descr] = 'online' THEN Account_Number ELSE NULL END) as online_accounts,
COUNT(DISTINCT CASE WHEN [Descr] = 'store' THEN Account_Number ELSE NULL END) as store_accounts,
(SELECT
SUM(Net_Revenue)
FROM [mydb].[dbo].[mytable]
WHERE
Descr = 'store'
AND Account_Number
NOT IN(
SELECT DISTINCT Account_Number
FROM [mydb].[dbo].[mytable]
WHERE
Descr = 'online')
) as store_only_revenue
FROM [mydb].[dbo].[mytable] as orders
WHERE
Group_name = 'T'
AND NOT
Type_name_1 = 'Electronic'
AND
Account_type <> 1
AND
Total_Value > 0
AND
(Insert_Date BETWEEN '2016-05-30' AND '2016-07-03'
OR
Insert_Date BETWEEN '2015-05-25' AND '2015-06-28')
OR
(Insert_Date BETWEEN '2016-05-30' AND '2016-07-03'
AND
Insert_Date BETWEEN '2015-05-25' AND '2015-06-28')
GROUP BY
town,
financial_pd as period
This expression is suspect:
Account_Number NOT IN (SELECT DISTINCT t.Account_Number
FROM [mydb].[dbo].mytable t
WHERE t.Descr = 'online'
)
Assuming that the syntax problems are typos (missing table name, desc is a reserved word), then this will never return true if even one Account_Number is NULL. One way to fix this is:
Account_Number NOT IN (SELECT t.Account_Number
FROM [mydb].[dbo].mytable t
WHERE t.Desc = 'online' AND t.Account_Number IS NOT NULL
)
I would use NOT EXISTS:
not exists (select 1
from [mydb].[dbo].??? x
where x.Desc = 'online' AND ??.Account_Number = x.Account_Number
)
You need to use proper table aliases for this to work. Either of these solutions may fix your problem.

How to optimize this query in SQL Server 2008

How can I optimize query in SQL Server 2008?
Here is my Query.
SELECT DISTINCT
ListName ,
( SELECT COUNT(id)
FROM tbl_SurveyAssign
WHERE ListName = a.ListName
AND UserName IN (
SELECT UserName
FROM tbl_Panelist
WHERE tbl_Panelist.Subscribe = '1'
AND tbl_Panelist.Pending = '0'
AND tbl_Panelist.UserName IN (
SELECT UserName
FROM tbl_PanelistActivity
WHERE tbl_PanelistActivity.ActivityDate > ( GETDATE()
- 180 ) ) )
) AS Active ,
( SELECT COUNT(id)
FROM tbl_SurveyAssign
WHERE ListName = a.ListName
AND UserName IN (
SELECT UserName
FROM tbl_Panelist
WHERE tbl_Panelist.Subscribe = '1'
AND tbl_Panelist.Pending = '1' )
) AS Pending ,
( SELECT COUNT(id)
FROM tbl_SurveyAssign
WHERE ListName = a.ListName
AND UserName IN (
SELECT UserName
FROM tbl_Panelist
WHERE tbl_Panelist.Subscribe = '0'
AND tbl_Panelist.Pending = '0' )
) AS UnSubscribe ,
( SELECT COUNT(id)
FROM tbl_SurveyAssign
WHERE ListName = a.ListName
AND UserName IN (
SELECT UserName
FROM tbl_Panelist
WHERE tbl_Panelist.Subscribe = '1'
AND tbl_Panelist.Pending = '0'
AND tbl_Panelist.UserName NOT IN (
SELECT UserName
FROM tbl_PanelistActivity
WHERE tbl_PanelistActivity.ActivityDate > ( GETDATE()
- 180 ) ) )
) AS Inactive ,
( SELECT COUNT(id)
FROM tbl_SurveyAssign
WHERE ListName = a.ListName
) AS Total ,
( SELECT COUNT(id)
FROM tbl_SurveyAssign
WHERE ListName = a.ListName
AND UserName NOT IN ( SELECT UserName
FROM tbl_Panelist )
) AS NotMember
FROM tbl_SurveyAssign a
Without knowing the data, indexes etc and looking on the execution plan I would say there is two ways of making the query easier for the SQL server to process.
Simple soluiotn.
If you doing SQL queries inside the select part, the server is likely to make the same query for each row in tbl_SurveyAssign.
if you for each query making a query groped by listname and (select listname, count(*) from xxxxx group by listname) and joining in the result, the server just needs to makeing one query for each column. But it also depends if hte content of tbl_SurveyAssign always contains all list rows...
More advanced
At a quick glance it looks like you should be able to make this query with just one or two queries using joins. If you for exemple using tbl_SurveyAssign as a main table and to a left join with tbl_Panelist using username grouping on list name, you could do a count on tbl_PanelList you should get the number of members. From that you can calculate NotMembers but substracting form count(*) (this will skip a Not In( that is quite heavy.
Someting like this
select a.listname, count() Total, count() - count(b.username) NonMembers,
sum(case when b.Subscribe = '0' and b.Pending = '0' then 1 else 0 end) Unsubscribed,
sum(case when b.Subscribe = '1' and b.Pending = '1' then 1 else 0 end) Pending
from tbl_SurveyAssign a
left outer join tbl_PanelList b on a.username = b.username
You shold also be able to do a grouping query calculating the pending,active and unsubscribed just once.
if you grouping the activity by username and doing a max on the activity date you should be able to calculate the activity in one query as well.
select a.listname, count() Total, count() - count(b.username) NonMembers,
sum(case when b.Subscribe = '0' and b.Pending = '0' then 1 else 0 end) Unsubscribed,
sum(case when b.Subscribe = '1' and b.Pending = '1' then 1 else 0 end) Pending,
sum(case when b.Subscribe = '1' and b.Pending = '0' and act.DaysSinceLastActivity < 180 then 1 else 0 end) Active,
sum(case when b.Subscribe = '1' and b.Pending = '0' and act.DaysSinceLastActivity >= 180 then 1 else 0 end) Inactive
from tbl_SurveyAssign a
left outer join tbl_PanelList b on a.username = b.username
left outer join
(
select UserName, DateDiff(day, max(ActivityDate), GetDate()) DaysSinceLastActivity from tbl_PanelistActivity group by UserName
) act on a.username = act.username
Something like that. Keep in mind that its not tested in anyway, and I am quit sure I have misspelled a keyword or two.. But it will give you much less code.. and I think it is more readable as well. AND if there still is a performance issue, you will have a much smaller activity plan to dig in to...
I hope that helps you!