SQL Column Populating - sql-server-2008-r2

I want to know if it is possible to create another column in a table that has data that I wish to populate in this new column? The new column is Flag2. Here is the table:
what I want to do is, where item id is 30, I want the ITEM ID to only display 30 once and, populate the QC Unsupportted in Flag2? How do I do this?
I can only think of doing an inner join but this is not working.
This is what I have done in trying to do so:
SELECT
A.ITEMID, A.FLAG1, A.FLAG2
FROM
#FLAGS as A
INNER JOIN
#FLAGS as B ON A.ITEMID = B.ITEMID
GROUP BY
a.ITEMID, a.FLAG1, A.FLAG2
ORDER BY
ITEMID

Assuming I understand what you are after, if the current FLAG1 values are distinct for any ITEMID and you only have at most two instances of the same ID, I think this should do what you want:
SELECT
lft.ITEMID
, lft.FLAG1
, rght.FLAG1 FLAG2
FROM (
SELECT
t.ITEMID
, t.FLAG1
FROM (
SELECT
l.ITEMID
, l.FLAG1
, COUNT(l.ITEMID) i
FROM #FLAGS l
INNER JOIN #FLAGS r ON l.ITEMID = r.ITEMID
WHERE r.FLAG1 <= l.FLAG1
GROUP BY
l.ITEMID
, l.FLAG1) t
WHERE t.i=1) lft
LEFT OUTER JOIN (
SELECT
t.ITEMID
, t.FLAG1
FROM (
SELECT
l.ITEMID
, l.FLAG1
, COUNT(l.ITEMID) i
FROM #FLAGS l
INNER JOIN #FLAGS r ON l.ITEMID = r.ITEMID
WHERE r.FLAG1 <= l.FLAG1
GROUP BY
l.ITEMID
, l.FLAG1) t
WHERE t.i=2) rght ON lft.ITEMID = rght.ITEMID
-- Or better
SELECT
lft.ITEMID
, lft.FLAG1
, rght.FLAG1 FLAG2
FROM (
SELECT
t.ITEMID
, t.FLAG1
FROM (
SELECT
l.ITEMID
, l.FLAG1
, ROW_NUMBER() OVER(PARTITION BY ITEMID ORDER BY FLAG1) as i
FROM test l) t
WHERE t.i=1) lft
LEFT OUTER JOIN (
SELECT
t.ITEMID
, t.FLAG1
FROM (
SELECT
l.ITEMID
, l.FLAG1
, ROW_NUMBER() OVER(PARTITION BY ITEMID ORDER BY FLAG1) as i
FROM test l) t
WHERE t.i=2) rght ON lft.ITEMID = rght.ITEMID
If you have additional flag values for the same ID, a new outer join can be added to a new inline table (rght2, rght3, etc.) where i=3, 4, etc. and you are selecting rght2 AS FLAG3, rght3 AS FLAG4, etc.
Also note that the current values for FLAG1 will be distributed through FLAG1 and FLAG2 in alphabetical order. If you wanted to distribute them in reverse order you could replace <= with >=. If you had more than two flags that you wanted distributed in a specific order, you would have to create a separate table with a ranking value and join to that which would be doable but even uglier!

Related

How to repeat some data points in query results?

