SQL SELECT JOIN FILTER - sql

I try to explain the problem as good as possible.
I have multiple tables:
project, group, period.
The connection table of these three is called project_status.
I will quickly show there content
Project
| projectID | name | date |
| ------------------------|
| 1 | test | 2015 |
| 2 | test | 2015 |
Group
| groupID| name |
| --------------|
| 1 | ab |
| 2 | cd |
Period
| periodID | status |
| ---------------------|
| 1 | 0 | #inactive
| 2 | 1 | #active
| 3 | 2 | #new
Project stats
| projectID | groepID | periodID |
| -------------------------------|
| 1 | 1 | 2 | #active period
| 1 | 1 | 3 | #new period
Now in a gui you can select a period. Is the period active then i dont show the
project because it's in use (active). Now when i select a period with status new there must be a check to determine:
Is this project already in a new period
The problem is when i write a query there is always the active period. How could i write a query that only checks in project status for status new
i have tried the following query
SELECT projectID, name
FROM project
WHERE projectID IN
(
SELECT ps.projectID
FROM project_status as ps
JOIN period as per
ON ps.periodID = per.periodID
WHERE per.status = 0
AND per.stats != 2
)
OR projectID NOT IN
(
SELECT projectID
FROM project_status
)

Your query looks right.
Only remove
AND per.periode_status != 2
What is periode_status ? You didnt explain
WHERE per.status = 0
AND per.periode_status != 2 -- remove it

You could use a cross apply, something like this would select projectID & name where periodID = 3:
SELECT projectID, name FROM project a
CROSS APPLY (SELECT projectID,periodID FROM project_status WHERE projectID = a.projectID) b
WHERE b.periodID = 3

Related

Join three tables by one foreign key

I have three tables:
Task (ID, TaskDescription)
Schedule (TaskID, ID, DueAt)
Audit (TaskID, TestID)
In Schedule table there is a list of scheduled tasks, and Audit table is for already done tasks. So first there is a row in Schedule, then when this task is done it's removing from Schedule table and added into Audit table.
Tasks table
+----+-----------------+
| ID | TaskDescription |
+----+-----------------+
| 1 | Clean room |
| 2 | Remove trash |
+----+-----------------+
Schedule table
+--------+--------+------------+
| ID | TaskID | DueAt |
+--------+--------+------------+
| 927847 | 1 | 2020-08-01 |
| 777777 | 2 | 2020-08-07 |
+--------+--------+------------+
Audit table
+--------+--------+
| TaskID | TestID |
+--------+--------+
| 1 | 3 |
| 1 | 2 |
| 1 | 1 |
| 2 | 4 |
+--------+--------+
I need to take all planned and already done tasks for one task ID. So for example, what I expect as result:
+---------+-----------------+-------------+----------------+--------+
| Task.ID | TaskDescription | Schedule.ID | Schedule.DueAt | TestID |
+---------+-----------------+-------------+----------------+--------+
| 1 | Clean room | 927847 | 2020-08-01 | NULL |
| 1 | Clean room | NULL | NULL | 3 |
| 1 | Clean room | NULL | NULL | 2 |
| 1 | Clean room | NULL | NULL | 1 |
+---------+-----------------+-------------+----------------+--------+
That means already 3 tasks are done and one is scheduled for 2020-08-01.
What i tried:
SELECT
TaskID = t.ID,
t.TaskDescription,
ScheduleID = s.ID,
ScheduleDueAt = s.DueAt,
a.TestID
FROM Task t
LEFT OUTER JOIN Schedule s
ON (s.TaskID = t.ID)
LEFT OUTER JOIN Audit a
ON (a.TaskID = t.ID)
WHERE t.ID = '1'
But of course, I get the wrong result:
+---------+-----------------+-------------+----------------+--------+
| Task.ID | TaskDescription | Schedule.ID | Schedule.DueAt | TestID |
+---------+-----------------+-------------+----------------+--------+
| 1 | Clean room | 927847 | 2020-08-01 | 3 |
| 1 | Clean room | 927847 | 2020-08-01 | 2 |
| 1 | Clean room | 927847 | 2020-08-01 | 1 |
+---------+-----------------+-------------+----------------+--------+
I'm going to use UNION for that but first wanted to ask maybe there is more right way how to do it.
You need to union all the schedule and audit tables and query nulls for the missing columns. Then, you can join that result with the task table:
SELECT t.id, t.taskdescription, s.id, s.dueat, s.testid
FROM task t
JOIN (SELECT taskid, id, dueat, NULL AS testid
FROM schedule
UNION ALL
SELECT taskid, NULL, NULL, testid
FROM audit) s ON t.id = s.taskid
I agree that using UNION ALL as #Mureinik suggested is probably your best option here, but just for fun, another alternative would be this.
If you added another entry to your audit table for each taskID with a TestID of 0 (sort of as a default whenever a new task is created), then it will allow you to join onto the audit table, without the need for UNION.
So your Audit table would look like this:
+--------+--------+
| TaskID | TestID |
+--------+--------+
| 1 | 0 |
| 2 | 0 |
| 1 | 3 |
| 1 | 2 |
| 1 | 1 |
| 2 | 4 |
+--------+--------+
Then you can modify your query to join the schedule table as normal, but only where the audit table value is 0.
And finally, to keep it tidy, use NULLIF to hide the 0 for that TestID if you wish:
Select
TaskID = t.ID,
t.TaskDescription,
ScheduleID = s.ID,
ScheduleDueAt = s.DueAt,
TestID= nullIF(a.TestID,0)
from
Task t
inner join
Audit a on
a.TaskID = t.ID
left join
Schedule s on
s.TaskID = t.ID
and a.TaskID = 0
where
t.ID = 1
UPDATE: You will also need an additional where clause for when there is no scheduled task, to prevent an empty row returning:
where
t.ID = 1
and not (s.TaskID is null and a.TestID = 0)

