Is it possible to include a sub-query inside a select statement? - sql

I have the following three tables:
dbo_CURRENCYRATES
+-------------+---------+---------------+
|CURRENCY_ID |BUY_RATE |DATE_EFFECTIVE |
+-------------+---------+---------------+
|GBP |1.5 |01/01/2000 |
|USD |2.5 |01/01/2000 |
|EUR |0.5 |01/01/2000 |
|GBP |1.7 |01/01/2017 |
|USD |2.7 |01/01/2017 |
|EUR |0.7 |01/01/2017 |
+-------------+---------+---------------+
dbo_DISCOUNTRATES
+-------------+--------------+
|DISCOUNT |DISCOUNT_RATE |
+-------------+--------------+
|50 |0.5 |
|25 |0.25 |
|35 |0.35 |
+-------------+--------------+
dbo_CUSTOMER
+-------+---------------+---------+----------+
|ID |NAME |CURRENCY | DISCOUNT |
+-------+---------------+---------+----------+
|1 |Widgets INC |USD |50 |
|2 |Widgets GMBH |EUR |35 |
|3 |Widgets PLC |GBP |25 |
+-------+---------------+---------+----------+
and a query
CurrentExchangeRate
+--------------------------------------------+
SELECT
a.CURRENCY_ID
,a.BUY_RATE
FROM dbo_CURRENCYRATES AS t
INNER JOIN
(
SELECT
CURRENCY_ID
,MAX(DATE_EFFECTIVE) AS MaxDate
FROM dbo_CURRENCYRATES
GROUP BY dbo_CURRENCYRATES.CURRENCY_ID
) AS tm
ON (a.CURRENCY_ID = b.CURRENCY_ID)
AND (a.DATE_EFFECTIVE = b.MaxDate);
I have a select query which selects data from dbo_CUSTOMER which includes currency and discount.
SELECT
dbo_CUSTOMER.ID
,dbo_CUSTOMER.NAME
,dbo_CUSTOMER.CURRENCY_ID AS [CURRENCY]
,BUY_RATE
,dbo_CUSTOMER.DISCOUNT
,DISCOUNT_RATE
FROM dbo_CUSTOMER
WHERE ID = '1';
Is is possible to include in this query, a sub-query which lets me get the BUY_RATE for the currency (from a separate query) and the the appropriate discount rate by querying another table?
I have a separate query to get the currency rate, as I need to have the latest value for the specific currency.
So, in pseudo code, can I do this:
SELECT
dbo_CUSTOMER.ID
,dbo_CUSTOMER.NAME
,dbo_CUSTOMER.CURRENCY_ID AS [CURRENCY]
,dbo_CUSTOMER.DISCOUNT
,(
SELECT BUY_RATE
FROM CurrentExchangeRate
WHERE CurrentExchangeRate.CURRENCY_ID = [CURRENCY]
) as BUY_RATE
,(
SELECT DISCOUNT_RATE
FROM dbo_DISCOUNTRATES
WHERE dbo_DISCOUNTRATES.DISCOUNT = dbo_CUSTOMER.DISCOUNT
) as DISCOUNT_RATE
FROM dbo_CUSTOMER
WHERE ID = '1';
Is is possible to do this, firstly in a query and secondly in an access database?
Thanks

Yes, you can. Only tip you need to know in your sub queries, for what you need, is that you need to select with a TOP 1, and ORDER BY DESC on the date field.

Related

Fill Future date for null groupby sql(presto)

