How select from query inside case in select - sql

I have following query
SELECT DISTINCT v.id,
( CASE
WHEN date IS NULL THEN v.o_id
ELSE (SELECT os
FROM (SELECT o_id1 os
FROM tablet t2
WHERE t2.vso_id = v.id
ORDER BY date ASC)
WHERE rownum = 1)
END ) AS p_o,
v.o_id AS k_o
FROM tablev v
LEFT JOIN tablet t
ON v.id = t.v_id;
Here is what I need:
TableV has distinct v.id values. I need join that table with another (tableT) which has many records for same v.id and then for each distinct v.id:
select O_ID from V
if record for v.id in tableT does not exists(date is null) then
select O_ID from V (same as above)else select o_id1 from T with
minimum date
Also, aditional, is if possible to join o_id from 1. and o_id/o_id1 from2 to another table and write person's name?
I would like, instead of o_id, for each v.id to join selected o_id to another table(tableO) and select name.
So, I need these columns in result: V.id, p_o, k_o
But it would be really great if it looks like: v.id, p_o.name, k_o.name

To answer your first question, assuming that t.vso_id and t.v_id are the same thing (otherwise I might have misunderstood your question), try this query (I use another DBMS, so you might need to adjust the call to COALESCE a little):
SELECT v.id, COALESCE(t.o_id1, v.o_id), v.o_id
FROM tableV v
LEFT JOIN (
(SELECT vso_id, min(date) AS min_date FROM tableT GROUP BY vso_id) min_dates
JOIN tableT t
ON min_dates.vso_id = t.vso_id AND min_dates.min_date = t.date
) ON v.id = t.v_id;
Here's a breakdown:
(SELECT vso_id, min(date) AS min_date FROM tableT GROUP BY vso_id) AS min_dates
selects a smallest date for each vso_id.
(SELECT vso_id, min(date) AS min_date FROM tableT GROUP BY vso_id) AS min_dates
JOIN tableT t
ON min_dates.vso_id = t.vso_id AND min_dates.min_date = t.date
selects one row per vso_id, and that row will correspond to the smallest date that that vso_id has.
Finally, I join tableV to that query. COALESCE(t.o_id1, v.o_id) will return t.o_id if it is not NULL, v.o_id otherwise. You can use CASE instead, but I find COALESCE to be a better fit for this particular purpose.
To answer your second question, you can then JOIN against the table with names like this:
JOIN table_with_names twn1 ON twn1.id = COALESCE(t.o_id1, v.o_id)
JOIN table_with_names twn2 ON twn2.id = v.o_id
and project twn1.name and twn2.name.

Related

New SQL user - I'm trying to extract the latest record in my query. Accountant new to SQL