Issue in running an update query in access

I have a relational access database and I want to update a table based on another table. You can see relations in the picture. SQL statement is as bellow. When I try to update I face "Your query does not include the specified expression "TRX900" as part of an aggregate function."
But When I try to see in datasheet view mode, it is OK. Your support is appreciated.
Update
(
(
Sites INNER JOIN Cells ON Sites.ID = Cells.SiteID
) INNER JOIN Cells_2G ON Cells.ID = Cells_2G.[Cell ID]
) ,
ImportedTRX INNER JOIN ActiveStatus ON ImportedTRX.[Active Status] = ActiveStatus.Status
Set
Cells_2G.TRX900=Sum( IIF ( ImportedTRX.Frequency <=124 , 1,0 ) )
,
Cells_2G.TRX1800=Sum( IIF ( ImportedTRX.Frequency >=512 , 1,0 ) )
WHERE
(
ImportedTRX.[cell name]=[Sites].[SiteID] & [Cells].[Cell_Order]
AND
ActiveStatus.YesNo=True
)
;
Sites table sample:
-----------------------
| ID | SiteID |
-----------------------
| 1 | T4000X |
-----------------------
Cells table sample:
------------------------------------
| ID | SiteID | Cell_Order |
------------------------------------
| 1 | 1 | A |
| 2 | 1 | B |
| 3 | 1 | C |
------------------------------------
Cell_2G sample table:
------------------------------------------------------------
| ID | CellID | Expected TRX900 | Expected TRX1800 |
------------------------------------------------------------
| 1 | 1 | 1 | 2 |
| 2 | 2 | 2 | 1 |
| 3 | 3 | 2 | 3 |
------------------------------------------------------------
ImportedTRX table sample
-------------------------
| Cell Name | Frequency |
-------------------------
| T4000XA | 800 |
| T4000XA | 801 |
| T4000XA | 22 |
| T4000XB | 4 |
| T4000XB | 33 |
| T4000XB | 860 |
| T4000XC | 20 |
| T4000XC | 21 |
| T4000XC | 840 |
| T4000XC | 841 |
| T4000XC | 842 |
-------------------------
There are two approaches to this problem:
Create 2 queries, 1 preparing the result and 1 executing the update, where the
Parse using VBA.
I'm going to share the second approach.
The first query is essentially your current query converted to a SELECT query, only the table you're updating has been removed
Query Query1:
SELECT
Sum( IIF ( ImportedTRX.Frequency <=124 , 1,0 ) ) As TRX900
,
Sum( IIF ( ImportedTRX.Frequency >=512 , 1,0 ) ) As TRX1800,
Cells.ID
FROM
(
Sites INNER JOIN Cells ON Sites.ID = Cells.SiteID
),
ImportedTRX INNER JOIN ActiveStatus ON ImportedTRX.[Active Status] = ActiveStatus.Status
WHERE
(
ImportedTRX.[cell name]=[Sites].[SiteID] & [Cells].[Cell_Order]
AND
ActiveStatus.YesNo=True
)
GROUP BY
Cells.ID
;
Then, we're going to update the table using DLookUp and querying from that query:
Query Query2:
UPDATE Cells_2G
SET
Cells_2G.TRX900= DLookUp("TRX900", "Query1", "ID = " & [Cell ID]),
Cells_2G.TRX1800= DLookUp("TRX1800", "Query1", "ID = " & [Cell ID])
This produces the desired result, though you haven't included the ActiveStatus table so I couldn't include that in testing.
Unfortunately, the statement is too complex for me to write into a single update query, so this two-step approach is the best non-VBA solution I can come up with.
After some try I used following code, but it is slow yet.
UPDATE
ImportedTRX,
(
Sites INNER JOIN Cells ON Sites.ID=Cells.SiteID
) INNER JOIN Cells_2G ON Cells.ID= cells_2G.[Cell ID]
SET
Cells_2G.TRX900 = DSUM("IIF(Frequency<=124,1,0)", "ImportedTRX", "[Cell Name]='"& Sites.SiteID & Cells.Cell_Order & "'")
,
Cells_2G.TRX1800 = DSUM("IIF(Frequency>=512,1,0)", "ImportedTRX", "[Cell Name]='"& Sites.SiteID & Cells.Cell_Order & "'")
WHERE
(
ImportedTRX.[Cell Name]=Sites.[SiteID] & Cells.[Cell_Order]
);

