SQL Advance, creating fill new column with coditional value - sql

Cound you please support with question below, i trying to fill a new colmn in my SQL query based in a conditional value.
pseudo code:
IF ap_id exists in visit_table and visit_type = 'first'
then firs_tvisit_id = visit_table.visit_id and first_visit_user = visit_table.Username
The same logic for second and third visit.
This are visit_table.
And this are ap_table (where are the key values):
And below is the expected table.
SQL is able to make these kind of data manipulation, if there are how can i get this.
I alrady tryed left joins, inner joins, and full outher join. however i were not able to create a new column and fill this based in a conditional.

One method is multiple joins:
select a.*,
v1.visit_id, v1.user_name,
v2.visit_id, v2.user_name,
v3.visit_id, v3.user_name
from ap_table a left join
visit_table v1
on v1.ap_id = a.ap_id and v1.type = 'first' left join
visit_table v2
on v2.ap_id = a.ap_id and v2.type = 'second' left join
visit_table v3
on v3.ap_id = a.ap_id and v3.type = 'third'

You can join and do conditional aggregation:
select
a.ap_id,
a.location,
max(case when visit_type = 'first' then visit_id end) first_visit_id,
max(case when visit_type = 'second' then visit_id end) second_visit_id,
...
max(case when visit_type = 'first' then username end) first_visit_username,
max(case when visit_type = 'second' then username end) second_visit_username,
...
from ap_table a
inner join visit_table v on v.ap_id = a.ap_id
group by a.ap_id, a.location

It looks like you have some potential data issues here that must be answered for a proper solution. Most specifically, what should happen if the visit_table has multiple records for the same ap_id and visit_type? Or should we assume that there is a unique key on those two columns?
The best answer to this question as posted is actually likely to be dependent on the flavor of SQL you are using and the nuance of what you are really trying to do. For example, PostgreSQL has the ability to "SELECT DISTINCT ON" columns without requiring grouping. Oracle, SQL Server, and others have the ability to simplify complex queries using the with clause. And if other methods fail you can always write nested queries to try and grab everything in one monster query.
If you are able to use a temp table or table variable you may be better off selecting information into a constructed table as needed. If you need more than 3 visits, you may have to write dynamic sql. And if this is information you want to have readily available for other queries to access you might consider writing it as a view instead.
One quick note about simply selecting min(visit_id) and min(username) for your query - if you have multiple grouped rows you are likely to get the id from one visit incorrectly paired with the username from another visit.
Here is an attempt at creating a monster query for just the 3 visit types included:
SELECT a.ap_id,
a.location,
v1.visit_id AS first_visit_id,
v1.username AS first_visit_username,
v2.visit_id AS second_visit_id,
v2.username AS second_visit_username,
v3.visit_id AS third_visit_id,
v3.username AS third_visit_username
FROM (
SELECT ai.ap_id,
(SELECT MIN(v1i.visit_id) FROM visit_table v1i ON v1i.ap_id = ai.ap_id AND v1i.type = 'first') AS v1_id,
(SELECT MIN(v2i.visit_id) FROM visit_table v2i ON v2i.ap_id = ai.ap_id AND v2i.type = 'second') AS v2_id,
(SELECT MIN(v3i.visit_id) FROM visit_table v3i ON v3i.ap_id = ai.ap_id AND v3i.type = 'third') AS v3_id
FROM ap_table ai
) x
JOIN ap_table a ON x.ap_id = a.ap_id
LEFT JOIN visit_table v1 ON x.v1_id = v1.visit_id
LEFT JOIN visit_table v3 ON x.v2_id = v2.visit_id
LEFT JOIN visit_table v3 ON x.v3_id = v3.visit_id
WHERE
x.v1_id IS NOT NULL
OR x.v2_id IS NOT NULL
OR x.v3_id IS NOT NULL

Related

Getting columns from a join that is within another join to show up in output

