LEFT JOIN with right table criteria - sql

I would like to join table A and B with the following criteria to get the result table.
TABLE A is the Starting TIME
TABLE B consist of both TIME IN and TIME OUT
USER need to clock in (TIME IN), start machine (TIME START), clock out (TIME OUT)
TIME IN >= TIME START
TIME START >= TIME OUT
MAX TIME IN/OUT
DISTINCT Results based on ID
I tried left join but couldn't get the result that I want. I tried using CASE but the are lots of redundant result. Please advise.

SELECT a.ID as id,
GROUP_CONCAT(CASE
WHEN b.Code = 'In'
THEN b.Time
end) as `Time In`,
`TIME START`,
GROUP_CONCAT(CASE
WHEN b.Code = 'Out'
THEN b.Time
end) as `Time Out`
FROM `Table A` a
JOIN `Table B` b on a.ID = b.ID
WHERE 1
AND ((b.Code = 'In' && b.`TIME` <= a.`TIME START`) OR
(b.Code = 'Out' && b.`TIME` >= a.`TIME START`))
GROUP BY a.ID, a.`Time Start`
query doesn`t handle Code='In/Out' you need to split this explict

You can do this with a couple of LEFT JOINs:
SELECT
ts.ID,
MAX(ti.Time) AS Time,
MAX(ts.TimeStart) AS TimeStart,
MAX(tto.Time) AS TimeOut
FROM tableA ts
LEFT JOIN tableB ti ON ts.ID = ti.ID AND ti.Code IN ('IN','IN/OUT') -- "Time IN"
LEFT JOIN tableB tto ON ts.ID = tto.ID AND tto.Code IN ('Out','IN/OUT') -- "Time OUT"
GROUP BY ts.ID
ORDER BY ts.ID
Like Impaler mentioned, your description of IN/OUT and the desired result you provided don't seem to be in sync. The example above assumes IN/OUT is both IN and OUT. This will also give you the max TimeIn/TimeOut values if there are duplicate entries.
DB Fiddle (Postgres)

Try aggregating table B before the join:
WITH cte AS
( -- pivot into in/out time
SELECT
ID
,Max(CASE WHEN Code IN ('IN', 'IN/OUT') THEN Time END) AS TimeIn
,Max(CASE WHEN Code IN ('Out','IN/OUT') THEN Time END) AS TimeOut
FROM tableB
GROUP BY ID
)
SELECT
a.ID
,CASE WHEN TimeStart >= TimeIn THEN TimeIn END AS TimeIn
,TimeStart
,CASE WHEN TimeStart <= TimeOut THEN TimeIn END AS TimeIn
FROM tableA AS a
LEFT JOIN cte AS b
ON a.ID = b.ID

Related

(probably) very simple SQL query needed

Having a slow day....could use some assistance writing a simple ANSI SQL query.
I have a list of individuals within families (first and last names), and a second table which lists a subset of those individuals. I would like to create a third table which flags every individual within a family if ANY of the individuals are not listed in the second table. The goal is essentially to flag "incomplete" families.
Below is an example of the two input tables, and the desired third table.
As I said...very simple...having a slow day. Thanks!
I think you want a left join and case expression:
select t1.*,
(case when t2.first_name is null then 'INCOMPLETE' else 'OK' end) as flag
from table1 t1 left join
table2 t2
on t1.first_name = t2.first_name and t1.last_name = t2.last_name;
Of course, this marks "Diane Thomson" as "OK", but I think that is an error in the question.
EDIT:
Oh, I see. The last name defines the family (that seems like a pretty big assumption). But you can do this with window functions:
select t1.*,
(case when count(t2.first_name) over (partition by t1.last_name) =
count(*) over (partition by t1.last_name)
then 'OK'
else 'INCOMPLETE'
end) as flag
from table1 t1 left join
table2 t2
on t1.first_name = t2.first_name and t1.last_name = t2.last_name;
That's not simple, at least not in SAS :-)
Standard SQL, when Windowed Aggregates are supported:
select ft.*,
-- counts differ when st.first_name is null due to the outer join
case when count(*) over (partition by ft.last_name)
= count(st.first_name) over (partition by ft.last_name)
then 'OK'
else 'INCOMPLETE'
end
from first_table as ft
left join second_table as st
on ft.first_name = st.first_name
and ft.last_name = ft.last_name
Otherwise you need to a standard aggregate and join back:
select ft.*, st.flag
from first_table as ft
join
(
select ft.last_name,
case when count(*)
= count(st.first_name)
then 'OK'
else 'INCOMPLETE'
end as flag
from first_table as ft
left join second_table as st
on ft.first_name = st.first_name
and ft.last_name = st.last_name
group by ft.last_name
) as st
on ft.last_name = st.last_name
It is pretty easy to do in SAS if you want to take advantage of its non-ANSI SQL feature of automatically re-merging aggregate function results back onto detail records.
select
a.first
, a.last
, case when 1=max(missing(b.last)) then 'INCOMPLETE'
else 'OK'
end as flag
from table1 a left join table2 b
on a.last=b.last and a.first=b.first
group by 2
order by 2,1
;

How to do a join in Oracle tables?

I am trying to help a co-worker do an inner join on two oracle tables so he can build a particular graph on a report.
I have no Oracle experience, only SQL Server and have gotten to what seems like the appropriate statement, but does not work.
SELECT concat(concat(month("a.timestamp"),','),day("a.timestamp")) as monthDay
, min("a.data_value") as minTemp
, max("a.data_value") as maxTemp
, "b.forecast" as forecastTemp
, "a.timestamp" as date
FROM table1 a
WHERE "a.category" = 'temperature'
GROUP BY concat(concat(month("timestamp"),','),day("timestamp"))
INNER JOIN (SELECT "forecast"
, "timestamp"
FROM table2
WHERE "category" = 'temperature') b
ON "a.timestamp" = "b.timestamp"
It doesn't like my aliases for some reason. It doesn't like not having quotes for some reason.
Also when I use the fully scored names it still fails because:
ORA-00933 SQL command not properly ended
The order of the query should be
SELECT
FROM
INNER JOIN
WHERE
GROUP BY
as below
SELECT concat(concat(month("a.timestamp"),','),day("a.timestamp")) as monthDay
, min("a.data_value") as minTemp
, max("a.data_value") as maxTemp
, "b.forecast" as forecastTemp
, "a.timestamp" as date
FROM table1 a
INNER JOIN (SELECT "forecast"
, "timestamp"
FROM table2
WHERE "category" = 'temperature') b
ON "a.timestamp" = "b.timestamp"
WHERE "category" = 'temperature'
GROUP BY concat(concat(month("timestamp"),','),day("timestamp"))
In a flood of attempts, here's yet another one.
table2 can be moved out of subquery; join it with table1 on category as well
note that all non-aggregates columns (from the SELECT) have to be contained in the GROUP BY clause. It seems that a.timestamp contains more info than just month and day - if that's so, it'll probably ruin the whole result set as data won't be grouped by monthday, but by the whole date - consider removing it from SELECT, if necessary
SELECT TO_CHAR(a.timestamp,'mm.dd') monthday,
MIN(a.data_value) mintemp,
MAX(a.data_value) maxtemp,
b.forecast forecasttemp,
a.timestamp c_date
FROM table1 a
JOIN table2 b ON a.timestamp = b.timestamp
AND a.category = b.category
WHERE a.category = 'temperature'
GROUP BY TO_CHAR(a.timestamp,'mm.dd'),
b.forecast,
a.timestamp;
The correct (simplified) syntax of select is
SELECT <columns>
FROM table1 <alias>
JOIN table2 <alias> <join_condition>
WHERE <condition>
GROUP BY <group by columns>
You are doing it wrong. Use subquery:
SELECT c.*, b.`forecast` as forecastTemp
FROM
(SELECT concat(concat(month(a.`timestamp`),','),day(a.`timestamp`)) as monthDay
, min(a.`data_value`) as minTemp
, max(a.`data_value`) as maxTemp
, a.`timestamp` as date
FROM table1 a
WHERE `category`='temperature'
GROUP BY concat(concat(month(`timestamp`),','),day(`timestamp`))) c
INNER JOIN (SELECT `forecast`
, `timestamp`
FROM table2
WHERE `category` = 'temperature') b
ON c.`timestamp` = b.`timestamp`;
In addition to the order of the components other answers have mentioned (where goes after join etc), you also need to remove all of the double-quote characters. In Oracle, these override the standard naming rules, so "a.category" is only valid if your table actually has a column named, literally, "a.category", e.g.
create table demo ("a.category" varchar2(10));
insert into demo ("a.category") values ('Weird');
select d."a.category" from demo d;
It's quite rare to need to do this.
The query should look something like this:
SELECT to_char(a.timestamp, 'MM-DD') as monthDay,
min(a.data_value) as minTemp,
max(a.data_value) as maxTemp,
b.forecast as forecastTemp
FROM table1 a JOIN
table2 b
ON a.timestamp = b.timestamp and b.category = 'temperature'
WHERE a.category = 'temperature'
GROUP BY to_char(timestamp, 'MM-DD'), b.forecast;
I'm not 100% sure this is what you want. Your query has numerous issues and complexities:
You don't need a subquery in the FROM clause.
You can use to_char() instead of the more complex date string processing.
The group by did not contain all the relevant fields.
Don't use double quotes, unless really, really needed.

Insert into table with multiple joins under a unique condition based off time

I have an insert statment that incorporates multiple joins. However, the last join (table ItemMulitplers) doesnt really have anything "tied" to the other tables. They are just multipliers in this table with no unique identification or connection with others. the only thing is a timestamp from this table.
I have 5 rows in this table and my script is taking all five rows. I need it to select only one and to base it off of the closest time from the table called ItemsProduced. They get executed at the same time but not on the same millisecond level. any help is most appreciated thank you
insert into KLNUser.dbo.ItemLookup (ItemNumber, Cases, [Description], [Type], Wic, Elc, totalelc, Shift, [TimeStamp])
select a.ItemNumber, b.CaseCount,b.ItemDescription, b.DivisionCode, b.WorkCenter, b.LaborPerCase, a.CaseCount* b.LaborPerCase* c.IaCoPc, a.shift, a.TimeStamp from ItemsProduced a
inner join MasterItemList b on a.ItemNumber = b.itemnumber
inner join ItemMultipliers c on c.MultiplyTimeStamp <=a.Timestamp Interval 1 seconds
where not exists (select * from ItemLookup where ItemNumber = a.ItemNumber and Cases = b.CaseCount and [TimeStamp] = a.TimeStamp)
I think the easiest way is with cross apply:
select a.ItemNumber, b.CaseCount,b.ItemDescription, b.DivisionCode, b.WorkCenter, b.LaborPerCase, a.CaseCount* b.LaborPerCase* c.IaCoPc, a.shift, a.TimeStamp
from ItemsProduced a inner join
MasterItemList b
on a.ItemNumber = b.itemnumber cross apply
(select top 1 *
from ItemMultipliers c
where c.MultiplyTimeStamp < a.Timestamp
order by c.MultiplyTimeStamp desc
) c
where not exists (select * from ItemLookup where ItemNumber = a.ItemNumber and Cases = b.CaseCount and [TimeStamp] = a.TimeStamp)

How to determine two different aggregate dates on employee attendance data?

I Need help to implement employee attendance sheet. Presently am having employee attendance i.e
Query:
SELECT c.First_name + c.Middle_name + c.last_name AS employeename,
b.Device_Person_id,a.Dept_Id,Date1,
CASE WHEN b.Device_Person_id IS NOT NULL
THEN 'P'
ELSE 'A' END AS status
FROM Emp_setting a
LEFT OUTER JOIN (SELECT Device_Person_id, MAX(logDateTime) AS Date1
FROM tempDeviceLogs
GROUP BY Device_Person_id) b
ON a.personal_id = b.Device_Person_id
LEFT OUTER JOIN persons_profile c
ON c.pesonal_id=a.personal_id
Result:
employeename Device_person_id dept_id date1 status
MEHABOOB NULL 4 NULL A
UDAY NULL 26 NULL A
SHANKRAYYA NULL 10 NULL A
BASAVARAJ NULL 24 NULL A
BHIMAPPA 5 10 2014-05-23 14:14:00.000 P
i.e. Employeename BHIMAPPA is present on 2014-05-23.
NOW I want the list of employees who is present on 2014-05-23.
Please help?
Assuming that logDateTime indicates the presence of the employee, and given that the current derived table returns the MAX() of this field (presumably the last time the employee was detected) you would need another join to the tempDeviceLogs table to do the filtering. You could do the filtering at the same time with an INNER JOIN, viz:
...
INNER JOIN (SELECT Device_Person_id
FROM tempDeviceLogs
WHERE logDateTime >= '2014-05-23' and logDateTime < '2014-05-24') x
ON x.Device_Person_id = a.personal_id
Edit
Given that you want to SELECT the date as well, I'm assuming you want to parameterize it / use it for a range. And making yet another assumption about your SqlServer version being >= 2008, cast the DateTime to a Date and group by it:
SELECT c.First_name + c.Middle_name + c.last_name AS employeename,
b.Device_Person_id,a.Dept_Id, Date1 as DateTimeLastSeen,
x.logDate As DatePresent,
CASE WHEN b.Device_Person_id IS NOT NULL THEN 'P' ELSE 'A' END AS status
FROM Emp_setting a
LEFT OUTER JOIN (SELECT Device_Person_id, MAX(logDateTime) AS Date1
FROM tempDeviceLogs
GROUP BY Device_Person_id) b
ON a.personal_id = b.Device_Person_id
LEFT OUTER JOIN persons_profile c
ON c.pesonal_id=a.personal_id
INNER JOIN (SELECT Device_Person_id, CAST(logDateTime AS DATE) as logDate
FROM tempDeviceLogs
GROUP BY Device_Person_id, CAST(logDateTime AS DATE)) x
ON x.Device_Person_id = a.personal_id
WHERE
logDate BETWEEN '2014-05-01' AND '2014-05-23';
This could also be done with a DISTINCT. If you have an earlier version of SqlServer, use a hack like this to obtain the date part of a DateTime (in the select + group)
Create a variable to hold the date which you are looking for.
DECLARE #cutOffDate DATE
SET #cutOffDate = '23-05-2014'
Then add to the end of your statement
Where Date1 = #cutOffDate

Aggregate function in comparison of 2 rows in the same table (SQL)

Given the table definition:
create table mytable (
id integer,
mydate datetime,
myvalue integer )
I want to get the following answer by a single SQL query:
id date_actual value_actual date_previous value_previous
where:
date_previous is the maximum of all the dates preceeding date_actual
for each id and values correspond with the two dates
{max(date_previous) < date_actual ?}
How can I achieve that?
Thanks for your hints
This is a variation of the common "greatest N per group" query which comes up every week on StackOverflow.
SELECT m1.id, m1.mydate AS date_actual, m1.myvalue AS value_actual,
m2.mydate AS date_previous, m2.myvalue AS value_previous
FROM mytable m1
LEFT OUTER JOIN mytable m2
ON (m1.id = m2.id AND m1.mydate > m2.mydate)
LEFT OUTER JOIN mytable m3
ON (m1.id = m3.id AND m1.mydate > m3.mydate AND m3.mydate > m2.mydate)
WHERE m3.id IS NULL;
In other words, m2 is all rows with the same id and a lesser mydate, but we want only the one such that there is no row m3 with a date between m1 and m2. Assuming the dates are unique, there will only be one row in m2 where this is true.
Assuming I understood your requirements correctly, here's something you can try.
select a.id,
a.mydate as date_actual,
a.value as value_actual,
b.date as date_previous,
b.value as value_previous
from mytable a, mytable b
where a.id = b.id and
a.mydate > b.mydate and
b.mydate = (select max(mydate) from mytable c where c.id = a.id and c.mydate < a.mydate)
Apologies for the ugly SQL. I am sure there are better ways of doing this.