SQL Three table join - sql

I haven't been able to solve this problem for several days now and I'm hoping you can help.
I'm trying to write a query that returns all the information about a stock and the last time it was updated. I would like to filter the results based on the parameter #date and return only the stocks which it's latests update is less than the supplied #date parameter. I also need the stocks with a timestamp of null so I know that theses stocks need to be updated. I have the follwing three tables that I'm working with:
stocks
- id
- asset_id
- market_id
- name
- symbol
- IPOYear
- sector
- industry
updates
- id
- [timestamp]
stock_updates
- stock_id
- update_id
I've been using the following query and it was working well for me until I realized it dosen't work if the stock doesn't have an update
select * from stocks s
where #date < (
select top 1 u.timestamp from
updates u,
stock_updates su
where
s.id = su.stock_id and
u.id = su.update_id
order by u.timestamp desc
)
So after some research I came accross outer joins and I think it's what I need to fix my problem I just haven't been able to construct the correct query. The closest I've come is the following, but it returns a record for each time the stock was updated. Thanks in advance for your help!
This is where I'm at now:
select * from stocks s
left outer join stock_updates su on s.id = su.stock_id
left outer join updates u on u.id = su.update_id
where u.[timestamp] < #date

select s.*, u.timestamp
from stocks s
left join
(select su.stock_id, MAX(u.timestamp) timestamp
from updates u
inner join stock_updates su
on u.id = su.update_id
group by su.stock_id
) as u
on s.id = u.stock_id
where u.[timestamp] is null or u.[timestamp] < #date

Something like this perhaps?
SELECT s.*, v.timestamp
FROM stocks s
LEFT JOIN (
SELECT MAX(u.timestamp) AS timestamp, su.stock_id
FROM stock_updates su
INNER JOIN updates u ON (u.id = su.update_id)
GROUP BY su.stock_id
) v ON (v.stock_id = s.stock_id)
Basically it just joins the stocks table to an "inline view" that is the result of a query to determine the maximum timestamp for each stock_id.

I haven't included any filtering by the #date parameter, as your question states
"I'm trying to write a query that returns all the information about a
stock and the last time it was updated"
and for that you don't require any filtering.
This query does exactly that:
select s.*, dr.maxtime,
from stocks s
left join (select MAX(u.timestamp) as maxtime, su.stock_id
from stock_updates su inner join updates u on u.id = su.update_id
group by su.stock_id) dr
on dr.stock_id = s.stock_id
where
maxtime < #date or maxtime is null
[BTW: left join is the same as left outer join]

Try this
select s.*, max(su.timestamp)
from
stocks s
left outer join
stock_update su
on (s.id = su.stock_id)
left outer join
updates u
on (u.id = su.update_id)
group by s.*
It's written off the top of my head. What do you refer to with #date? Does that mean "now"? Do you mean the latest timestamp, or the latest before #date?

Related

Finding days when users haven't created any entries

I've 2 tables: users and time_entries, time entries has a foreign key to the users table. Users may create time entries with some time amount in it. I want to write a query which could return summarized amounts of time in arbitrary dates range grouped by user and date - it's easy but I need to include also days when nobody entered any time_entry. I've tried to create an additional table called calendar with dates and left join time_entries to it but I couldn't retrieve a list of users that haven't entered any time_entry. Here is my query:
SELECT te.date, SUM(te.amount), user_name
FROM calendar c
LEFT JOIN time_entries te on c.date = te.date
RIGHT JOIN asp_net_users anu on te.user_id = anu.id
GROUP BY user_name, te.date
If you just want the days no user made any entry. you can use NOT EXISTS and a correlated subquery.
SELECT c.date
FROM calendar c
WHERE NOT EXISTS (SELECT *
FROM time_entries te
WHERE te.date = c.date);
If you want all users along with the days they haven't made any entry cross join the users and the days and then also use a NOT EXISTS.
SELECT anu.user_name,
c.date
FROM asp_net_users anu
CROSS JOIN calendar c
WHERE NOT EXISTS (SELECT *
FROM time_entries te
WHERE te.user_id = anu.id
AND te.date = c.date);
Thanks to sticky bit examples I was able to write the following query which solves my problem:
SELECT c.date, a.id, COALESCE(sum(te.amount), 0)
FROM asp_net_users a
CROSS JOIN (SELECT *
FROM calendar
WHERE date BETWEEN '2019-10-01 00:00:00'::timestamp AND '2019-10-31 00:00:00'::timestamp) c
LEFT JOIN time_entries te on a.id = te.user_id AND c.date = te.date
WHERE a.department_guid = '95b7538d-3830-48d7-ba06-ad7c51a57191'
GROUP BY c.date, a.id
ORDER BY c.date