This might be easier than I'm thinking, but essentially want to fill in values that would be null for ID 2. Example below. Thanks.
Given Table:
|ID| food category | time |
:--:----------:-------
|1 |italian | 2021-10-01|
|1 | indian | 2021-10-23|
|1 | american| 2021-10-05|
|1 | mexican | 2021-10-07|
|1 | Chinese | 2021-10-09|
|1 | vietnamese| 2021-10-11|
|1 | thai | 2021-10-12|
|1 | Moroccan| 2021-9-01|
|1 | russian | 2021-7-01|
|1 | korean | 2021-4-30|
|1 | canadian| 2021-7-01|
|2 |italian | 2020-10-11|
|2 | indian | 2021-04-23|
|2 | american| 2021-10-25|
|2 | mexican | 2021-10-27|
I'd like to transform the table above by grouping by id and food category, but still have the time for ID 2 to be replaced with future dates(date_add('year',1, now()) for null time. Since there would be no record for ID 2 for the food categories of Chinese, Vietnamese, Thai, Moroccan, Russian, Korean, and Canadian these would be null, but I'd like them to still show in the group by the table and be placed by the date 1 year from now. Example of desired results below. Thank you for the help.
Desired Table:
|ID| food category | time |
:--:----------:-------
|1 |italian | 2021-10-01|
|1 | indian | 2021-10-23|
|1 | american| 2021-10-05|
|1 | mexican | 2021-10-07|
|1 | Chinese | 2021-10-09|
|1 | vietnamese| 2021-10-11|
|1 | thai | 2021-10-12|
|1 | Moroccan| 2021-9-01|
|1 | russian | 2021-7-01|
|1 | korean | 2021-4-30|
|1 | canadian| 2021-7-01|
|2 |italian | 2020-10-11|
|2 | indian | 2021-04-23|
|2 | american| 2021-10-25|
|2 | mexican | 2021-10-27|
|2 | Chinese | 2022-11-23|
|2 | vietnamese| 2022-11-23|
|2 | thai | 2022-11-23|
|2 | Moroccan| 2022-11-23|
|2 | russian | 2022-11-23|
|2 | korean | 2022-11-23|
|2 | canadian| 2022-11-23|
you can use following query
SELECT COALESCE(t1.ID,t2.ID) as ID,
COALESCE(t1.foodcategory,t2.foodcategory) as foodcategory,
CAST(COALESCE(t2.time,dateadd(year, 1, getdate())) AS DATE) time
FROM
(SELECT *
FROM
(SELECT foodcategory
FROM testTB
GROUP BY foodcategory) t1
JOIN
(SELECT id
FROM testTB
GROUP BY id) t2 on 1=1) t1
LEFT JOIN testTB t2 on t1.ID = t2.ID and t1.foodcategory = t2.foodcategory
or
WITH cte AS (
select distinct foodcategory from testTB
)
SELECT t2.ID,t1.foodcategory,CAST(COALESCE(t3.time,dateadd(year, 1, getdate())) AS DATE) time
FROM cte t1
FULL OUTER JOIN (
select distinct [ID] from testTB
) t2 on 1=1
left join testTB t3 on t2.ID = t3.ID and t1.foodcategory = t3.foodcategory
order by t2.id
demo in db<>fiddle
Use a CTE to gather the list of food categories first. Then gather the list of IDs.
WITH cteCat AS (
select distinct [food category] from table
)
, cteID AS (
select distinct [ID] from table
)
SELECT id.[ID], cat.[food category],
COALESCE(t.[time], dateadd(year, 1, getdate())) as [time]
FROM cteCat cat
, cteID id
LEFT OUTER JOIN table t
ON t.[ID] = id.[ID]
AND t.[food category] = cat.[food category]

How to concatenate multiple result rows of one row into one, based on FK