Merging multiple rows according to an order

Suppose there are the following rows
| Id | MachineName | WorkerName | MachineState |
|----------------------------------------------|
| 1 | Alpha | Young | RUNNING |
| 1 | Beta | | STOPPED |
| 1 | Gamma | Foo | READY |
| 1 | Zeta | Zatta | |
| 2 | Guu | Niim | RUNNING |
| 2 | Yuu | Jaam | STOPPED |
| 2 | Nuu | | READY |
| 2 | Faah | Siim | |
| 3 | Iem | | RUNNING |
| 3 | Nyt | Fish | READY |
| 3 | Qwe | Siim | |
We want to merge these rows according to following priority :
STOPPED > RUNNING > READY > (null or empty)
If a row has a value for greatest priority, then value from that row should be used (only if it is not null). If it is null, a value from any other row should be used. The rows should be grouped by id
The correct output for the above input is :
| Id | MachineName | WorkerName | MachineState |
|----------------------------------------------|
| 1 | Beta | Foo | STOPPED |
| 2 | Yuu | Jaam | STOPPED |
| 3 | Iem | Fish | RUNNING |
What would be a good sql query to accomplish this? I tried using joins, but it did not work out.
You can view this as a case of the group-wise maximum problem, provided you can obtain a suitable ordering over your MachineState column—e.g. by using a CASE expression:
SELECT a.Id,
COALESCE(a.MachineName, t.MachineName) MachineName,
COALESCE(a.WorkerName , t.WorkerName ) WorkerName,
a.MachineState
FROM myTable a JOIN (
SELECT Id,
MIN(MachineName) AS MachineName,
MIN(WorkerName ) AS WorkerName,
MAX(CASE MachineState
WHEN 'READY' THEN 1
WHEN 'RUNNING' THEN 2
WHEN 'STOPPED' THEN 3
END) AS MachineState
FROM myTable
GROUP BY Id
) t ON t.Id = a.Id AND t.MachineState = CASE a.MachineState
WHEN 'READY' THEN 1
WHEN 'RUNNING' THEN 2
WHEN 'STOPPED' THEN 3
END
See it on sqlfiddle:
| id | machinename | workername | machinestate |
|----|-------------|------------|--------------|
| 1 | Beta | Foo | STOPPED |
| 2 | Yuu | Jaam | STOPPED |
| 3 | Iem | Fish | RUNNING |
You could save yourself the pain of using CASE if MachineState was an ENUM type column (defined in the appropriate order). It so happens in this case that a simple lexicographic ordering over the string value will yield the same result, but that's a coincidence on which you really shouldn't rely as it's bound to slip under the radar when someone tries to maintain this code in the future.
This is a prioritization query. One method uses variables. Another uses union all . . . this works if the states are not repeated for a given id:
select t.*
from table t
where machinestate = 'STOPPED'
union all
select t.*
from table t
where machinestate = 'RUNNING' and
not exists (select 1 from table t2 where t2.id = t.id and t2.machinestate in ('STOPPED'))
union all
select t.*
from table t
where machinestate = 'READY' and
not exists (select 1 from table t2 where t2.id = t.id and t2.machinestate in ('STOPPED', 'RUNNING'));
change MachineState as enum:
`MachineState` enum('READY','RUNNING','STOPPED') DEFAULT NULL
and sql is simple:
select t.id,state.machinename,state.workername,t.mstate from state,(select id,max(MachineState) mstate from state group by Id) t where t.mstate=state.machinestate and t.id=state.id;