SQL server SELECT with join performance issue

Sorry about the saga here but am trying to explain everything.
We have 2 databases that I would like to join some tables in.
1 database holds sales data from various different stores/sites. This database is quite large (over 3mill rows currently) This table is ItemSales
The other holds application data from an in house web app. These tables are Departments and GroupItems
I would like to create a query that joins 2 tables from the app database with the sales database table. This is so we can group some items together for a date range and see the amount sold for example.
My first attempt was (DealId being the variable that it is grouped on in the App):
SELECT d.Id, d.ItemNo, d.UnitValue, d.NoGST, d.ItemStartDate, d.ItemEndDate,
(SELECT SUM(ItemQty) AS Expr1
FROM Sales.dbo.ItemSales AS s
WHERE (Store = d.SiteId) AND (ItemNo = d.ItemNo) AND (ItemSaleDate >= d.ItemStartDate) AND (ItemSaleDate <= d.ItemEndDate)) AS ItemsSold, Sales.dbo.ItemSales.ItemDesc, Departments.Description
FROM Departments INNER JOIN
Sales.dbo.ItemSales ON Departments.Id = Sales.dbo.ItemSales.ItemDept RIGHT OUTER JOIN
GroupItems AS d ON Sales.dbo.ItemSales.ItemNo = d.ItemNo
WHERE (d.DealId = 11)
GROUP BY d.Id, d.ItemNo, d.UnitValue, d.NoGST, d.ItemStartDate, d.ItemEndDate, ItemDesc, Departments.Description, d.SiteId
ORDER BY d.Id
This does exactly what I want which is:
-Give me all the details from the GroupItems table (UnitValue, ItemStartDate, ItemEndDate etc)
-Gives me the SUM() on the ItemQty column for the amount sold (plus the description etc)
-Returns NULL for something with no sales for the period
It is VERY slow though. To the point that if the GroupItems table has more than about 7 items in it, it times out.
Second attempt has been:
SELECT d.Id, d.ItemNo, d.UnitValue, d.NoGST, d.ItemStartDate, d.ItemEndDate, SUM(ItemQty) AS ItemsSold, Sales.dbo.ItemSales.ItemDesc, Departments.Description
FROM Departments INNER JOIN
Sales.dbo.ItemSales ON Departments.Id = Sales.dbo.ItemSales.ItemDept RIGHT OUTER JOIN
GroupItems AS d ON Sales.dbo.ItemSales.ItemNo = d.ItemNo
WHERE (Store = d.SiteId) AND (d.DealId = 11) AND (Sales.dbo.ItemSales.ItemSaleDate >= d.ItemStartDate) AND (Sales.dbo.ItemSales.ItemSaleDate <= d.ItemEndDate)
GROUP BY d.Id, d.ItemNo, d.UnitValue, d.NoGST, d.ItemStartDate, d.ItemEndDate, ItemDesc, Departments.Description
ORDER BY d.Id
This is very quick and does not time out but does not return the NULLs for no sales items in the ItemSales table. This is a problem as we need to see nothing or 0 for a no sales item otherwise people will think we forgot to check that item.
Can someone help me come up with a query please that returns everything from the GroupItems table, shows the SUM() of items sold and doesn't time out? I have also tried a SELECT x WHERE EXISTS (Subquery) but this also didn't return the NULLs for me but I may have had that one wrong.
If you want everything from GroupItems regardless of the sales, use it as the base of the query and then use left outer joins from there. Something along these lines:
SELECT GroupItems.Id, GroupItems.ItemNo, GroupItems.UnitValue, GroupItems.NoGST,
GroupItems.ItemStartDate, GroupItems.ItemEndDate,
Sales.ItemDesc,
SUM(ItemQty) AS SumOfSales,
Departments.Description
FROM GroupItems
LEFT OUTER JOIN #tempSales AS Sales ON
Sales.ItemNo = GroupItems.ItemNo
AND Sales.Store = GroupItems.SiteId
AND Sales.ItemSaleDate >= GroupItems.ItemStartDate
AND Sales.ItemSaleDate <= GroupItems.ItemEndDate
LEFT OUTER JOIN Departments ON Departments.Id = Sales.ItemDept
WHERE GroupItems.DealId = 11
GROUP BY GroupItems.Id, GroupItems.ItemNo, GroupItems.UnitValue, GroupItems.NoGST,
GroupItems.ItemStartDate, GroupItems.ItemEndDate,
Sales.ItemDesc,
SUM(ItemQty) AS SumOfSales,
Departments.Description
ORDER BY GroupItems.Id
Does changing the INNER JOIN to Sales.dbo.ItemSales into a LEFT OUTER JOIN to Sales.dbo.ItemSales and changing the RIGHT OUTER JOIN to GroupItems into an INNER JOIN to GroupItems fix your issue?

