I have a table like this one:
Yr | Mnth | W_ID | X_ID | Y_ID | Z_ID | Purchases | Sales | Returns |
2015 | 10 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2015 | 12 | 1 | 5210 | 1402 | 2 | 12000.00 | etc | etc |
2016 | 1 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2016 | 3 | 1 | 5210 | 1402 | 2 | etc | etc | etc |
2014 | 3 | 9 | 880 | 2 | 7 | etc | etc | etc |
2014 | 12 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 5 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 7 | 9 | 880 | 2 | 7 | etc | etc | etc |
For each combination of (W, X, Y, Z) I would like to insert the months that don't appear in the table and are between the first and last month.
In this example, for combination (W=1, X=5210, Y=1402, Z=2), I would like to have additional rows for 2015/11 and 2016/02, where Purchases, Sales and Returns are NULL. For combination (W=9, X=880, Y=2, Z=7) I would like to have additional rows for months between 2014/4 and 2014/11, 2015/01 and 2015/04, 2016/06.
I hope I have explained myself correctly.
Thank you in advance for any help you can provide.
The process is rather cumbersome in this case, but quite possible. One method uses a recursive CTE. Another uses a numbers table. I'm going to use the latter.
The idea is:
Find the minimum and maximum values for the year/month combination for each set of ids. For this, the values will be turned into months since time 0 using the formula year*12 + month.
Generate a bunch of numbers.
Generate all rows between the two values for each combination of ids.
For each generated row, use arithmetic to re-extract the year and month.
Use left join to bring in the original data.
The query looks like:
with n as (
select row_number() over (order by (select null)) - 1 as n -- start at 0
from master.spt_values
),
minmax as (
select w_id, x_id, y_id, z_id, min(yr*12 + mnth) as minyyyymm,
max(yr*12 + mnth) as maxyyyymm
from t
group by w_id, x_id, y_id, z_id
),
wxyz as (
select minmax.*, minmax.minyyyymm + n.n,
(minmax.minyyyymm + n.n) / 12 as yyyy,
((minmax.minyyyymm + n.n) % 12) + 1 as mm
from minmax join
n
on minmax.minyyyymm + n.n <= minmax.maxyyyymm
)
select wxyz.yyyy, wxyz.mm, wxyz.w_id, wxyz.x_id, wxyz.y_id, wxyz.z_id,
<columns from t here>
from wxyz left join
t
on wxyz.w_id = t.w_id and wxyz.x_id = t.x_id and wxyz.y_id = t.y_id and
wxyz.z_id = t.z_id and wxyz.yyyy = t.yr and wxyz.mm = t.mnth;
Thank you for your help.
Your solution works, but I noticed it is not very good in terms of performance, but meanwhile I have managed to get a solution for my problem.
DECLARE #start_date DATE, #end_date DATE;
SET #start_date = (SELECT MIN(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
SET #end_date = (SELECT MAX(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
DECLARE #tdates TABLE (Period DATE, Yr INT, Mnth INT);
WHILE #start_date <= #end_date
BEGIN
INSERT INTO #tdates(PEriod, Yr, Mnth) VALUES(#start_date, YEAR(#start_date), MONTH(#start_date));
SET #start_date = EOMONTH(DATEADD(mm,1,DATEFROMPARTS(YEAR(#start_date), MONTH(#start_date), 1)));
END
DECLARE #pks TABLE (W_ID NVARCHAR(50), X_ID NVARCHAR(50)
, Y_ID NVARCHAR(50), Z_ID NVARCHAR(50)
, PerMin DATE, PerMax DATE);
INSERT INTO #pks (W_ID, X_ID, Y_ID, Z_ID, PerMin, PerMax)
SELECT W_ID, X_ID, Y_ID, Z_ID
, MIN(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMin
, MAX(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMax
FROM Table1
GROUP BY W_ID, X_ID, Y_ID, Z_ID;
INSERT INTO Table_Output(W_ID, X_ID, Y_ID, Z_ID
, ComprasLiquidas, RTV, DevManuais, ComprasBrutas, Vendas, Stock, ReceitasComerciais)
SELECT TP.DB, TP.Ano, TP.Mes, TP.Supplier_Code, TP.Depart_Code, TP.BizUnit_Code
, TA.ComprasLiquidas, TA.RTV, TA.DevManuais, TA.ComprasBrutas, TA.Vendas, TA.Stock, TA.ReceitasComerciais
FROM
(
SELECT W_ID, X_ID, Y_ID, Z_ID
FROM #tdatas CROSS JOIN #pks
WHERE Period BETWEEN PerMin And PerMax
) AS TP
LEFT JOIN Table_Input AS TA
ON TP.W_ID = TA.W_ID AND TP.X_ID = TA.X_ID AND TP.Y_ID = TA.Y_ID
AND TP.Z_ID = TA.Z_ID
AND TP.Yr = TA.Yr
AND TP.Mnth = TA.Mnth
ORDER BY TP.W_ID, TP.X_ID, TP.Y_ID, TP.Z_ID, TP.Yr, TP.Mnth;
I do the following:
Get the Min and Max date of the entire table - #start_date and #end_date variables;
Create an auxiliary table with all dates between Min and Max - #tdates table;
Get all the combinations of (W_ID, X_ID, Y_ID, Z_ID) along with the min and max dates of that combination - #pks table;
Create the cartesian product between #tdates and #pks, and in the WHERE clause I filter the results between the Min and Max of the combination;
Compute a LEFT JOIN of the cartesian product table with the input data table.
Related
How to create a table with columns having date,hour as a separate fields for a period of time say 10 years from 1st Jan 2010 to 1st Jan 2020.
I have created calendar table referring to url :
click here
It generates only dates,day,week,month but not time.
I want to generate new column with the value as 0,...23 hours for every day.
You can achieve it an another easy way as follows
;WITH CTE AS (
SELECT NUMBER FROM master.dbo.spt_values where TYPE='P' AND NUMBER BETWEEN 1 AND 2047
)
, YEARS AS(
SELECT NUMBER+2009 AS Y FROM CTE WHERE NUMBER BETWEEN 1 AND 11
)
,MONTHS AS (
SELECT NUMBER AS M FROM CTE WHERE NUMBER BETWEEN 1 AND 12
)
,DAYSS AS(
SELECT NUMBER AS D FROM CTE WHERE NUMBER BETWEEN 1 AND 31
)
,HRS AS (
SELECT NUMBER AS H FROM CTE WHERE NUMBER BETWEEN 1 AND 24
)
SELECT * FROM YEARS Y
CROSS APPLY MONTHS M
CROSS APPLY (SELECT D FROM DAYSS
WHERE D<= DATEDIFF(DD,DATEFROMPARTS(Y.Y, M.M,01),DATEADD(MM,1, DATEFROMPARTS(Y.Y, M.M,01))))D
CROSS APPLY HRS H
ORDER BY Y.Y, M.M, D.D, H.H
Sample Result:
+------+---+----+----+
| Y | M | D | H |
+------+---+----+----+
| -- | - | -- | -- |
| 2020 | 2 | 29 | 21 |
| 2020 | 2 | 29 | 22 |
| 2020 | 2 | 29 | 23 |
| 2020 | 2 | 29 | 24 |
| 2020 | 3 | 1 | 1 |
| 2020 | 3 | 1 | 2 |
| 2020 | 3 | 1 | 3 |
| -- | --| -- | -- |
+------+---+----+----+
Note: DATEFROMPARTS function will work only with SQL Server 2012 and
above. You can replace it by concatinating them and converting to date
field for lower versions.
Either create another table that holds 24 rows for each hour of the day or use a derived table like so
SELECT
H.[Hour]
,D.[Date]
FROM
dbo.DateDimension D
CROSS APPLY
( --Derived table with 24 rows
SELECT
[Hour] = V.number
FROM
master..spt_values V
WHERE
V.type = 'P'
AND
V.number >= 1 AND V.number <= 24
) H
WHERE
[D].[Date] = '2000-01-01'
Output
Create a time table as well as your Calendar table. How specific you get with your times is up to you (for example, mine has a value for every second in a day). Then, simple JOIN to too:
SELECT * --Yes, * is bad, but I don't know what the OP's Columns are.
--The OP will need to replace the * with the appropriate columns
FROM CalendarTable CT
JOIN TimeTable TT ON TT.[Minutes] = 0 AND TT.[Seconds] = 0
WHERE CT.DateValue >= '20100101'
AND CT.DateValue < '20200102';
The filter means that only the Hour values would be returned.
Also, it's worth noting there is no hour 24 in a day. The hours are 0 - 23, not 1-24.
I would do it like this:
--set start variables
declare #period_length_days as int = 21
declare #start_dt as datetime = getdate()
declare #end_dt as datetime = dateadd(day,#period_length_days,getdate())
--declare table
declare #my_calender table (recid int identity(1,1),
my_datetime datetime,
my_year int,
my_month int,
my_day int,
my_hour int)
--of course you can add multiple more: iso_wk, minute, second etc etc
--fill table
declare #loop_dt as datetime = #start_dt
--loop from start_dt to end_dt, by adding 1 hour each cycle, until loop_dt = end_dt
while #loop_dt <> #end_dt
begin
insert into #my_calender
select
#loop_dt
, datepart(year,#loop_dt)
, datepart(month,#loop_dt)
, datepart(day,#loop_dt)
, datepart(hour,#loop_dt)
set #loop_dt = dateadd(hour,1,#loop_dt)
end
select * from #my_calender
+-------+-------+-----------+
| EmpID | PerID | VisitDate |
+-------+-------+-----------+
| 1 | 22 | 2/24/2017 |
| 1 | 22 | 3/25/2017 |
| 1 | 22 | 4/5/2017 |
| 2 | 33 | 5/6/2017 |
| 2 | 33 | 8/9/2017 |
| 2 | 33 | 6/7/2017 |
+-------+-------+-----------+
I am trying to find the latest visit date and average days between visits per EmpID. For Avg, I'll first have to order the days consecutively and then find the average.
Eg: Avg. days for EmpID=1 and PerID=22 would be [29(Days between 3/25 and 2/24) + 11 (Days between 3/25 and 4/5)/2] = 20 Days.
Desired Output:
+-------+-------+----------+----------+
| EmpID | PerID | MaxVDate | AvgVDays |
+-------+-------+----------+----------+
| 1 | 22 | 4/5/2017 | 20 |
| 2 | 33 | 8/9/2017 | 47.5 |
+-------+-------+----------+----------+
Attempt:
SELECT
EmpID
,PerID
,MAX(VisitDate) AS MaxVDate
,--Dunno how to find average AS AvgVDays
FROM
T1
GROUP BY
EmpID
,PerID
You can use lag to get the previous date and compute the date difference. Then use avg window function to get the average days.
Select distinct empid,perid,maxVdate,avg(diff_with_prev) OVER(Partition by empid) as avgVDays
from (
SELECT EmpID,PerID
,MAX(VisitDate) OVER(Partition BY EmpID) AS MaxVDate
,DATEDIFF(DAY,LAG(VisitDate) OVER(Partition BY EmpID order by VisitDate), VisitDate) as diff_with_prev
FROM T1
) t
Here's an option...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
EmpID INT NOT NULL,
PerID INT NOT NULL,
VisitDate DATE NOT NULL
);
INSERT #TestData (EmpID, PerID, VisitDate) VALUES
(1, 22, '2/24/2017'),
(1, 22, '3/25/2017'),
(1, 22, '4/5/2017'),
(2, 33, '5/6/2017'),
(2, 33, '8/9/2017'),
(2, 33, '6/7/2017');
-- SELECT * FROM #TestData td;
SELECT
db.EmpID,
db.PerID,
AvgDays = AVG(db.DaysBetween * 1.0)
FROM (
SELECT
*,
DaysBetween = DATEDIFF(dd, LAG(td.VisitDate, 1) OVER (PARTITION BY td.EmpID, td.PerID ORDER BY td.VisitDate), td.VisitDate)
FROM
#TestData td
) db
GROUP BY
db.EmpID,
db.PerID;
Results...
EmpID PerID AvgDays
----------- ----------- ---------------------------------------
1 22 20.000000
2 33 47.500000
The task is much easier than you think. You get the average with (last visit - first visit) / (count visits - 1).
select
empid,
perid,
max(VisitDate) as MaxVDate,
datediff(day, min(VisitDate), max(VisitDate)) * 1.0 / (count(*) - 1) as avgvdays
from mytable
group by empid, perid
having count(*) > 1
order by empid, perid;
The multiplication with 1.0 is necessary in order to avoid integer division. (You could also cast to decimal instead.)
As the calcualtion only makes sense for empid/perid pairs with more than one entry (and in order to avoid division by zero), I have applied an according HAVING clause.
Here is a test: http://rextester.com/AIFPA62612
I have a records look like below
From two rows, I want to split ShiftPattern values and create multiple records and StartWeek will be created sequentially.
Final Query:
Split ShiftPattern Column and Create multiple records
Increase StartWeek like as 20, 21 to rotation.
Output result
This is what you need. Tested in fiddle.
SQLFiddle Demo
select q.locationid,q.employeeid,
case
when (lag(employeeid,1,null) over (partition by employeeid order by weekshiftpatternid)) is null
then startweek
else startweek + 1
end as rotation ,
q.weekshiftpatternid,
q.shiftyear
from
(
select locationid,employeeid, left(d, charindex(',', d + ',')-1) as weekshiftpatternid ,
startweek,shiftyear
from (
select *, substring(shiftpattern, number, 200) as d from MyTable locationid left join
(select distinct number from master.dbo.spt_values where number between 1 and 200) col2
on substring(',' + shiftpattern, number, 1) = ','
) t
) q
Output
+------------+------------+----------+--------------------+-----------+
| locationid | employeeid | rotation | weekshiftpatternid | shiftyear |
+------------+------------+----------+--------------------+-----------+
| 1 | 10000064 | 20 | 1006 | 2016 |
| 1 | 10000064 | 21 | 1008 | 2016 |
| 1 | 10000065 | 20 | 1006 | 2016 |
| 1 | 10000065 | 21 | 1008 | 2016 |
+------------+------------+----------+--------------------+-----------+
Similar:
In my test table my ID is your EmployeeID or however you want to work it.
SELECT
*,
LEFT(shiftBits, CHARINDEX(',', shiftBits + ',')-1) newShiftPattern,
StartWeek+ROW_NUMBER() OVER(PARTITION BY ID ORDER BY shiftBits ) as newStartWeek
FROM
(
SELECT
SUBSTRING(shiftPattern, number, LEN(shiftPattern)) AS shiftBits,
test2.*
FROM
test2,master.dbo.spt_values
WHERE
TYPE='P' AND number<LEN(shiftPattern)
AND SUBSTRING(',' + shiftPattern, number, 1) = ','
) AS x
I want to compare the current row with a value in the next row. SQL has LEAD and LAG functions to get the next and previous values but I can not use them because I am using SQL Server 2008.
So how do I get this?
I have table with output
+----+-------+-----------+-------------------------+
| Id | ActId | StatusId | MinStartTime |
+----+-------+-----------+-------------------------+
| 1 | 42 | 1 | 2014-02-14 11:17:21.203 |
| 2 | 42 | 1 | 2014-02-14 11:50:19.367 |
| 3 | 42 | 1 | 2014-02-14 11:50:19.380 |
| 4 | 42 | 6 | 2014-02-17 05:25:57.280 |
| 5 | 42 | 6 | 2014-02-19 06:09:33.150 |
| 6 | 42 | 1 | 2014-02-19 06:11:24.393 |
| 7 | 42 | 6 | 2014-02-19 06:11:24.410 |
| 8 | 42 | 8 | 2014-02-19 06:44:47.070 |
+----+-------+-----------+-------------------------+
What I want to do is if the current row status is 1 and the next row status is 6 and both times are the same (up to minutes) then I want to get the row where the status is 1.
Eg: Id 6 row has status 1 and Id 7 row has status 6 but both times are the same ie. 2014-02-19 06:11
So I want to get this row or id for status 1 ie. id 6
In your case, the ids appear to be numeric, you can just do a self-join:
select t.*
from table t join
table tnext
on t.id = tnext.id - 1 and
t.StatusId = 1 and
tnext.StatusId = 6 and
datediff(second, t.MinStartTime, tnext.MinStartTime) < 60;
This isn't quite the same minute. It is within 60 seconds. Do you actually need the same calendar time minute? If so, you can do:
select t.*
from table t join
table tnext
on t.id = tnext.id - 1 and
t.StatusId = 1 and
tnext.StatusId = 6 and
datediff(second, t.MinStartTime, tnext.MinStartTime) < 60 and
datepart(minute, t.MinStartTime) = datepart(minute, tnext.MinStartTime);
Just posting a more complex join using two different tables created with Gordon's foundation. Excuse the specific object names, but you'll get the gist. Gets the percentage change in samples from one to the next.
SELECT
fm0.SAMPLE curFMSample
, fm1.SAMPLE nextFMSample
, fm0.TEMPERATURE curFMTemp
, fm1.TEMPERATURE nextFMTemp
, ABS(CAST((fm0.Temperature - fm1.Temperature) AS DECIMAL(4, 0)) / CAST(fm0.TEMPERATURE AS DECIMAL(4, 0))) AS fmTempChange
, fm0.GAUGE curFMGauge
, fm1.GAUGE nextFMGauge
, ABS(CAST((fm0.GAUGE - fm1.GAUGE) AS DECIMAL(4, 4)) / CAST(fm0.GAUGE AS DECIMAL(4, 4))) AS fmGaugeChange
, fm0.WIDTH curFMWidth
, fm1.WIDTH nextFMWidth
, ABS(CAST((fm0.Width - fm1.Width) AS DECIMAL(4, 2)) / CAST(fm0.Width AS DECIMAL(4, 2))) AS fmWidthChange
, cl0.TEMPERATURE curClrTemp
, cl1.TEMPERATURE nextClrTemp
, ABS(CAST((cl0.Temperature - cl1.Temperature) AS DECIMAL(4, 0)) / CAST(cl0.TEMPERATURE AS DECIMAL(4, 0))) AS clrTempChange
FROM
dbo.COIL_FINISHING_MILL_EXIT_STR02 fm0
INNER JOIN dbo.COIL_FINISHING_MILL_EXIT_STR02 fm1 ON (fm0.SAMPLE = fm1.SAMPLE - 1 AND fm1.coil = fm0.coil)
INNER JOIN dbo.COIL_COILER_STR02 cl0 ON fm0.coil = cl0.coil AND fm0.SAMPLE = cl0.SAMPLE
INNER JOIN dbo.COIL_COILER_STR02 cl1 ON (cl0.SAMPLE = cl1.SAMPLE - 1 AND cl1.coil = cl0.coil)
WHERE
fm0.coil = 2015515872
Well, I would suggest a very simple solution if you do not have a sequential row id but a different step (if some records were deleted for example..):
declare #t table(id int, obj_name varchar(5))
insert #t select 1,'a'
insert #t select 5,'b'
insert #t select 22,'c'
insert #t select 543,'d'
---------------------------------
select *from #t
Example Source Table #t:
---------------------------------
id obj_name
1 a
5 b
22 c
543 d
---------------------------------
Select with self join
select obj_name_prev=tt.obj_name, obj_name_next=min(t.obj_name)
from #t t
join #t tt on tt.id < t.id
group by tt.obj_name
Result:
---------------------------------
obj_name_prev obj_name_next
a b
b c
c d
---------------------------------
I need help on something that seems to be complex to me.
I made a query to create a tbl1 which is the Cartesian product of the tables Item and Warehouse. It give’s me back all items in all warehouses:
SELECT i.ItemID, w.WarehouseID
FROM Item i, Warehouse w
I made a second query (tbl2) where I check the date of the last document previous or equal to a variable date (#datevar) and whose quantity rule is 1 (PhysicalQtyRule = 1), this by Item and Warehouse, obtained from StockHistory table
SELECT MAX(CreateDate) AS [DATE1], ItemID, Quantity, WarehouseID
FROM StockHistory
WHERE PhysicalQtyRule = 1 AND CreateDate <= #datevar
GROUP BY ItemID, Quantity, WarehouseID
Now, I need more three steps:
Build a third table containing per item and warehouse the sum of quantity, but the quantity rule is 2 (PhysicalQtyRule = 2) and date between tbl2.date (if exists) and the date of the variable #datevar, obtained from the table StockHistory. Something like that:
SELECT ItemID, WarehouseID, SUM(Quantity)
FROM StockHistory
WHERE PhysicalQtyRule = 2 AND CreateDate > tbl2.DATE1 --If exists
AND CreateDate <= #datevar
GROUP BY ItemID, WarehouseID
Build a fourth table containing per item and warehouse the sum of quantity, but the quantity rule is 3 (PhysicalQtyRule = 3) and date between tbl2.date (if any) and the date of the variable #datevar, obtained from the table StockHistory. Something like that:
SELECT ItemID, WarehouseID, SUM(Quantity)
FROM StockHistory
WHERE PhysicalQtyRule = 3 AND CreateDate > tbl2.DATE1 --If exists
AND CreateDate <= #datevar
GROUP BY ItemID, WarehouseID
Create a final table based on the first one, with an sum quantity column, something like that:
SELECT i.ItemID, w.WarehouseID, tbl2.Quantity + tbl3.Quantity – tbl4.Quantity AS [Qty]
FROM Item i, Warehouse w
I don't know if need cursors (something new for me) or multiple querys, but it's important the best performance because my StockHistory table have millions of records.
Can anyone help-me please? Thank you!
Some sample data, only for one Item and one warehouse:
+--------+-------------+------------+-----------------+----------+
| ItemID | WarehouseID | CreateDate | PhysicalQtyRule | Quantity | Balance | comments
+--------+-------------+------------+-----------------+----------+
| 1234 | 11 | 2013-03-25 | 2 | 35 | 35 | Rule 2 = In
| 1234 | 11 | 2013-03-28 | 3 | 30 | 5 | Rule 3 = Out
| 1234 | 11 | 2013-04-01 | 1 | 3 | 3 | Rule 1 = Reset
| 1234 | 11 | 2013-07-12 | 2 | 40 | 43 | Rule 2 = In
| 1234 | 11 | 2013-09-05 | 3 | 20 | 23 | Rule 3 = Out
| 1234 | 11 | 2013-12-31 | 1 | 25 | 25 | Rule 1 = Reset
| 1234 | 11 | 2014-01-09 | 3 | 11 | 14 | Rule 3 = Out
| 1234 | 11 | 2014-01-16 | 3 | 6 | 8 | Rule 3 = Out
I want to know the balance on any variable date.
Without your data, I can't test this but I believe this should be your solution.
SELECT i.ItemID
,w.WarehouseID
,[Qty] = tbl2.Quantity + tbl3.Quantity – tbl4.Quantity
FROM Item i
CROSS JOIN Warehouse w
OUTER APPLY (
SELECT [DATE1] = MAX(sh.CreateDate)
,sh.ItemID
,sh.Quantity
,sh.WarehouseID
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 1 AND sh.CreateDate <= #datevar
AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.Quantity, sh.WarehouseID ) tbl2
OUTER APPLY (
SELECT sh.ItemID
,sh.WarehouseID
,[Quantity] = SUM(sh.Quantity)
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 2 AND sh.CreateDate > tbl2.DATE1 --If exists
AND sh.CreateDate <= #datevar AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.WarehouseID ) tbl3
OUTER APPLY (
SELECT sh.ItemID
,sh.WarehouseID
,[Quantity] = SUM(sh.Quantity)
FROM StockHistory sh
WHERE sh.PhysicalQtyRule = 3 AND sh.CreateDate > tbl2.DATE1 --If exists
AND sh.CreateDate <= #datevar AND i.ItemID = sh.ItemID
AND w.WarehouseID = sh.WarehouseID
GROUP BY sh.ItemID, sh.WarehouseID ) tbl4