I am using SQL Server 2016.
Here is part of my query (the only part that matters for this) with some unnecessary columns removed:
SELECT DISTINCT
S.Status,
DT.scheduledstartdate
DT.actualstartdate
DT.scheduledenddate
MAX(CASE DD.Detail WHEN 'Note' THEN DD.Value END) Notes,
MAX(CASE DD.Detail WHEN 'Late' THEN DD.[Value] END) Reason,
FROM
dbo.View_RptMod S
JOIN
[dbo].[View_Phase] P ON P.Studyd = S.Studyd
JOIN
[dbo].[View_Prop] VP ON VP.[Studyd] = S.Studyd
JOIN
(SELECT
[RowId], [actualstartdate], [scheduledstartdate],[scheduledenddate], [comments]
FROM
[dbo].[DataInfo] DT
JOIN
(SELECT [RowId], [Detail], [Value]
FROM dbo.DataDetail) DD ON DD.RowId = DT.RowId
WHERE
[scheduledstartdate] IS NOT NULL) DT ON DT.PhaseRowId = P.phaserowid
As you can see I am doing a lot of joins. If I remove
MAX(CASE DD.Detail WHEN 'Note' THEN DD.Value END) Notes,
MAX(CASE DD.Detail WHEN 'Reason' THEN DD.Value END) Reasons,
from the top part of my query that selects all the variables in the entire query, it runs without issue but I am then missing two columns that I need. I am getting an error:
The multi-part identifier "DD.Detail"/"DD.Value" could not be bound
when I run it with the MAX(CASE...) statements. I have tried running it with without "DD." (just Detail/Value alone) but same error message pops up.
The two MAX(CASE...) statements pretty much do what a pivot does whereby I get the info from a column and "create" another column based off those values.
I have already googled this error message and am wondering if maybe I just don't understand but, how do I properly call a column from a join that is inside another join statement, so that when I run the query, that column appears in the output?
Without MAX(CASE....) my output looks like this:
Status
scheduledstartdate
actualstartdate
Closed
2019-01-01
2021-01-01
Open
2021-10-31
2021-09-10
What I would like my output to look like (with MAX(CASE...) or similar) is like this:
Status
scheduledstartdate
actualstartdate
Notes
Reasons
Closed
2019-01-01
2021-01-01
Another Note
second reason
Open
2021-10-31
2021-09-10
third note
third reason
Seen people with this error message on here but I haven't found one that ends up being similar to what I am doing, unless again, I am just not understanding this properly. Have seen some say schema [dbo] might be wrong but if I remove this, I still get the error message.
You need to expose the Detail and Value columns via the DT inline table. Something like the following should do it.
SELECT DISTINCT
S.Status,
DT.scheduledstartdate
DT.actualstartdate
DT.scheduledenddate
MAX(CASE DT.Detail WHEN 'Note' THEN DT.Value END) Notes,
MAX(CASE DT.Detail WHEN 'Late' THEN DT.[Value] END) Reason,
FROM dbo.View_RptMod S
JOIN [dbo].[View_Phase] P ON P.Studyd = S.Studyd
JOIN [dbo].[View_Prop] VP ON VP.[Studyd] = S.Studyd
JOIN
(SELECT [RowId],[actualstartdate],[scheduledstartdate],[scheduledenddate],[comments], DD.Value, DD.Detail
FROM [dbo].[DataInfo] DT
JOIN (SELECT [RowId],[Detail],[Value] FROM dbo.DataDetail) DD ON DD.RowId = DT.RowId
WHERE [scheduledstartdate] IS NOT NULL) DT
ON DT.PhaseRowId = P.phaserowid
There are a number of syntax errors in your query:
The one that is causing that error is that DD is within a derived table, so is not visible to the outer scope
It is not necessary to put those joins in derived tables anyway
Missing and extra commas
DISTINCT but no GROUP BY, you must have a GROUP BY for all non-aggregated columns. Generally, DISTINCT is the wrong tool for most jobs, and its presence is a code smell
SELECT
S.Status,
DT.scheduledstartdate
DT.actualstartdate
DT.scheduledenddate
MAX(CASE DD.Detail WHEN 'Note' THEN DD.Value END) Notes,
MAX(CASE DD.Detail WHEN 'Late' THEN DD.[Value] END) Reason,
FROM
dbo.View_RptMod S
JOIN
[dbo].[View_Phase] P ON P.Studyd = S.Studyd
JOIN
[dbo].[View_Prop] VP ON VP.[Studyd] = S.Studyd
JOIN
[dbo].[DataInfo] DT ON DT.PhaseRowId = P.phaserowid
JOIN
dbo.DataDetail DD ON DD.RowId = DT.RowId
AND DT.[scheduledstartdate] IS NOT NULL
GROUP BY
S.Status,
DT.scheduledstartdate
DT.actualstartdate
DT.scheduledenddate;
You probably need to add primary keys to the GROUP BY clause also

Joining multiple CTEs