Left outer join and group by issue

I wrote a query. this query sum fields from 2 different table. And grouped by main table id field. But second left outer join is not grouped and giving me different results.
SELECT s.*,
f.firma_adi,
sum(sd.fiyat) AS konak,
sum(ss.fiyat) AS sponsor
FROM fuar_sozlesme1 s
INNER JOIN fuar_firma_2012 f
ON ( s.cari = f.cari )
LEFT OUTER JOIN fuar_sozlesme1_detay sd
ON ( sd.sozlesme_id = s.id )
LEFT OUTER JOIN fuar_sozlesme1_sponsor ss
ON ( ss.sozlesme_id = s.id )
GROUP BY s.id
ORDER BY s.id DESC
I know, it is really complicated but I'm stucking on this issue.
My question is: why second left outer join is not correctly sum of field . If I remove second left outer join or first, everything is normal.
The problem is that you have multiple dimensions on your data, and the number of rows is multiplying beyond what you expect. I would suggest that you run the query for one id, without the group by, to see what rows the join is producing.
One way to fix this is by using correlated subqueries:
select s.*, f.firma_adi,
(select SUM(sd.fiyat)
from fuar_sozlesme1_detay fd
where sd.sozlesme_id = s.id
) as konak,
(select SUM(ss.fiyat)
from fuar_sozlesme1_sponsor ss
where (ss.sozlesme_id = s.id)
) as sponsor
from fuar_sozlesme1 s inner join
fuar_firma_2012 f
on (s.cari = f.cari)
order by s.id DESC
By the way, you appear to by using MySQL (because your query is not parsable in any other dialect). You should tag your questions with the version of the database you are using.

SQL QUERY to get Count of particular column between two Range of Dates