Update an ordinal column based on the alphabetic ordering of another column

I have a table representing a system of folders and sub-folders with an ordinal m_order column.
Sometimes sub-folders are sorted alphanumerically, others are sorted by date or by importance.
I recently had to delete some sub-folders of a particular parent folder and add a few new ones. I also had to switch the ordering scheme to alphanumeric. This needed to be reflected in the m_order column.
Here's an example of the table:
+-----+-----------+-----------+------------+
| ID | parent | title | m_order |
+-----+-----------+-----------+------------+
| 100 | 1 | docs | 3 |
| 101 | 1 | reports | 2 |
| 102 | 1 | travel | 1 |
| 103 | 1 | weekly | 4 |
| 104 | 1 | briefings | 5 |
| ... | ... | ... | ... |
+-----+-----------+-----------+------------+
And here is what I want:
+-----+-----------+-----------+------------+
| ID | parent | title | m_order |
+-----+-----------+-----------+------------+
| 100 | 1 | docs | 3 |
| 101 | 1 | reports | 4 |
| 102 | 1 | travel | 5 |
| 200 | 1 | contacts | 2 |
| 201 | 1 | admin | 1 |
| ... | ... | ... | ... |
+-----+-----------+-----------+------------+
I would do this with a simple update:
with toupdate as (
select m.*, row_number() over (partition by parent order by title) as seqnum
from menu m
)
update toupdate
set m_order = toupdate.seqnum;
This restarts the ordering for each parent. If you have a particular parent in mind, use a WHERE clause:
where parentid = #parentid and m_order <> toupdate.seqnum
After deleting the old folders and inserting the new records, I accomplished the reordering by using MERGE INTO and ROW_NUMBER():
DECLARE #parentID INT
...
MERGE INTO menu
USING (
SELECT ROW_NUMBER() OVER (ORDER BY title) AS rowNumber, ID
FROM menu
WHERE parent = #parentID
) AS reordered
ON menu.ID = reordered.ID
WHEN MATCHED THEN
UPDATE
SET menu.m_order = reordered.rowNumber
I needed both t-sql and Oracle versions of this. To save future readers the struggle with the subtle differences in the ORA UPDATE syntax, here it is, shamelessly ripped off Gordon Linoff's answer:
update (
with toupdate as (
select
m.primarykey,
row_number() over(partition by parent order by title) as seqnum
from menu m
)
select m.primarykey, t.seqnum from menu m inner join toupdate t on t.primarykey=m.primarykey
)
set m_order = t.seqnum;

Using a SQL cursor. Worried about performance. Any way to do this with set operations?