I am looking for a way to concatenate the result of the table into one row.
I have 4 tables;
Suppliers table
+--+----------------+----------------+
|id|name |hook_name |
+--+----------------+----------------+
|1 |724 |724 |
|2 |Air |air |
|3 |Akustik |akustik |
|4 |Almira |almira |
+--+----------------+----------------+
Supplier Offices;
(label column represents pickup/dropoff string)
+---+-----------+----------+------------+
|id |supplier_id| zip_code | label |
+---+-----------+----------------+------+
|95 |24 |25325 | 344 | <- supplier_id 24 has office location 77,98 (label pickup)
|96 |24 |9535 | 93 | <- same. only label different (label dropoff)
|97 |1 |2858 | 95 |
|98 |1 |50285 | 954 |
|99 |1 |10094 | 24 |
|100|1 |4353 | 59 |
+---+-----------+----------------+------+
OfficeLocations (Pivot table)
+------------------+-----------+
|supplier_office_id|location_id|
+------------------+-----------+
|95 |77 | <- location I want to concatenate `supplier_id = 24` (istanbul)
|96 |98 | <- location I want to concatenate `supplier_id = 24` (london)
|97 |77 |
|98 |77 |
+------------------+-----------+
Locations
+---------------+
|id |name |
+---------------+
|77 |istanbul |
|96 |berlin |
|97 |newyork |
|98 |london |
+---------------+
I want to find the office for the given location information.
I haven't manage to create custom column about label.
If I want to access the office information of locations 1 and 2 I want to get something like this;
+---------------+------------+----------------+
| supplier_id | pickup_label | dropoff_label |
+-------------+--------------+----------------+
| 95 | 344 | 93 |
+-------------+--------------+----------------+
I've been able to get this far right now with my Postgresl SQL.
SELECT supplier_offices.id,
supplier_offices.supplier_id
FROM "supplier_offices"
INNER JOIN suppliers on supplier_offices.supplier_id = suppliers.id
INNER JOIN office_locations on supplier_offices.id = office_locations.supplier_office_id
AND ("office_locations"."location_id" IN (77, 98)
This code works if I understand what you mean. This code works if I understand what you mean. Of course for sql, but with a little change, I think it will work in Postgresl SQL as well
select supplier_offices.id as supplier_id, max(supplier_offices.label) as pickup_label, min(supplier_offices.label) as dropoff_label
from supplier_offices
inner join suppliers on supplier_offices.supplier_id = suppliers.id
inner join office_locations on supplier_offices.id = office_locations.supplier_office_id
where office_locations.location_id in (77,98)
group by supplier_id
I think you want some sort of aggregation. It is entirely unclear hoe pickup locations are identified versus drop off. But, something like this:
SELECT so.supplier_id,
ARRAY_AGG(location_id) FILTER (WHERE so.label in (344)) as pickup,
ARRAY_AGG(location_id) FILTER (WHERE so.label not in (344)) as dropoff
FROM "supplier_offices" so JOIN
office_locations ol
ON so.id = ol.supplier_office_id AND
ol.location_id IN (77, 98)
GROUP BY so.supplier_id;

Update a column value within a SELECT query

I have a complicated SQL question.
Can we update a column within a SELECT query? Example:
Consider this table:
|ID |SeenAt |
----------------
|1 |20 |
|1 |21 |
|1 |22 |
|2 |70 |
|2 |80 |
I want a SELECT Query that gives for each ID when was it seen for the first time. And when did it seen 'again':
|ID |Start |End |
---------------------
|1 |20 |21 |
|1 |20 |22 |
|1 |20 |22 |
|2 |70 |80 |
|2 |70 |80 |
First, both columns Start and End would have the same value, but when a second row with the same ID is seen we need to update its predecessor to give End the new SeenAt value.
I succeeded to create the Start column, I give the minimum SeenAt value per ID to all IDs. But I can't find a way to update the End column everytime.
Don't mind the doubles, I have other columns that change in every new row
Also, I am working in Impala but I can use Oracle.
I hope that I have been clear enough. Thank you
You could use lead() and nvl():
select id, min(seenat) over (partition by id) seen_start,
nvl(lead(seenat) over (partition by id order by seenat), seenat) seen_end
from t
demo
Start is easy just the MIN of the GROUP
End you need to find the MIN after the SeenAt and in case you don't find it then the current SeenAt
SQL DEMO
SELECT "ID",
(SELECT MIN("SeenAt")
FROM Table1 t2
WHERE t1."ID" = t2."ID") as "Start",
COALESCE(
(SELECT MIN("SeenAt")
FROM Table1 t2
WHERE t1."ID" = t2."ID"
AND t1."SeenAt" < t2."SeenAt")
, t1."SeenAt"
) as End
FROM Table1 t1
OUTPUT
| ID | START | END |
|----|-------|-----|
| 1 | 20 | 21 |
| 1 | 20 | 22 |
| 1 | 20 | 22 |
| 2 | 70 | 80 |
| 2 | 70 | 80 |
you seem to need min() analytic function with a self-join:
select distinct t1.ID,
min(t1.SeenAt) over (partition by t1.ID order by t1.ID) as "Start",
t2.SeenAt as "End"
from tab t1
join tab t2 on t1.ID=t2.ID and t1.SeenAt<=t2.SeenAt
order by t2.SeenAt;
Demo

Max value from joined table

I have two tables:
Operations (op_id,super,name,last)
Orders (or_id,number)
Operations:
+--------------------------------+
|op_id| super| name | last|
+--------------------------------+
|1 1 OperationXX 1 |
|2 1 OperationXY 2 |
|3 1 OperationXC 4 |
|4 1 OperationXZ 3 |
|5 2 OperationXX 1 |
|6 3 OperationXY 2 |
|7 4 OperationXC 1 |
|8 4 OperationXZ 2 |
+--------------------------------+
Orders:
+--------------+
|or_id | number|
+--------------+
|1 2UY |
|2 23X |
|3 xx2 |
|4 121 |
+--------------+
I need query to get table:
+-------------------------------------+
|or_id |number |max(last)| name |
|1 2UY 4 OperationXC|
|2 23X 1 OperationXX|
|3 xx2 2 OperationXY|
|4 121 2 OperationXZ|
+-------------------------------------+
use corelared subquery and join
select o.*,a.last,a.name from
(
select super,name,last from Operations from operations t
where last = (select max(last) from operations t2 where t2.super=t.super)
) a join orders o on t1.super =o.or_id
you can use row_number as well
with cte as
(
select * from
(
select * , row_number() over(partition by super order by last desc) rn
from operations
) tt where rn=1
) select o.*,cte.last,cte.name from Orders o join cte on o.or_id=cte.super
SELECT Orders.or_id, Orders.number, Operations.name, Operations.last AS max
FROM Orders
INNER JOIN Operations on Operations.super = Orders.or_id
GROUP BY Orders.or_id, Orders.number, Operations.name;
I don't have a way of testing this right now, but I think this is it.
Also, you didn't specify the foreign key, so the join might be wrong.

Better way of writing my SQL query with conditional group by

Here's my data
|vendorname |total|
---------------------
|Najla |10 |
|Disney |20 |
|Disney |10 |
|ToysRus |5 |
|ToysRus |1 |
|Gap |1 |
|Gap |2 |
|Gap |3 |
|Najla |2 |
Here's the resultset I want
|vendorname |grandtotal|
---------------------
|Disney |30 |
|Gap |6 |
|ToysRus |6 |
|Najla |2 |
|Najla |10 |
If the vendorname = 'Najla' I want individual rows with their respective total otherwise I would like to group them and return a sum of their totals.
This is my query--
select *
from
(
select vendorname, sum(total) grandtotal
from vendor
where vendorname<>'Najla'
group by vendorname
union all
select vendorname, total grandtotal
from vendor
where vendorname='Najla'
) A
I was wondering if there's a better way to write this query instead of repeating it twice and performing a union. Is there a condensed way to group some rows "conditionally".
Honestly, I think the union all version is going to be the best performing and easiest to read option if it has appropriate indexes.
You could, however, do something like this (assuming you have a unique id on your table):
select vendorname, sum(total) grandtotal
from t
group by
vendorname
, case when vendorname = 'Najla' then id else null end
rextester demo: http://rextester.com/OGZQ33364
returns
+------------+------------+
| vendorname | grandtotal |
+------------+------------+
| Disney | 30 |
| Gap | 6 |
| ToysRus | 6 |
| Najla | 10 |
| Najla | 2 |
+------------+------------+