For every AbsenceBalance.AbsenceTypesUID I want to return the latest record AbsenceBalance.BalanceTime for each AbsenceBalance.EmployeeUID
I have tried select max but it only returns the most recent entry for the entire table and not by AbsenceBalance.AbsenceTypesUID or AbsenceBalance.EmployeeUID
This is my query
SELECT TOP (1000)
AbsenceBalance.[UID],
AbsenceBalance.BalanceTime,
AbsenceBalance.AbsenceTypesUID,
AbsenceBalance.Mins,
Employee.FullName,
Employee.FirstName,
Employee.LastName,
AbsenceBalance.EmployeeUID,
absencetypes.LongName
from [RiteqDB].[dbo].[AbsenceBalance]
LEFT JOIN [RiteqDB].[dbo].Employee on AbsenceBalance.EmployeeUID = Employee.UID
LEFT JOIN [RiteqDB].[dbo].AbsenceTypes on absencebalance.AbsenceTypesUID = absencetypes.UID
where AbsenceBalance.[UID] = (select max (AbsenceBalance.[UID]) from [RiteqDB].[dbo].[AbsenceBalance] where AbsenceBalance.AbsenceTypesUID = AbsenceBalance.AbsenceTypesUID)
--where Select Max(v) from (values (AbsenceBalance.BalanceTime)
order by FullName, AbsenceTypesUID
It sounds like you might need a group by link, then either use an inner select in a where (like you have) or use this with an inner join.
SELECT
Max(AbsenceBalance.[UID]),
AbsenceBalance.AbsenceTypesUID,
AbsenceBalance.EmployeeUID,
from [RiteqDB].[dbo].[AbsenceBalance]
GROUP BY AbsenceTypesUID, EmployeeUID
;with cte as (
SELECT TOP (1000) AB.[UID]
,AB.BalanceTime
,AB.AbsenceTypesUID
,AB.Mins
,E.FullName
,E.FirstName
,E.LastName
,AB.EmployeeUID
,AT.LongName
, ROW_NUMBER() OVER(PARTITION BY AB.[UID], AB.EmployeeUID order by AB.BalanceTime DESC) AS RUN
FROM [RiteqDB].[dbo].[AbsenceBalance] AB
LEFT JOIN [RiteqDB].[dbo].Employee E ON AB.EmployeeUID = E.UID
LEFT JOIN [RiteqDB].[dbo].AbsenceTypes AT ON AB.AbsenceTypesUID = AT.UID
)
select * from cte
where RUN = 1
In your where condition your comparing with itself that might be the issue.
select max (AbsenceBalance.[UID]) from [RiteqDB].[dbo].[AbsenceBalance]
where AbsenceBalance.AbsenceTypesUID = AbsenceBalance.AbsenceTypesUID
following is wrong
AbsenceBalance.AbsenceTypesUID = AbsenceBalance.AbsenceTypesUID

Select min value from Tables using (sub Select )

I am trying to select a specific row from the tables. All are good, but the last select min is not working. So I need the min Leg Number after I make the whole selection.
SELECT cashier.* , legs.* ,cashier.id as cashier,
cashier.cashierNumber as cashierNum ,cashier.fullName as cashier
FROM myTable
INNER JOIN legs ON main.main= legs.legMain
INNER JOIN cashier ON legs.cashier = cashier.id
WHERE legs.RRZZFrom ='RR'
AND legs.LegNumber = (SELECT Min(legs.LegNumber) FROM legs)
where legs.RRZZFrom ='RR' and legs.LegNumber in (select min(legs.LegNumber) from legs)
you might want to try using cte.
with cte as (
select min(LegNumber) as minLegNum from legs
)SELECT cashier.* , legs.* ,cashier.id as cashier,
cashier.cashierNumber as cashierNum ,cashier.fullName as cashier
FROM myTable
INNER JOIN legs ON main.main= legs.legMain
INNER JOIN cashier ON legs.cashier = cashier.id
INNER JOIN cte c on c.minLegNum = legs.LegNumber
WHERE legs.RRZZFrom ='RR'
Are you looking for the MIN leg number only from the selected data? In that case, something like this:
WITH Details
AS
(
SELECT cashier.* , legs.* ,cashier.id as cashier,
cashier.cashierNumber as cashierNum ,cashier.fullName as cashier
FROM myTable
INNER JOIN legs ON main.main= legs.legMain
INNER JOIN cashier ON legs.cashier = cashier.id
WHERE legs.RRZZFrom ='RR'
)
SELECT d.*
FROM Details AS d
WHERE d.LegNumber = (SELECT MIN(d2.LegNumber) FROM Details AS d2);
Not sure if you'd need to alias any other columns there, as I don't know the table layout.
Try this:
WITH DataSource AS
(
SELECT cashier.*
,legs.*
,cashier.id as cashier
,cashier.cashierNumber as cashierNum
,cashier.fullName as cashier
,MIN(legs.LegNumber) OVER() AS [MinLegNUmber]
FROM myTable
INNER JOIN legs
ON main.main= legs.legMain
INNER JOIN cashier
ON legs.cashier = cashier.id
WHERE legs.RRZZFrom ='RR'
)
SELECT *
FROM DataSource
WHERE LegNumber = [MinLegNUmber];
The idea is to use a OVER() clause to calculate the minimum value after the selection for each row:
MIN(legs.LegNumber) OVER()
Then in outer query to return only the rows which are matching this value.
The OVER clause is particular powerful syntax which allows to perform operations (ranking, aggregations) over given set of values. The OVER() syntax means the whole entity.

access - row_number function?

I had this query, which gives me the desired results on postgres
SELECT
t.*,
ROW_NUMBER() OVER (PARTITION BY t."Internal_reference", t."Movement_date" ORDER BY t."Movement_date") AS "cnt"
FROM (SELECT
"Internal_reference",
MAX("Movement_date") AS maxtime
FROM dw."LO-D4_Movements"
GROUP BY "Internal_reference") r
INNER JOIN dw."LO-D4_Movements" t
ON t."Movement_date" = r.maxtime
AND t."Internal_reference" = r."Internal_reference"
Issue is I have to translate the query above on Access where the analytical function does not exist ...
I used this answer to build the query below
SELECT
t."Internal_reference",
t.from_code,
t.to_code,
t."Movement_date",
t.shipment_number,
t."PO_number",
t."Quantity",
t."Movement_value",
t."Site",
t."Import_date",
COUNT(*) AS "cnt"
FROM (
SELECT "Internal_reference",
MAX("Movement_date") AS maxtime
FROM dw."LO-D4_Movements"
GROUP BY "Internal_reference") r
LEFT OUTER JOIN dw."LO-D4_Movements" t
ON t."Movement_date" = r.maxtime AND t."Internal_reference" = r."Internal_reference"
GROUP BY
t.from_code,
t.to_code,
t."Movement_date",
t.shipment_number,
t."PO_number",
t."Quantity",
t."Movement_value",
t."Site",
t."Import_date",
t."Internal_reference"
ORDER BY t.from_code
Issue is I only have 1 in the cnt column.
I tried to tweak it by removing the internal_reference (see below)
SELECT
t.from_code,
t.to_code,
t."Movement_date",
t.shipment_number,
t."PO_number",
t."Quantity",
t."Movement_value",
t."Site",
t."Import_date",
COUNT(*) AS "cnt"
FROM (
SELECT "Internal_reference",
MAX("Movement_date") AS maxtime
FROM dw."LO-D4_Movements"
GROUP BY "Internal_reference") r
LEFT OUTER JOIN dw."LO-D4_Movements" t
ON t."Movement_date" = r.maxtime AND t."Internal_reference" = r."Internal_reference"
GROUP BY
t.from_code,
t.to_code,
t."Movement_date",
t.shipment_number,
t."PO_number",
t."Quantity",
t."Movement_value",
t."Site",
t."Import_date"
ORDER BY t.from_code
However, the results are even worse. The cnt is growing but it gives me the wrong cnt
Any help are more than welcome as I'm slow losing my sanity.
Thanks
Edit: Please find the sqlfiddle
I think Gordon-Linoff's code is close to what you want, but there are some typos I couldn't correct without a rewrite, so here's my attempt
SELECT
t1.Internal_reference,
t1.Movement_date,
t1.PO_Number as Combination_Of_Columns_Which_Make_This_Unique,
t1.Other_columns,
Count(1) AS Cnt
FROM
([LO-D4_Movements] AS t1
INNER JOIN [LO-D4_Movements] AS t2 ON
t1.Internal_reference = t2.Internal_reference AND
t1.Movement_date = t2.Movement_date)
INNER JOIN (
SELECT
t3.Internal_reference,
MAX(t3.Movement_date) AS Maxtime
FROM
[LO-D4_Movements] AS t3
GROUP BY
t3.Internal_reference
) AS r ON
t1.Internal_reference = r.Internal_reference AND
t1.Movement_date = r.Maxtime
WHERE
t1.PO_Number>=t2.PO_Number
GROUP BY
t1.Internal_reference,
t1.Movement_date,t1.PO_Number,
t1.Other_columns
ORDER BY
t1.Internal_reference,
t1.Movement_date,
Count(1);
In addition to within the max(movement_date) subquery, the main table is brought in twice. One version is the one for showing in your results, the other is for counting records to generate the sequence numbers.
Gordon said you need a unique id column for each row. And that's true if by "column" you mean to include derived columns also. Also it only needs to be unique within any combination of "internal_reference" and "Movement_date".
I've assumed, perhaps wrongly, that PO_Number will suffice. If not, concatenate with that (and some delimeters) other fields which will make it unique. The where clause will need updating to compare t1 and t2 for the "Combination of Columns which make this unique".
If, there is no appropriate combination available, I'm not sure it can be done without VBA and/or temp tables as The-Gambill suggested.
This is a real pain in MS Access, as far as I know. One method is a correlated subquery, but you need a unique id column on each row:
SELECT t.*,
(SELECT COUNT(*)
FROM (SELECT "Internal_reference", MAX("Movement_date") AS maxtime
FROM dw."LO-D4_Movements"
GROUP BY "Internal_reference"
) as t2
WHERE t2."Internal_reference" AND t."Internal_reference" AND
t2."Movement_date" = t."Movement_date" AND
t2.?? <= t.??
) as cnt
FROM (SELECT "Internal_reference", MAX("Movement_date") AS maxtime
FROM dw."LO-D4_Movements"
GROUP BY "Internal_reference"
) r INNER JOIN
dw."LO-D4_Movements" t
ON t."Movement_date" = r.maxtime AND
t."Internal_reference" = r."Internal_reference";
The ?? is for the id or creation date or something to allow the counting of rows.

How to find a record in another table recorded at the nearest time in a subquery aggregate

select sl.*,
(select pnd_invoiceno
from PINVDET
where PND_INVNO = sl.invno and
abs(DATEDIFF(ss, pnd_date, sl.adjustedon)) =
(select min(abs(DATEDIFF(ss, pnd_date, sl.adjustedon)))
from PINVDET
where pnd_invno = sl.invno))
from vwstocklog sl where sl.invno in (select invno from vwStockDiff)
order by sl.invno, sl.adjustedon
When I run the above query I get the error:
Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.
I understand it's saying that the expression min(abs(DATEDIFF(ss, pnd_date, sl.adjustedon))) is the problem because it references sl.adjusted on in the min() aggregate, and it cannot do so unless it's the only column referenced in the aggregate expression. What I'm not sure about is how to go about fixing it.
What I'm attempting to do here is find the pnd_invoiceno value on the record in pinvdet with the pnd_date value nearest to sl.adjustedon for the same item (and I recognize that this has the possibility of linking to multiple records).
Any ideas on how I might adjust this query to accomplish that?
Second attempt (filter first):
With x as (
select
sl.invno,
sl.adjustedon,
p.pnd_invoiceno,
rank() over (
partition by sl.invno
order by abs(datediff(ss, p.pnd_date, sl.adjustedon))
) rk
from
vwstocklog sl
inner join
pinvdet p
on p.pnd_invno = sl.invno
Where
Exists (
Select
'x'
From
vwStockDiff sd
Where
sl.invno = sd.invno
)
)
Select
x.invno,
x.adjustedon,
x.pnd_invoiceno
From
x
Where
x.rk = 1
order by
x.invno,
x.adjustedon
First attempt:
With x as (
select
sl.invno,
sl.adjustedon,
p.pnd_invoiceno,
rank() over (
partition by sl.invno
order by abs(datediff(ss, p.pnd_date, sl.adjustedon))
) rk
from
vwStockDiff sd
inner join
vwstocklog sl
on sl.invno = sd.invno
inner join
pinvdet p
on p.pnd_invno = sl.invno
)
Select
x.invno,
x.adjustedon,
x.pnd_invoiceno
From
x
Where
x.rk = 1
order by
x.invno,
x.adjustedon
If you happen to have two times that are an equal distance away, this will return a row for both. Replace rank() with row_number() if you'd only like 1.
SQLFiddle doesn't seem to be working at the moment, so I can't test this. There's probably syntax errors.

Limit join to one row

I have the following query:
SELECT sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount, 'rma' as
"creditType", "Clients"."company" as "client", "Clients".id as "ClientId", "Rmas".*
FROM "Rmas" JOIN "EsnsRmas" on("EsnsRmas"."RmaId" = "Rmas"."id")
JOIN "Esns" on ("Esns".id = "EsnsRmas"."EsnId")
JOIN "EsnsSalesOrderItems" on("EsnsSalesOrderItems"."EsnId" = "Esns"."id" )
JOIN "SalesOrderItems" on("SalesOrderItems"."id" = "EsnsSalesOrderItems"."SalesOrderItemId")
JOIN "Clients" on("Clients"."id" = "Rmas"."ClientId" )
WHERE "Rmas"."credited"=false AND "Rmas"."verifyStatus" IS NOT null
GROUP BY "Clients".id, "Rmas".id;
The problem is that the table "EsnsSalesOrderItems" can have the same EsnId in different entries. I want to restrict the query to only pull the last entry in "EsnsSalesOrderItems" that has the same "EsnId".
By "last" entry I mean the following:
The one that appears last in the table "EsnsSalesOrderItems". So for example if "EsnsSalesOrderItems" has two entries with "EsnId" = 6 and "createdAt" = '2012-06-19' and '2012-07-19' respectively it should only give me the entry from '2012-07-19'.
SELECT (count(*) * sum(s."price")) AS amount
, 'rma' AS "creditType"
, c."company" AS "client"
, c.id AS "ClientId"
, r.*
FROM "Rmas" r
JOIN "EsnsRmas" er ON er."RmaId" = r."id"
JOIN "Esns" e ON e.id = er."EsnId"
JOIN (
SELECT DISTINCT ON ("EsnId") *
FROM "EsnsSalesOrderItems"
ORDER BY "EsnId", "createdAt" DESC
) es ON es."EsnId" = e."id"
JOIN "SalesOrderItems" s ON s."id" = es."SalesOrderItemId"
JOIN "Clients" c ON c."id" = r."ClientId"
WHERE r."credited" = FALSE
AND r."verifyStatus" IS NOT NULL
GROUP BY c.id, r.id;
Your query in the question has an illegal aggregate over another aggregate:
sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount
Simplified and converted to legal syntax:
(count(*) * sum(s."price")) AS amount
But do you really want to multiply with the count per group?
I retrieve the the single row per group in "EsnsSalesOrderItems" with DISTINCT ON. Detailed explanation:
Select first row in each GROUP BY group?
I also added table aliases and formatting to make the query easier to parse for human eyes. If you could avoid camel case you could get rid of all the double quotes clouding the view.
Something like:
join (
select "EsnId",
row_number() over (partition by "EsnId" order by "createdAt" desc) as rn
from "EsnsSalesOrderItems"
) t ON t."EsnId" = "Esns"."id" and rn = 1
this will select the latest "EsnId" from "EsnsSalesOrderItems" based on the column creation_date. As you didn't post the structure of your tables, I had to "invent" a column name. You can use any column that allows you to define an order on the rows that suits you.
But remember the concept of the "last row" is only valid if you specifiy an order or the rows. A table as such is not ordered, nor is the result of a query unless you specify an order by
Necromancing because the answers are outdated.
Take advantage of the LATERAL keyword introduced in PG 9.3
left | right | inner JOIN LATERAL
I'll explain with an example:
Assuming you have a table "Contacts".
Now contacts have organisational units.
They can have one OU at a point in time, but N OUs at N points in time.
Now, if you have to query contacts and OU in a time period (not a reporting date, but a date range), you could N-fold increase the record count if you just did a left join.
So, to display the OU, you need to just join the first OU for each contact (where what shall be first is an arbitrary criterion - when taking the last value, for example, that is just another way of saying the first value when sorted by descending date order).
In SQL-server, you would use cross-apply (or rather OUTER APPLY since we need a left join), which will invoke a table-valued function on each row it has to join.
SELECT * FROM T_Contacts
--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
-- CROSS APPLY -- = INNER JOIN
OUTER APPLY -- = LEFT JOIN
(
SELECT TOP 1
--MAP_CTCOU_UID
MAP_CTCOU_CT_UID
,MAP_CTCOU_COU_UID
,MAP_CTCOU_DateFrom
,MAP_CTCOU_DateTo
FROM T_MAP_Contacts_Ref_OrganisationalUnit
WHERE MAP_CTCOU_SoftDeleteStatus = 1
AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID
/*
AND
(
(#in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo)
AND
(#in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom)
)
*/
ORDER BY MAP_CTCOU_DateFrom
) AS FirstOE
In PostgreSQL, starting from version 9.3, you can do that, too - just use the LATERAL keyword to achieve the same:
SELECT * FROM T_Contacts
--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
LEFT JOIN LATERAL
(
SELECT
--MAP_CTCOU_UID
MAP_CTCOU_CT_UID
,MAP_CTCOU_COU_UID
,MAP_CTCOU_DateFrom
,MAP_CTCOU_DateTo
FROM T_MAP_Contacts_Ref_OrganisationalUnit
WHERE MAP_CTCOU_SoftDeleteStatus = 1
AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID
/*
AND
(
(__in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo)
AND
(__in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom)
)
*/
ORDER BY MAP_CTCOU_DateFrom
LIMIT 1
) AS FirstOE
Try using a subquery in your ON clause. An abstract example:
SELECT
*
FROM table1
JOIN table2 ON table2.id = (
SELECT id FROM table2 WHERE table2.table1_id = table1.id LIMIT 1
)
WHERE
...