I am working on a database of a large retail store.
I have to query data from multiple tables to get numbers such as revenue, raw proceeds and compare different time periods.
Most of it is quite easy but I was struggling to work out a way of joining multiple CTEs.
I made a fiddle so you know what I am talking about.
I simplified the structure a lot and left out quite a few columns in the subqueries because they do not matter in this case.
As you can see every row in every table has country and brand in it.
The final query has to be grouped by those.
What I first tried was to FULL JOIN all the tables, but that didn't work in some cases as you can see here: SQLfiddle #1. Note the two last rows which did not group correctly.
Select Coalesce(incoming.country, revenue.country, revcompare.country,
openord.country) As country,
Coalesce(incoming.brand, revenue.brand, revcompare.brand,
openord.brand) As brand,
incoming.OrdersNet,
openord.OpenOrdersNet,
revenue.Revenue,
revenue.RawProceeds,
revcompare.RevenueCompare,
revcompare.RawProceedsCompare
From incoming
Full Join openord On openord.country = incoming.country And
openord.brand = incoming.brand
Full Join revenue On revenue.country = incoming.country And
revenue.brand = incoming.brand
Full Join revcompare On revcompare.country = incoming.country And
revcompare.brand = incoming.brand
Group By incoming.OrdersNet,
openord.OpenOrdersNet,
revenue.Revenue,
revenue.RawProceeds,
revcompare.RevenueCompare,
revcompare.RawProceedsCompare,
incoming.country,
revenue.country,
openord.country,
revcompare.country,
incoming.brand,
revenue.brand,
revcompare.brand,
openord.brand
Order By country,
brand
I then rewrote the query keeping all the CTEs. I added another CTE (basis) which UNIONs all the possible country and brand combinations and left joined on that one.
Now it works fine (check it out here -> SQLfiddle #2) but it just seems so complicated. Isn't there an easier way to achieve this? The only thing I probably won't be able to change are the CTEs as in real life they are way more complex.
WITH basis AS (
SELECT Country, Brand FROM incoming
UNION
SELECT Country, Brand FROM openord
UNION
SELECT Country, Brand FROM revenue
UNION
SELECT Country, Brand FROM revcompare
)
SELECT
basis.Country,
basis.Brand,
incoming.OrdersNet,
openord.OpenOrdersNet,
revenue.Revenue,
revenue.RawProceeds,
revcompare.RevenueCompare,
revcompare.RawProceedsCompare
FROM basis
LEFT JOIN incoming On incoming.Country = basis.Country AND incoming.Brand = basis.Brand
LEFT JOIN openord On openord.Country = basis.Country AND openord.Brand = basis.Brand
LEFT JOIN revenue On revenue.Country = basis.Country AND revenue.Brand = basis.Brand
LEFT JOIN revcompare On revcompare.Country = basis.Country AND revcompare.Brand = basis.Brand
Thank you all for your help!
Since you only work with two tables, orders and rev, consider conditional aggregation by moving WHERE conditions to CASE logic for single aggregate query. Also, consider only one CTE for all possible country/brand pairs for LEFT JOIN on the two tables.
WITH cb AS (
SELECT Country, Brand FROM orders
UNION
SELECT Country, Brand FROM rev
)
SELECT cb.Country
, cb.Brand
, SUM(o.netprice) AS OrdersNet
, SUM(CASE
WHEN o.isopen = 1
THEN o.netprice
END) AS OpenOrdersNet
, SUM(CASE
WHEN r.bdate BETWEEN '2020-12-01' AND '2020-12-31'
THEN r.netprice
END) AS Revenue
, SUM(CASE
WHEN r.bdate BETWEEN '2020-12-01' AND '2020-12-31'
THEN r.rpro
END) AS RawProceeds
, SUM(CASE
WHEN r.bdate BETWEEN '2020-11-01' AND '2020-11-30'
THEN r.netprice
END) AS RevenueCompare
, SUM(CASE
WHEN r.bdate BETWEEN '2020-11-01' AND '2020-11-30'
THEN r.rpro
END) AS RawProceedsCompare
FROM cb
LEFT JOIN orders o
ON cb.Country = o.Country
AND cb.Brand = o.Brand
LEFT JOIN rev r
ON cb.Country = r.Country
AND cb.Brand = r.Brand
GROUP BY cb.Country
, cb.Brand
SQL Fiddle

How to Get a Count of Records Using Partitioning in Oracle

I have the following query:
SELECT
F.IID,
F.E_NUM AS M_E_NUM,
MCI.E_NUM AS MCI_E_NUM,
F.C_NUM AS M_C_NUM,
MCI.C_NUM AS MCI_C_NUM,
F.ET_ID AS M_ET_ID,
EDIE.ET_ID AS ED_INDV_ET_ID,
COUNT(*) OVER (PARTITION BY F.IID) IID_COUNT
FROM FT_T F JOIN CEMEI_T MCI ON F.IID = MCI.IID
JOIN EDE_T EDE ON MCI.E_NUM = EDE.E_NUM
JOIN EDIE_T EDIE ON EDIE.IID = F.IID AND EDIE.ET_ID = EDE.ET_ID
WHERE
F.DEL_F = 'N'
AND MCI.EFF_END_DT IS NULL
AND MCI.TOS = 'BVVB'
AND EDE.PTEND_DT IS NULL
AND EDE.DEL_S = 'N'
AND EDE.CUR_IND = 'A'
AND EDIE.TAR_N = 'Y'
AND F.IID IN
(
SELECT DISTINCT IID
FROM FT_T
WHERE GROUP_ID = 'BG'
AND DEL_F = 'N'
AND (IID, E_NUM) NOT IN
(
SELECT IID, E_NUM FROM CEMEI_T
WHERE TOS = 'BVVB' AND EFF_END_DT IS NULL
)
);
I am basically grabbing information from several tables and creating a flat record of them.
Everything works accordingly except now I need to find out whether there are two records in FT_T table with identical IID's and display that count as part of the result set.
I tried to use partitioning but all the rows in the result set return a single count even though there are ones that have 2 records with identical IID's in FT_T.
The reason I initially said that I'm gathering information from several tables is due to the fact that FT_T might not have all the information I need if two records are not available for the same IID, so I have to retrieve them from other tables JOINed in the query. However, I need to know which FT_T.IID's have two records in FT_T (or greater than one).
Perhaps you need to calculate the count before the join and filtering:
SELECT . . .
FROM (SELECT F.*,
COUNT(*) OVER (PARTITION BY F.IID) as IID_CNT
FROM FT_T F
) JOIN
CEMEI_T MCI
ON F.IID = MCI.IID JOIN
EDE_T EDE
ON MCI.E_NUM = EDE.E_NUM JOIN
EDIE_T EDIE
ON EDIE.IID = F.IID AND EDIE.ET_ID = EDE.ET_ID
. . .
this is merely a comment/observation, but formatting is needed
You use of in(...) with select distinct and not in(...,...) seems complex and could be a problem if some values are NULL. I suggest you consider using EXISTS and NOT EXISTS instead. e.g.
AND EXISTS (
SELECT
NULL
FROM FT_T
WHERE F.IID = FT_T.IID
AND FT_T.GROUP_ID = 'BG'
AND FT_T.DEL_F = 'N'
AND NOT EXISTS (
SELECT
NULL
FROM CEMEI_T
WHERE FT_T.IID = CEMEI_T.IID
AND FT_T.E_NUM = CEMEI_T.E_NUM
AND CEMEI_T.TOS = 'BVVB'
AND CEMEI_T.EFF_END_DT IS NULL
)
)

Query from multiple tables with multiple where conditions in the tables

I'm trying to get a count of all speakers who are active regarding that item as well as the total of speakers who correlate to a certain item. The first LEFT JOIN for the total speakers works, but the other for ONLY the active speakers regarding that item doesn't, any help is appreciated. The SQLFiddle is here
http://sqlfiddle.com/#!3/b579d/1
But when I try to add in the portion where you would get the number of active speakers
(LEFT JOIN (SELECT COUNT (tbl_SpeakerCard_Log.SpeakerName)
WHERE tbl_Speaker_Log.Spoken = 0)
ON tbl_AgendaList.AID = tbl_SpeakerCard_Log.AID)
under the previous LEFT JOIN I get an error. I'm 100% sure the query is wrong in some form, but I'm not sure how to approach it.
*NOTE: Spoken/Active are interchangeable, I just use different wording to clarify what I'm looking for.
EDIT: This is the desired output
http://imgur.com/yP1FKxg
You can use conditional aggregation to do this:
SELECT
AgendaList.AID,
AgendaList.Item,
COUNT(SpeakerList.SPID) as SpeakerTotal,
SUM(CASE WHEN SpeakerList.Spoken = 0 THEN 1 ELSE 0 END) as ActiveSpeakers
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
GROUP BY AgendaList.AID, AgendaList.Item;
Sample SQL Fiddle
Or you could use count instead of sum (which might be clearer):
COUNT(CASE WHEN Spoken = 0 THEN Spoken END) as ActiveSpeakers
SQL FIDDLE
WITH sTotal AS (
SELECT AgendaList.AID, AgendaList.Item, COUNT( SpeakerList.SPID) as SpeakerTotal
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
GROUP BY AgendaList.AID, AgendaList.Item
),
sActive AS (
SELECT AgendaList.AID, AgendaList.Item, COUNT( SpeakerList.SPID) as SpeakerActive
FROM AgendaList
LEFT JOIN SpeakerLIST ON AgendaList.AID = SpeakerList.AID
WHERE SpeakerLIST.Spoken = 0
GROUP BY AgendaList.AID, AgendaList.Item
)
SELECT sTotal.*, sActive.SpeakerActive
FROM sTotal left join
sActive on sTotal.AID = sActive.AID

Confused in join query in SQL

The following works:
SELECT IBAD.TRM_CODE, IBAD.IPABD_CUR_QTY, BM.BOQ_ITEM_NO,
IBAD.BCI_CODE, BCI.BOQ_CODE
FROM IPA_BOQ_ABSTRCT_DTL IBAD,
BOQ_CONFIG_INF BCI,BOQ_MST BM
WHERE BM.BOQ_CODE = BCI.BOQ_CODE
AND BCI.BCI_CODE = IBAD.BCI_CODE
AND BCI.STATUS = 'Y'
AND BM.STATUS = 'Y'
order by boq_item_no;
Results:
But after joining many tables with that query, the result is confusing:
SELECT (SELECT CMN_NAME
FROM CMN_MST
WHERE CMN_CODE= BRI.CMN_RLTY_MTRL) MTRL,
RRI.RRI_RLTY_RATE AS RATE,
I.BOQ_ITEM_NO,
(TRIM(TO_CHAR(IBAD.IPABD_CUR_QTY,
'9999999999999999999999999999990.999'))) AS IPABD_CUR_QTY,
TRIM(TO_CHAR(BRI.BRI_WT_FACTOR,
'9999999999999999999999999999990.999')) AS WT,
TRIM(TO_CHAR((IBAD.IPABD_CUR_QTY*BRI.BRI_WT_FACTOR),
'9999999999999999999999990.999')) AS RLTY_QTY,
(TRIM(TO_CHAR((IBAD.IPABD_CUR_QTY*BRI.BRI_WT_FACTOR*RRI.RRI_RLTY_RATE),
'9999999999999999999999990.99'))) AS TOT_AMT,
I.TRM_CODE AS TRM
FROM
(SELECT * FROM ipa_boq_abstrct_dtl) IBAD
INNER JOIN
(SELECT * FROM BOQ_RLTY_INF) BRI
ON IBAD.BCI_CODE = BRI.BCI_CODE
INNER JOIN
(SELECT * FROM RLTY_RATE_INF) RRI
ON BRI.CMN_RLTY_MTRL = RRI.CMN_RLTY_MTRL
INNER JOIN
( SELECT IBAD.TRM_CODE, IBAD.IPABD_CUR_QTY,
BM.BOQ_ITEM_NO, IBAD.BCI_CODE, BCI.BOQ_CODE
FROM IPA_BOQ_ABSTRCT_DTL IBAD,
BOQ_CONFIG_INF BCI,BOQ_MST BM
WHERE
BM.BOQ_CODE = BCI.BOQ_CODE
AND BCI.BCI_CODE = IBAD.BCI_CODE
and BCI.status = 'Y'
and bm.status = 'Y') I
ON BRI.BCI_CODE = I.BCI_CODE
AND I.TRM_CODE = BRI.TRM_CODE
AND BRI.TRM_CODE =4
group by BRI.CMN_RLTY_MTRL, RRI.RRI_RLTY_RATE, I.BOQ_ITEM_NO,
IBAD.IPABD_CUR_QTY, BRI.BRI_WT_FACTOR, I.TRM_CODE, I.bci_code
order by BRI.CMN_RLTY_MTRL
Results:
TRM should be 11 instead of 4 in the first row.
you getting 4 because you use
AND BRI.TRM_CODE =4
if you remove this criter you can get true result
In your first query, both of the rows you've highlighted have BCI_CODE=1866.
In the second query, you are joining that result set with a number of others (which come from the same tables, which seems odd). In particular, you are joining from the subquery to another table using BCI_CODE, and from there to (SELECT * FROM ipa_boq_abstrct_dtl) IBAD. Since both of the rows from the subquery have the same BCI_CODE, they will join to the same rows in the other tables.
The quantity that you are actually displaying in the second query is from (SELECT * FROM ipa_boq_abstrct_dtl) IBAD, not from the other subquery.
Is the problem simply that you mean to select I.IPABD_CUR_QTY instead of IBAD.IPABD_CUR_QTY?
You might find this clearer if you did not reuse the same aliases for tables at multiple points in the query.