Select max date from table when joining multiple tables - sql

I am trying to write a query to retrieve the max dispatched date and also bringing in fields from multiple tables. Im using the fields from other tables since the table that contains the dispatched date does not contain any of the information that I am looking for. (hope that makes sense) This is the code that I am currently using and it is not working:
Here is what I was getting before I started playing with the code:
This is what I am trying to retrieve:
select a.order_num,
LAB_USER.NAME as ASGN_TECH,
(select max(asn_assignment.dispatched)
from asn_assignment
where a.for_order=asn_assignment.for_order) as "max date"
from asn_assignment, ord_order_state a, lab_user, lab_resource, asn_assignment
where
LAB_USER.USER_ID = LAB_RESOURCE.FOR_USER
AND LAB_RESOURCE.RESOURCE_ID = ASN_ASSIGNMENT.FOR_RESOURCE
and order_num in ('800000194709',
'800000213722',
'800006513931',
'800006525705')

Perhaps fix it like this first while still don't catch you point:
select a.order_num,
LAB_USER.NAME as ASGN_TECH,
(select max(asn_assignment.dispatched)
from asn_assignment
where a.for_order=asn_assignment.for_order) as "max date"
from asn_assignment, ord_order_state a, lab_user, lab_resource
where a.for_order=asn_assignment.for_order
and LAB_USER.USER_ID = LAB_RESOURCE.FOR_USER
AND LAB_RESOURCE.RESOURCE_ID = ASN_ASSIGNMENT.FOR_RESOURCE
and order_num in ('800000194709',
'800000213722',
'800006513931',
'800006525705')