I am working on a production table that constantly updates, it is in its infancy, but eventually there may be multiple insertions per minute to this table (and will likely reach millions of entries). I have no control over the table structure or how data is input.
I need to count the number of _COFF's made after a machine has the status of _SETUP. This count must be made at least every 15 minutes. The ID is used as a key in another table, and must be associated with the MachineNumber and PartNumber in the output. (sample output below)
Here is what i am working with:
'Production' table:
+---------------+---------------------+---------+------------+--------+
| MachineNumber | DateTime | Comment | PartNumber | Status |
+---------------+---------------------+---------+------------+--------+
| 1 | 11/11/2014 12:12:32 | | 104 | _SETUP |
| 1 | 11/11/2014 12:12:40 | 155 | 104 | _ID |
| 1 | 11/11/2014 12:12:45 | | 104 | _CON |
| 1 | 11/11/2014 12:16:45 | | 104 | _COFF |
| 1 | 11/11/2014 12:16:46 | | 104 | _CON |
| 1 | 11/11/2014 12:20:46 | | 104 | _COFF |
| 2 | 11/11/2014 12:20:50 | | 223 | _SETUP |
| 1 | 11/11/2014 12:21:00 | | 104 | _CON |
| 1 | 11/11/2014 12:23:00 | | 104 | _COFF |
| 2 | 11/11/2014 12:25:00 | 543 | 223 | _ID |
| 2 | 11/11/2014 12:25:20 | | 223 | _CON |
| 2 | 11/11/2014 12:26:20 | | 223 | _COFF |
... ... ... ... ...
+---------------+---------------------+---------+------------+--------+
Currently i use a cursor to get the following output:
+---------------+------------+-----+-------------+
| MachineNumber | DateTime | ID | _COFF Count |
+---------------+------------+-----+-------------+
| 1 | 11/11/2014 | 155 | 3 |
| 2 | 11/11/2014 | 543 | 1 |
+---------------+------------+-----+-------------+
Anyway to do this better than looping through (possibly) a million entries? What about deleting records from the table i have already looped through, and storing the output in another table?
EDIT: There will be only one _SETUP and one _ID per part per machine, there will more than one part per machine however (and therefore more than one _SETUP and _ID for each machine in the table).
The best thing would be creating a trigger which would reset the last setup date for a machine, then use it in an indexed view.
Assuming your table is called Status:
CREATE INDEX
IX_Status_Machine_Status_DateTime
ON Status (machineNumber, status, dateTime)
GO
CREATE UNIQUE INDEX
UX_Status_Machine_Part__Id
ON Status (machineNumber, partNumber)
WHERE status = '_ID'
CREATE TABLE
Status_LastSetup
(
machineNumber INT NOT NULL PRIMARY KEY,
partNumber INT NOT NULL,
lastSetup DATETIME NOT NULL
)
GO
CREATE TRIGGER
TR_Status_All
ON Status
AFTER INSERT, UPDATE, DELETE
AS
WITH c AS
(
SELECT machineNumber
FROM INSERTED
UNION ALL
SELECT machineNumber
FROM DELETED
),
t AS
(
SELECT sls.*
FROM c
JOIN Status_LastSetup sls
ON sls.machineNumber = c.machineNumber
),
s AS
(
SELECT *
FROM c
CROSS APPLY
(
SELECT TOP 1
partNumber, dateTime
FROM Status st
WHERE st.machineNumber = c.machineNumber
AND st.status = '_STATE'
ORDER BY
dateTime DESC
)
)
ON t.machineNumber = s.machineNumber
WHEN NOT MATCHED BY TARGET THEN
INSERT (machineNumber, partNumber, lastSetup)
VALUES (s.machineNumber, s.partNumber, s.dateTime)
WHEN MATCHED THEN
UPDATE
SET t.dateTime = s.dateTime,
t.partNumber = s.partNumber
WHERE EXISTS
(
SELECT t.dateTime, t.partNumber
EXCEPT
SELECT s.dateTime, s.partNumber
)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
GO
CREATE VIEW
V_Status_CoffCount
WITH SCHEMABINDING
AS
SELECT s.machineNumber,
COUNT_BIG(*) cnt
FROM dbo.Status_LastSetup sls
JOIN dbo.Status s
ON s.machineNumber = sls.machineNumber
AND s.status = '_COFF'
AND s.dateTime >= sls.lastSetup
GROUP BY
s.machineNumber
GO
CREATE UNIQUE CLUSTERED INDEX
UX_V_Status_CoffCount_Machine
ON V_Status_CoffCount (machineNumber)
GO
Once it's all up and running, just run this query:
SELECT s.machineNumber, sls.lastSetup, comment id, cnt
FROM V_Status_CoffCount scc WITH (NOEXPAND)
JOIN Status_LastSetup sls
ON sls.machineNumber = scc.machineNumber
LEFT JOIN
Status s
ON s.machineNumber = sls.machineNumber
AND s.partNumber = sls.partNumber
AND s.status = '_ID'
Its execution time is linear to the number of machines you have and (almost) does not depend on the number of entries in Status.
See this all on SQLFiddle: http://sqlfiddle.com/#!6/9671c/2
select table.machinenumber, timestamp.starttime, id.comment, count(*)
from table
join ( select MachineNumber, min(comment) as commment
from table
where Status = '_ID'
and comment is not null
group by MachineNumber ) as id
on id.MachineNumber = table.machinenumber
join ( select MachineNumber, min(datetime) as starttime
from table
where Status = '_SETUP'
group by MachineNumber ) as timestamp
on timestamp.ID = table.ID
and timestamp.starttime < table.datetime
and table.Status = '_CON'
group by table.machinenumber, timestamp.starttime, id.comment