I am trying to get the max date by account from 3 different tables and view those dates side by side. I created a separate query for each table, merged the results with UNION ALL, and then wrapped all that in a PIVOT.
The first 2 sections in the link/pic below show what I have been able to accomplish and the 3rd section is what I would like to do.
Query results by step
How can I get the results from 2 of the tables to repeat? Is that possible?
--define var_ent_type = 'ACOM'
--define var_ent_id = '52766'
--define var_dict_id = 113
SELECT
*
FROM
(
SELECT
E.ENTITY_TYPE,
E.ENTITY_ID,
'PERF_SUMMARY' as "TableName",
PS.DICTIONARY_ID,
to_char(MAX(PS.END_EFFECTIVE_DATE), 'YYYY-MM-DD') as "MaxDate"
FROM
RULESDBO.ENTITY E
INNER JOIN PERFORMDBO.PERF_SUMMARY PS ON (PS.ENTITY_ID = E.ENTITY_ID)
WHERE
1=1
-- AND E.ENTITY_TYPE = '&var_ent_type'
-- AND E.ENTITY_ID = '&var_ent_id'
AND PS.DICTIONARY_ID >= 100
AND (E.ACTIVE_STATUS <> 'N' )--and E.TERMINATION_DATE is null )
GROUP BY
E.ENTITY_TYPE,
E.ENTITY_ID,
'PERF_SUMMARY',
PS.DICTIONARY_ID
union all
SELECT
E.ENTITY_TYPE,
E.ENTITY_ID,
'POSITION' as "TableName",
0 as DICTIONARY_ID,
to_char(MAX(H.EFFECTIVE_DATE), 'YYYY-MM-DD') as "MaxDate"
FROM
RULESDBO.ENTITY E
INNER JOIN HOLDINGDBO.POSITION H ON (H.ENTITY_ID = E.ENTITY_ID)
WHERE
1=1
-- AND E.ENTITY_TYPE = '&var_ent_type'
-- AND E.ENTITY_ID = '&var_ent_id'
AND (E.ACTIVE_STATUS <> 'N' )--and E.TERMINATION_DATE is null )
GROUP BY
E.ENTITY_TYPE,
E.ENTITY_ID,
'POSITION',
1
union all
SELECT
E.ENTITY_TYPE,
E.ENTITY_ID,
'CASH_ACTIVITY' as "TableName",
0 as DICTIONARY_ID,
to_char(MAX(C.EFFECTIVE_DATE), 'YYYY-MM-DD') as "MaxDate"
FROM
RULESDBO.ENTITY E
INNER JOIN CASHDBO.CASH_ACTIVITY C ON (C.ENTITY_ID = E.ENTITY_ID)
WHERE
1=1
-- AND E.ENTITY_TYPE = '&var_ent_type'
-- AND E.ENTITY_ID = '&var_ent_id'
AND (E.ACTIVE_STATUS <> 'N' )--and E.TERMINATION_DATE is null )
GROUP BY
E.ENTITY_TYPE,
E.ENTITY_ID,
'CASH_ACTIVITY',
1
--ORDER BY
-- 2,3, 4
)
PIVOT
(
MAX("MaxDate")
FOR "TableName"
IN ('CASH_ACTIVITY', 'PERF_SUMMARY','POSITION')
)
Everything is possible. You only need a window function to make the value repeat across rows w/o data.
--Assuming current query is QC
With QC as (
...
)
select code, account, grouping,
--cash,
first_value(cash) over (partition by code, account order by grouping asc rows unbounded preceding) as cash_repeat,
perf,
--pos,
first_value(pos) over (partition by code, account order by grouping asc rows unbounded preceding) as pos_repeat
from QC
;
See first_value() help here: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/FIRST_VALUE.html#GUID-D454EC3F-370C-4C64-9B11-33FCB10D95EC

SQL - Rank or Row_Number not bringing back desired data