You can rewrite the query through use of DENSE_RANK() analytic function such as
SELECT order_num, asgn_tech, dispatched
FROM
(
SELECT o.order_num,
lu.name as asgn_tech,
a.dispatched,
DENSE_RANK() OVER (PARTITION BY o.order_num
ORDER BY a.dispatched DESC) AS dr
FROM asn_assignment a
JOIN ord_order_state o
ON o.for_order = a.for_order
JOIN lab_resource lr
ON lr.resource_id = a.for_resource
JOIN lab_user lu
ON lu.user_id = lr.for_user
WHERE order_num IN (800000194709, 800000213722, 800006513931, 800006525705)
)
WHERE dr = 1
where
prefer using SQL-92 standard which contains JOIN keyword rather
than former standards in which the tables are seperated by commas
properly alias tables with one or two (maybe three depending on the
tables' names) letters conforming to initial letters of their names or underscore seperated substrings within the names
get rid of quotes wrapping up the values within the list after IN
operator considering data type of order_num is numeric
If your database is 12c+, then you can also prefer using the below query without need of a subquery such as
SELECT o.order_num,
lu.name as asgn_tech,
a.dispatched
FROM asn_assignment a
JOIN ord_order_state o
ON o.for_order = a.for_order
JOIN lab_resource lr
ON lr.resource_id = a.for_resource
JOIN lab_user lu
ON lu.user_id = lr.for_user
WHERE order_num IN (800000194709, 800000213722, 800006513931, 800006525705)
ORDER BY DENSE_RANK() OVER (PARTITION BY o.order_num ORDER BY a.dispatched DESC)
FETCH NEXT 1 ROW WITH TIES

Related

SQL add a column with COUNT(*) to my query

I need to add a column with the content of this query :
SELECT COUNT(*) FROM account_subscriptiongroups WHERE account_subscriptiongroups.active = true AND account_subscriptiongroups.user_id = account_user.id
to this query :
SELECT
account_user.id as user_id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id as sub_group_id,
account_adminaction.description,
account_adminaction.id as admin_action_id,
account_adminaction.created_on as subscription_ended_on
FROM
account_adminaction
LEFT JOIN
account_user ON account_user.id = account_adminaction.user_id
LEFT JOIN
account_subscriptiongroup ON account_adminaction.sub_group_id = account_subscriptiongroup.id
WHERE
account_adminaction.created_on >= '2021-04-07' AND account_adminaction.created_on <= '2021-04-13' AND
((account_adminaction.description LIKE 'Arrêt de l''abonnement%') OR (account_adminaction.description LIKE 'L''utilisateur a arrêté%'))
ORDER BY
subscription_ended_on
I tried adding a LEFT JOIN like that:
LEFT JOIN
account_subscriptiongroup all_sg ON account_user.id = account_subscriptiongroup.user_id
with this line in my WHERE statement :
AND all_sg.active = true
and this line in my SELECT :
COUNT(all_sg.id)
but I get an error :
ERROR: column "account_user.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 2: account_user.id as user_id, account_user.email, account_us...
^
I don't understand how I could perform this action properly
To count something, you need to specify a group where that count applies.
So every column that you select (and is not used in an aggregate function, like COUNT or SUM), you need to mention in the GROUP BY clause.
Or to put it the other way around: the non-aggregate columns must apply to all rows that are contained in that particular COUNT.
So between the WHERE and ORDER BY clauses, add a GROUP BY clause:
GROUP BY account_user.id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id,
account_adminaction.description,
account_adminaction.id,
account_adminaction.created_on
If, on the other hand, you want a count from a different table, you can add a sub-select:
SELECT
account_user.id as user_id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id as sub_group_id,
account_adminaction.description,
account_adminaction.id as admin_action_id,
account_adminaction.created_on as subscription_ended_on,
(SELECT COUNT(*)
FROM account_subscriptiongroups
WHERE account_subscriptiongroups.active = true
AND account_subscriptiongroups.user_id = account_user.id) AS groupcount
FROM
account_adminaction
LEFT JOIN
account_user ON account_user.id = account_adminaction.user_id
You can left join to to a derived table that does the grouping and counting:
SELECT au.id as user_id, au.email, au.first_name, au.last_name, au.phone,
asg.id as sub_group_id,
ad.description,
ad.id as admin_action_id,
ad.created_on as subscription_ended_on,
asgc.num_groups
FROM account_adminaction ad
LEFT JOIN account_user au ON au.id = ad.user_id
LEFT JOIN account_subscriptiongroups asg on ON ad.sub_group_id = asg.id
LEFT JOIN (
SELECT user_id, count(*) as num_groups
FROM account_subscriptiongroups ag
WHERE ag.active
GROUP by user_id
) asgc on asgc.user_id = au.id
WHERE ad.created_on >= '2021-04-07'
AND ad.created_on <= '2021-04-13'
AND ((ad.description LIKE 'Arrêt de l''abonnement%') OR (ad.description LIKE 'L''utilisateur a arrêté%'))
ORDER BY subscription_ended_on
It's not entirely clear to me, what you are trying to count, but another option (most probably slower) could be to use a window function combined with a filter clause:
count(*) filter (where asg.active) over (partition by asg.user_id) as num_groups
EDIT: my answer is the same as submitted by a_horse_with_no_name
Two answers, a literal one just solving the problem you posed, and then another one questioning whether what you asked for is really what you want.
Simple answer: modify your desired query to add user_id to the Select and remove user_id from the Where clause. Now you have a table that can be left-joined to the rest of your larger query.
...
Left Join (Select user_id, count(*) as total_count
From account_subscriptiongroup
Where account_subscriptiongroups.active = true
Group By user_id) Z
On Z.user_id=account_user.id
I question whether this count is what you really want here. This counts every account_subscriptiongroup entry for all time but only the active ones. Your larger query brings back inactive as well as active records, so if your goal is to create a denominator for a percentage, you are mixing 'apples and oranges' here.
If you decide you want a total by user of the records in your query instead, then you can add one more element to your larger query without adding any more tables. Use a windowing function like this:
Select ..., Sum(Case When account_subscriptiongroup.active Then 1 else 0 End) Over (Group By account_user.id) as total count
This just counts the records within the date range and having the desired actions.

By store (number), list the maximum number of store visits made by a customer

The three tables that I'm linking are item_scan_fact, member_dimension and store_dimension. So far this is what I have:
SELECT
store_dimension.store_number,
member_dimension.member_number
COUNT (item_scan_fact.visit_number) AS NumVisits
FROM
member_dimension,
item_scan_fact
INNER JOIN store_dimension
ON item_scan_fact.member_key = member_dimension.member_key
AND item_scan_fact.store_key = store_dimension.store_key
GROUP BY
store_dimension.store_number,
member_dimension.member_number, NumVisits;
On the surface it appears solvable with a couple Common Table Expressions
Does this help point you in the right direction?
WITH s1 -- JJAUSSI: Find the visit_number_count by member_key and store_key
AS
(SELECT isf.member_key
,isf.store_key
-- JJAUSSI: DISTINCT resolves a potential 1:N (one to many) relationship here
,COUNT( DISTINCT isf.visit_number) AS visit_number_count
FROM item_scan_fact isf
GROUP BY isf.member_key
,isf.store_key),
s2 -- JJAUSSI: Find the visit_number_count_max by member_key
AS
(SELECT s1.member_key
,MAX(s1.visit_number_count) AS visit_number_count_max
FROM s1
GROUP BY s1.member_key)
-- JJAUSSI: Use this version to see the list of store_key values
-- that have the visit_number_count_max value. This has the potential
-- to be a 1:N relationship.
SELECT s1.member_key
,md.member_number
,s1.store_key
,sd.store_number
,s1.visit_number_count
FROM s2 INNER JOIN s1
ON s2.member_key = s1.member_key
AND s2.visit_number_count_max = s1.visit_number_count
INNER JOIN store_dimension sd
ON sd.store_key = s1.store_key
INNER JOIN member_dimension md
ON md.member_key = s1.member_key;
If this is what you were going for...congratulations! On to the next query!
If you ultimately are after a single store_key response for each member_key (basically you want to determine the member_key's "primary" store_key) then an additional step is probably needed (depending on your data).
Here are some ideas:
Evaluate the member_key based on some other summable facet of
item_scan_fact (like total price paid?)
If you consider all store_key values of equal merit that have the same visit_number_count_max value for a given member_key, just choose a store_key with MAX or MIN
You would seem to want:
SELECT member_number, MAX(NumVisits)
FROM (SELECT sd.store_number, md.member_number
COUNT(*) AS NumVisits
FROM member_dimension md JOIN
item_scan_fact isf
ON md.member_key = isf.member_key JOIN
store_dimension sd
ON isf.store_key = sd. store_key
GROUP BY sd.store_number, md.member_number
) sm
GROUP BY member_number;
If you want to return both the max and the matching customer number you can apply a Teradata SQL extension, qualify:
SELECT sd.store_number, md.member_number
COUNT(*) AS NumVisits
FROM member_dimension md JOIN
item_scan_fact isf
ON md.member_key = isf.member_key JOIN
store_dimension sd
ON isf.store_key = sd. store_key
GROUP BY sd.store_number, md.member_number
QUALIFY
rank() -- might return multiple rows with the same max, ROW_NUMBER a single row
over (partition by sd.store_number
order by NumVisits desc) = 1

sorting out the latest filedate in a VIEW in ms sql server

I'm trying to make a view out of three tables in my database.
I'm trying to only show the latest filetime that I have. As you can see I have a vehicle that shows two different values depending on the filetime (I have marked that area with a red square).
I would only like to show the latest filetime. How do i do that? I have tried with group on and using max. But it doesn't work. Here is my current code:
SELECT TOP (100) PERCENT MAX(db_ddladmin.Data_Readouts.ReadOutID) AS ReadOutID,
db_ddladmin.Data_Values.Identifier, db_ddladmin.Data_Values.ECU,
MAX(db_ddladmin.Data_Readouts.FileTime) AS FILETIME,
MAX(db_ddladmin.Data_Readouts.FileName) AS FN,
db_ddladmin.Data_Vehicles.Name, db_ddladmin.Data_Values.Value
FROM db_ddladmin.Data_Readouts
INNER JOIN db_ddladmin.Data_Values
ON db_ddladmin.Data_Readouts.ReadOutID = db_ddladmin.Data_Values.ReadOutID
INNER JOIN db_ddladmin.Data_Vehicles
ON db_ddladmin.Data_Readouts.VehicleID = db_ddladmin.Data_Vehicles.ID
GROUP BY db_ddladmin.Data_Values.Identifier, db_ddladmin.Data_Values.ECU,
db_ddladmin.Data_Vehicles.Name, db_ddladmin.Data_Values.Value
ORDER BY MAX(db_ddladmin.Data_Vehicles.Name)
EDIT: The values are from readouts from the vehicle. Why i need the latest filetime is because i want the latest readout. ReadOutID is a number which is given when a readout is made. The higher the number the later the readout is. FN is Filename and it consists the milage and filetime when the readout was made.
If I understand correctly, you don't need aggregation. Just use ROW_NUMBER() and select the most recent for each vehicle:
SELECT *
FROM (SELECT r.ReadOutID, v.Identifier, v.ECU, r.FileTime, r.FileName, ve.Name, v.Value
ROW_NUMBER() OVER (PARTITION BY ve.id ORDER BY r.FileTime DESC) as seqnum
FROM db_ddladmin.Data_Readouts r INNER JOIN
db_ddladmin.Data_Values v
ON r.ReadOutID = v.ReadOutID INNER JOIN
db_ddladmin.Data_Vehicles ve
ON r.VehicleID = ve.ID
) rv
WHERE seqnum = 1;
Also, notice that table aliases make the query easier to write and to read.
Try this:
SELECT ReadOutID,
Identifier,
ECU,
FileTime,
FileName,
Name,
[Value]
FROM (
SELECT dr.ReadOutID,
dv.Identifier,
dv.ECU,
dr.FileTime,
dr.FileName,
dve.Name,
dv.[Value],
DENSE_RANK() OVER (PARTITION BY dve.Name ORDER BY dr.FileTime DESC) as drnk
FROM db_ddladmin.Data_Readouts dr
INNER JOIN db_ddladmin.Data_Values dv
ON dr.ReadOutID = dv.ReadOutID
INNER JOIN db_ddladmin.Data_Vehicles dve
ON dr.VehicleID = dve.ID
) as rv
WHERE drnk = 1;

Oracle SQL Distinct Clause not presenting distinct values

I have a script when I'm trying to select locations in an inventory where quantity of said location is <= 5. The query is complete, now I'm trying to do some fine tuning, and what I'm running into now is when I use the distinct clause I am still receiving duplicate records in the same column. I do know the column next to the first are unique, but I thought distinguishing distinct and one column would roll over to next related to said column.
Here is my code:
select DISTINCT bin.scannable_id as bin,
bi.bin_id as case1,
pallet.scannable_id as pallet,
-- bi.isbn as fcsku,
nvl(fs.asin,bi.isbn) as asin,
sum(bi.quantity) as quantity,
pallet.creation_date as received_date
from containers bin
join containers pallet on pallet.containing_container_id = bin.container_id
join containers case on case.containing_container_id = pallet.container_id
join bin_items bi on bi.container_id = case.container_id
left join fcskus fs on fs.fcsku = bi.isbn
where bin.scannable_id like 'R-1-T%'
and bi.quantity <= '5'
group by bin.scannable_id, pallet.scannable_id, bi.bin_id, bi.owner,bi.isbn,nvl(fs.asin,bi.isbn), pallet.creation_date
order by sum(bi.quantity);
My output, which is obviously showing duplicate records in the scannable_id column:
Correct Formatting Thanks to conrad.
select DISTINCT bin.scannable_id as bin,
pallet.scannable_id as pallet,
nvl(fs.asin,bi.isbn) as asin,
sum(bi.quantity) as quantity
from containers bin
join containers pallet on pallet.containing_container_id = bin.container_id
join containers case on case.containing_container_id = pallet.container_id
join bin_items bi on bi.container_id = case.container_id
left join fcskus fs on fs.fcsku = bi.isbn
where bin.scannable_id like 'R-1-T%'
having sum(bi.quantity) <= '5'
group by bin.scannable_id, pallet.scannable_id, nvl(fs.asin,bi.isbn), bi.quantity
order by sum(bi.quantity);
As said on the comments you dont need a DISTINCT if you have the group by statement. And format your date field because depending on your oracle client configuration it will not show you the entire date format (e.g. date time). So try with this:
select bin.scannable_id as bin,
bi.bin_id as case1,
pallet.scannable_id as pallet,
nvl(fs.asin,bi.isbn) as asin,
to_char(pallet.creation_date, 'yyyy-mm-dd') as received_date
sum(bi.quantity) as quantity,
from containers bin
join containers pallet on pallet.containing_container_id = bin.container_id
join containers case on case.containing_container_id = pallet.container_id
join bin_items bi on bi.container_id = case.container_id
left join fcskus fs on fs.fcsku = bi.isbn
where bin.scannable_id like 'R-1-T%'
and bi.quantity <= '5'
group by bin.scannable_id,
pallet.scannable_id,
bi.bin_id,
bi.owner,
bi.isbn,
nvl(fs.asin,bi.isbn),
to_char(pallet.creation_date, 'yyyy-mm-dd')
order by sum(bi.quantity);
bi.bin_id is different for each row, so you do only have distinct results in your resultset.
distinct is applied to the final visible resultset (once the to_char etc. functions are processed)
distinct is redundant if you already use a group by expression
Solution: skipp the bi.bin_id column from your select expression.
Your logic is also confusing. You want to know the SUM of all the bi.* elements. To do so you cannot group by bi.bin_id nor any field from the bi table. This is the reason why your quantity result is always 1.

Select statement to show the corresponding user with the lowest/highest amount?

I want to write a select statement output that, among other things, has both a lowest_bid and highest_bid column. I know how to do that bit, but want I also want is to show the user (user_firstname and user_lastname combined into their own column) as lowest_bidder and highest_bidder. What I have so far is:
select item_name, item_reserve, count(bid_id) as number_of_bids,
min(bid_amount) as lowest_bid, ???, max(big_amount) as highest_bid,
???
from vb_items
join vb_bids on item_id=bid_item_id
join vb_users on item_seller_user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
sort by item_reserve
(The ???'s are where the columns should go, once I figure out what to put there!)
This seems like good use of window functions. I've assumed a column vb_bids.bid_user_id. If there's no link between a bid and a user, you can't answer this question
With x as (
Select
b.bid_item_id,
count(*) over (partition by b.bid_item_id) as number_of_bids,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount desc
) as high_row,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount
) as low_row,
b.bid_amount,
u.user_firstname + ' ' + u.user_lastname username
From
vb_bids b
inner join
vb_users u
on b.bid_user_id = u.user_id
Where
b.bid_status = 'ok'
)
Select
i.item_name,
i.item_reserve,
min(x.number_of_bids) number_of_bids,
min(case when x.low_row = 1 then x.bid_amount end) lowest_bid,
min(case when x.low_row = 1 then x.username end) low_bidder,
min(case when x.high_row = 1 then x.bid_amount end) highest_bid,
min(case when x.high_row = 1 then x.username end) high_bidder
From
vb_items i
inner join
x
on i.item_id = x.bid_item_id
Where
i.item_sold = 'no'
Group By
i.item_name,
i.item_reserve
Order By
i.item_reserve
Example Fiddle
In order to get the users, I broke out the aggregates into their own tables, joined them by the item_id and filtered them by a derived value that is either the min or max of bid_amount. I could have joined to vb_bids for a third time, and kept the aggregate functions, but that would've been redundant.
This will fail if you have two low bids of the exact same amount for the same item, since the join is on bid_amount. If you use this, then you'd want to created an index on vb_bids covering bid_amount.
select item_name, item_reserve, count(bid_id) as number_of_bids,
low_bid.bid_amount as lowest_bid, low_user.first_name + ' ' + low_user.last_name,
high_bid.bid_amount as highest_bid, high_user.first_name + ' ' + high_user.last_name
from vb_items
join vb_bids AS low_bid on item_id = low_bid.bid_item_id
AND low_bid.bid_amount = (
SELECT MIN(bid_amount)
FROM vb_bids
WHERE bid_item_id = low_bid.bid_item_id)
join vb_bids AS high_bid on item_id = high_bid.bid_item_id
AND high_bid.bid_amount = (
SELECT MAX(bid_amount)
FROM vb_bids
WHERE bid_item_id = high_bid.bid_item_id)
join vb_users AS low_user on low_bid.user_id=user_id
join vb_users AS high_user on high_bid.user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
group by item_name, item_reserve,
low_bid.bid_amount, low_user.first_name, low_user.last_name,
high_bid.bid_amount, high_user.first_name, high_user.last_name
order by item_reserve
I am a big fan of using Common Table Expressions (CTEs) for situations like this, because of the following advantages:
Separating different parts of the logic, adding to readability, and
Reducing complexity (for example, the need to GROUP BY a large number of fields, or to repeat the same join multiple times.)
So, my suggested approach would be something like this:
-- semi-colon must precede CTE
;
-- collect bid info
WITH item_bids AS (
SELECT
i.item_id, i.item_name, i.item_reserve, b.bid_id, b.bid_amount,
(u.first_name + ' ' + u.last_name) AS bid_user_name
FROM vb_items i
JOIN vb_bids b ON i.item_id = b.bid_item_id
JOIN vb_users u ON b.user_id = u.user_id
WHERE b.bid_status = 'ok'
AND i.item_sold = 'no'
),
-- group bid info
item_bid_info AS (
SELECT item_id, item_name, item_reserve
COUNT(bid_id) AS number_of_bids, MIN(bid_amount) AS lowest_bid, MAX(bid_amount) AS highest_bid
FROM item_bids
GROUP BY item_id, item_name, item_reserve
)
-- assemble final result
SELECT
bi.item_name, bi.item_reserve, bi.number_of_bids,
bi.low_bid, low_bid.bid_user_name AS low_bid_user,
bi.high_bid, high_bid.bid_user_name AS high_bid_user
FROM item_bid_info bi
JOIN item_bids AS low_bid ON bi.lowest_bid = low_bid.bid_amount AND bi.item_id = low_bid.bid_item_id
JOIN item_bids AS high_bid ON bi.lowest_bid = high_bid.bid_amount AND bi.item_id = high_bid.bid_item_id
ORDER BY bi.item_reserve;
Note that the entire SQL statement (from the starting WITH all the way down to the final semi-colon after the ORDER BY) is a single statement, and is evaluated by the optimizer as such. (Some people think each part is evaluated separately, like temp tables, and then all the rows are joined together at the end in a final step. That's not how it works. CTEs are just as efficient as sub-queries.)
Also note that this approach does a JOIN on the bid amount, so if there are identical bids for a single item, it will fail. (Seems like that should be an invalid state anyway, though, right?) Also you may have efficiency concerns depending on:
The size of your table
Whether the lookup can use an index
You could address both issues by including a unique constraint (which has the added advantage of indexing the foreign key bid_item_id as well; always a good practice):
ALTER TABLE [dbo].[vb_bids] ADD CONSTRAINT [UK_vbBids_item_amount]
UNIQUE NONCLUSTERED (bid_item_id, bid_amount)
GO
Hope that helps!