Here I have a doubt regarding sql query.
In this scenario I have a table called tblcrime : where we will get the sum(crime) here I track MainID and sum(crime) query will be like this :
SELECT sum(o.crimeID) as crimeNumber,u.UserID
from tblcrime o
inner join tblSubContractor ts on
o.MainID=ts.SubContractorID
from here I will chk the tblUSER with these subcontractorID :
inner join tblUser u on
u.SubContractorID=ts.SubContractorID
and my doubt is that up to here I will get the total sum of crime and appropriate userid., for e.g.
UserID : 520 Totalcrime:6000
but there is another table called tblAudit where we will get logondate and userid, which is tracking here.. so I want to display crime based on userlogin(userid) ...since last login. So that when user login it shows in a jquery notification that "60 crimes has been done since last login".
I want help in query format.
I'm not sure, if I understand your question right, but may this be, what you are looking for?
SELECT sum(o.crimeID) as crimeNumber,u.UserID
from tblcrime o
inner join tblSubContractor ts on o.MainID=ts.SubContractorID
inner join tblUser u on u.SubContractorID=ts.SubContractorID
where
u.UserID = theOneYouAreLookingFor
AND crimedate >= lastLogOn
GROUP BY u.UserID
Firstly, I suspect that the call to the SUM function should really be to COUNT. The former adds the values of the specified column together, whereas the latter gives you a row count.
Secondly, does your tblcrime table store the date that crimes are added? I'll assume it does, let's call the column DateAdded. The following query should work:
SELECT COUNT(o.crimeID) AS crimeNumber,
u.UserID
FROM tblcrime o
INNER JOIN tblSubContractor ts on o.MainID = ts.SubContractorID
INNER JOIN tblUser u on u.SubContractorID = ts.SubContractorID
INNER JOIN tblAudit a on a.userid = u.UserID
WHERE a.logondate < o.DateAdded
GROUP BY u.UserID
You could find the max auditdate for that user:
SELECT sum(o.crimeID) as crimeNumber,u.UserID
from tblcrime o
inner join tblSubContractor ts on
o.MainID=ts.SubContractorID
inner join tblUser u on
u.SubContractorID=ts.SubContractorID
where o.crimeDate >= (select max(auditdate) from tblAudit where UserID = #UserID)

i want to modify this SQL statement to return only distinct rows of a column

select
picks.`fbid`,
picks.`time`,
categories.`name` as cname,
options.`name` as oname,
users.`name`
from
picks
left join categories
on (categories.`id` = picks.`cid`)
left join options
on (options.`id` = picks.oid)
left join users
on (users.fbid = picks.`fbid`)
order by
time desc
that query returns a result that like:
my question is.... I would like to modify the query to select only DISTINCT fbid's. (perhaps the first row only sorted by time)
can someone help with this?
select
p2.fbid,
p2.time,
c.`name` as cname,
o.`name` as oname,
u.`name`
from
( select p1.fbid,
min( p1.time ) FirstTimePerID
from picks p1
group by p1.fbid ) as FirstPerID
JOIN Picks p2
on FirstPerID.fbid = p2.fbid
AND FirstPerID.FirstTimePerID = p2.time
LEFT JOIN Categories c
on p2.cid = c.id
LEFT JOIN Options o
on p2.oid = o.id
LEFT JOIN Users u
on p2.fbid = u.fbid
order by
time desc
I don't know why you originally had LEFT JOINs, as it appears that all picks must be associated with a valid category, option and user... I would then remove the left, and change them to INNER joins instead.
The first inner query grabs for each fbid, the FIRST entry time which will result in a single entity for the FBID. From that, it re-joins to the picks table for the same ID and timeslot... then continues for the rest of the category, options, users join criteria of that single entry.
2 options, you could write a group by clause.
Or you could write a nested query joined back to itself to get pertinent info.
Nested aliased table:
SELECT
n.fBids
FROM
MyTable t
INNER JOIN
(SELECT DISTINCT fBids
FROM MyTable) n
ON n.ID = t.ID
Or group by option
SELECT fBId from MyTable
GROUP BY fBID
select picks.`fbid`, picks.`time`, categories.`name` as cname,
options.`name` as oname, users.`name` from picks left join categories
on (categories.`id` = picks.`cid`) left join options on (options.`id` = picks.oid)
left join users on (users.fbid = picks.`fbid`)
order by time desc GROUP BY picks.`fbid`
select
picks.fbid,
MIN(picks.time) as first_time,
MAX(picks.time) as last_time
from
picks
group by
picks.fbid
order by
MIN(picks.time) desc
However, if you want only distinct fbid's you cannot display cname and other columns at the same time.