I'm writing a query that will bring me back either row or rank '1'. When I run the query with the 'where rank or row clause equals 1', no data returns for those columns but if I take out the 'where rank or row clause' the data populates. Can someone tell me why the where clause is causing no data to return?
LEFT OUTER JOIN
(SELECT DISTINCT CLMED.SIMPLE_GENERIC_C
, MEDINFO.MEDICATION_ID as [CYTOTEC]
,FMED.PAT_ENC_CSN_ID
, FMED.HOSP_ADMSN_DATE
-- , MIN(FMED.TAKEN_DATETIME) over (Partition by FMED.PAT_ENC_CSN_ID,
MEDINFO.MEDICATION_ID ) as 'FIRST DOSE'
, MIN(FMED.TAKEN_DATETIME) over (Partition by FMED.PAT_ENC_CSN_ID, CLMED.SIMPLE_GENERIC_C
) AS 'FIRST DOSE'
, RANK() over (Partition by FMED.PAT_ENC_CSN_ID, CLMED.SIMPLE_GENERIC_C order by
FMED.TAKEN_DATETIME DESC) 'CYTOTEC_RANK'
, ROW_NUMBER() over (Partition by FMED.PAT_ENC_CSN_ID, MEDINFO.MEDICATION_ID order by
FMED.TAKEN_DATETIME) AS 'CYTOTEC_ROW'
, FMED.DISPLAY_NAME
, FMED.ORDER_MED_ID
, ORDMED.DESCRIPTION
, ZCGEN.NAME
FROM MED_ADMIN FMED
LEFT JOIN MEDINFO MEDINFO ON FMED.ORDER_MED_ID = MEDINFO.ORDER_MED_ID
LEFT JOIN MED ORDMED ON FMED.ORDER_MED_ID = ORDMED.ORDER_MED_ID
LEFT JOIN MEDICATION CLMED ON MEDINFO.MEDICATION_ID = CLMED.MEDICATION_ID
LEFT JOIN GENERIC ZCGEN ON CLMED.SIMPLE_GENERIC_C = ZCGEN.SIMPLE_GENERIC_C
WHERE ZCGEN.NAME = 'miSOPROStol'
AND 'CYTOTEC_RANK' = 1
) AS [CYT] ON CYT.PAT_ENC_CSN_ID = VITALS.PAT_ENC_CSN_ID

Avoiding Order By in T-SQL

Below sample query is a part of my main query. I found SORT operator in below query is consuming 30% of the cost.
To avoid SORT, there is need of creation of Indexes. Is there any other way to optimize this code.
SELECT TOP 1 CONVERT( DATE, T_Date) AS T_Date
FROM TableA
WHERE ID = r.ID
AND Status = 3
AND TableA_ID >ISNULL((
SELECT TOP 1 TableA_ID
FROM TableA
WHERE ID = r.ID
AND Status <> 3
ORDER BY T_Date DESC
), 0)
ORDER BY T_Date ASC
Looks like you can use not exists rather than the sorts. I think you'll probably get a better performance boost by use a CTE or derived table instead of the a scalar subquery.
select *
from r ... left outer join
(
select ID, min(t_date) as min_date from TableA t1
where status = 3 and not exists (
select 1 from TableA t2
where t2.ID = t1.ID
and t2.status <> 3 and t2.t_date > t1.t_date
)
group by ID
) as md on md.ID = r.ID ...
or
select *
from r ... left outer join
(
select t1.ID, min(t1.t_date) as min_date
from TableA t1 left outer join TableA t2
on t2.ID = t1.ID and t2.status <> 3
where t1.status = 3 and t1.t_date < t2.t_date
group by t1.ID
having count(t2.ID) = 0
) as md on md.ID = r.ID ...
It also appears that you're relying on an identity column but it's not clear what those values mean. I'm basically ignoring it and using the date column instead.
Try this:
SELECT TOP 1 CONVERT( DATE, T_Date) AS T_Date
FROM TableA a1
LEFT JOIN (
SELECT ID, MAX(TableA_ID) AS MaxAID
FROM TableA
WHERE Status <> 3
GROUP BY ID
) a2 ON a2.ID = a1.ID AND a1.TableA_ID > coalesce(a2.MAXAID,0)
WHERE a1.ID = r.ID AND a1.Status = 3
ORDER BY T_Date ASC
The use of TOP 1 in combination with the unexplained r alias concern me. There's almost certainly a MUCH better way to get this data into your results that doesn't involve doing this in a sub query (unless this is for an APPLY operation).

How should I add fields without adding them to a GROUP BY?

I have a SQL statement that works as-is. I get an area name and the minimum value within that area. next, I need to add in a key so I can actually do something with the results. The key is necessary since names and values are unlikely to be unique.
select g.name, min(g.rndval) from
(
select p.rndval, a.name, p.id
from points p, areas a
where ST_WITHIN(p.geom, a.geom)
) AS g
group by g.name
When I add the Id field to the group by, the query returns multiple rows for each area, as expected since it's grouping by the name and id combination, and the results are no longer what I need. How should I add in the id field (p.id in the inner select)?
You can try:
WITH cte AS
( select p.rndval, a.name, p.id
from points p, areas a
where ST_WITHIN(p.geom, a.geom)
), cte_aggregated AS
(
SELECT name, min(rndval) AS min_value
FROM cte
GROUP BY name
)
SELECT DISTINCT c.rndval, c.name, c.id
FROM cte c
JOIN cte_aggregated ca
ON c.rndval = ca.min_value
AND c.name = ca.name;
You can solve this quite elegantly with a window function:
select name, rndval as min, id
from (
select a.name, p.rndval, p.id, rank() over (partition by a.name order by p.rndval) as rnk
from points p
join areas a on ST_Within(p.geom, a.geom)) as g
where rnk = 1;

Query to get row from one table, else random row from another

tblUserProfile - I have a table which holds all the Profile Info (too many fields)
tblMonthlyProfiles - Another table which has just the ProfileID in it (the idea is that this table holds 2 profileids which sometimes become monthly profiles (on selection))
Now when I need to show monthly profiles, I simply do a select from this tblMonthlyProfiles and Join with tblUserProfile to get all valid info.
If there are no rows in tblMonthlyProfile, then monthly profile section is not displayed.
Now the requirement is to ALWAYS show Monthly Profiles. If there are no rows in monthlyProfiles, it should pick up 2 random profiles from tblUserProfile. If there is only one row in monthlyProfiles, it should pick up only one random row from tblUserProfile.
What is the best way to do all this in one single query ?
I thought something like this
select top 2 * from tblUserProfile P
LEFT OUTER JOIN tblMonthlyProfiles M
on M.profileid = P.profileid
ORder by NEWID()
But this always gives me 2 random rows from tblProfile. How can I solve this ?
Try something like this:
SELECT TOP 2 Field1, Field2, Field3, FinalOrder FROM
(
select top 2 Field1, Field2, Field3, FinalOrder, '1' As FinalOrder from tblUserProfile P JOIN tblMonthlyProfiles M on M.profileid = P.profileid
UNION
select top 2 Field1, Field2, Field3, FinalOrder, '2' AS FinalOrder from tblUserProfile P LEFT OUTER JOIN tblMonthlyProfiles M on M.profileid = P.profileid ORDER BY NEWID()
)
ORDER BY FinalOrder
The idea being to pick two monthly profiles (if that many exist) and then 2 random profiles (as you correctly did) and then UNION them. You'll have between 2 and 4 records at that point. Grab the top two. FinalOrder column is an easy way to make sure that you try and get the monthly's first.
If you have control of the table structure, you might save yourself some trouble by simply adding a boolean field IsMonthlyProfile to the UserProfile table. Then it's a single table query, order by IsBoolean, NewID()
In SQL 2000+ compliant syntax you could do something like:
Select ...
From (
Select TOP 2 ...
From tblUserProfile As UP
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Order By NewId()
) As RandomProfile
Union All
Select MP....
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From (
Select TOP 1 ...
From tblUserProfile As UP
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
Order By NewId()
) As RandomProfile
Using SQL 2005+ CTE you can do:
With
TwoRandomProfiles As
(
Select TOP 2 ..., ROW_NUMBER() OVER ( ORDER BY UP.ProfileID ) As Num
From tblUserProfile As UP
Order By NewId()
)
Select MP.Col1, ...
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From TwoRandomProfiles
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Union All
Select ...
From TwoRandomProfiles
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
And Num = 1
The CTE has the advantage of only querying for the random profiles once and the use of the ROW_NUMBER() column.
Obviously, in all the UNION statements the number and type of the columns must match.