Modify and add new records to an Access database - sql

We have a MS Access 2016 database. We queried the DB to find some parts in inventory. The first few rows of the query results are below. We have a primary key called DBID with a unique number for all of our records in the original table.
ORIGINAL QUERY:
SELECT [Slash Series Query].BRAND, [Slash Series Query].[PRODUCT ID], [Slash Series Query].[STOCK QUANTITY] FROM [Slash Series Query] WHERE ((([Slash Series Query].[PRODUCT ID]) Not Like "k"));
RESULTS:
Slash Straight Bore Query
BRAND PRODUCT ID
FAG 230/500 BMB H14C W33
SKF 230/500 CA C08 W509
ZKL 230/500 MW33
KOY 230/500 RW513 SOFY
KOY 230/500R W33 C3 FY
FAG 230/530 MBR50150H78CW209C
FAG 230/530 C3 H40AC
NSK 230/530CAM E4 C3 S11
FAG 230/600 BM C3 W33 T52BW
FAG 230/600 C08W503
SKF 230/600 CA C08 W509
FAG 239/530 MBH40.T52BW
FAG 239/530MB
SKF 248/530 CAMA/C3/W20
TOR 249/850 W33 W45 CW57 C2
We would like to insert a solution that adds a new record to the database with a “K” in the part number and also adds a “_(MOD)” to the end of the part number, in addition to the existing part number, so it would look like this:
(Brand = 3 letter manufacturer code, Product ID = part number)
Slash Tapered Bore Query
BRAND PRODUCT ID
FAG 230/500 K BMB H14C W33 (MOD)
SKF 230/500 K CA C08 W509 (MOD)
ZKL 230/500 K MW33 (MOD)
KOY 230/500 K RW513 SOFY (MOD)
KOY 230/500 K R W33 C3 FY (MOD)
FAG 230/530 K MBR50150H78CW209C (MOD)
FAG 230/530 K C3 H40AC (MOD)
NSK 230/530 K CAM E4 C3 S11 (MOD)
FAG 230/600 K BM C3 W33 T52BW (MOD)
FAG 230/600 K C08W503 (MOD)
SKF 230/600 K CA C08 W509 (MOD)
FAG 239/530 K MBH40.T52BW (MOD)
FAG 239/530 K MB (MOD)
SKF 248/530 K CAMA/C3/W20 (MOD)
TOR 249/850 K W33 W45 CW57 C2 (MOD)
What would be the most efficient way to accomplish this?

You could try the MS Access substring and concatenation operators:
SELECT S.BRAND,
Mid(S.[PRODUCT ID], 1, 7) & " K" & Mid(S.[PRODUCT ID], 8) & " (MOD)" as [PRODUCT ID],
S.[STOCK QUANTITY]
FROM [Slash Series Query] S WHERE (S.[PRODUCT ID] Not Like "k");
I've used the table alias instead of the whole table name to reduce the query's size.
EXPLANATION:
Mid(columnName, starting char position, [optional last character position])
returns that part of the column value from the starting char position to the last character position if specified(end of the string if not specified.)
"a" & "b" & "c" uses the MS access concatenation operator & and results in "abc"

If you just want to select the values:
SELECT ssq.BRAND, ssq.[PRODUCT ID], ssq.[STOCK QUANTITY],
("K" & ssq.[PART NUMBER] & " (MOD)") as [PRODUCT ID]
FROM [Slash Series Query] as ss1q
WHERE ssq.[PRODUCT ID] Not Like "k";
If you want to insert this into the database, then use INSERT . . . SELECT.

Related

Manipulating SQL table

I have a table, the structure of which I have simplified to the smaller table below.
I want to manipulate the dataset below into the following form:
The new dataset will contain a single record for each case of DC, with a yes/no flag indicating if the NatureOfTumour has changed from DC to IN, and the time taken to change from DC to IN if applicable.
The change from DC to IN will be considered only if location has remained the same i.e. only those records should be considered where NatureOfTumour has changed from DC to IN and the location remained the same. ItemNo is the unique ID.
On a community member's advice I have pasted the table in text below as well, cleaned up as best as I could. The last column "Gen" is empty. ItemNo is the unique ID. Copying the text below to excel and doing a text-to-columns (separated by spaces) should give you the original table in a readable format. Sorry cant think of a better way to paste the table here.
ItemNo DateOfTest NatureOfTumour Location Centre Gen
2345 07/2006 DC P S-224
2345 12/2006 IN P S-224
2342 05/2004 DC Q B-266
3878 06/2006 DC P S-224
3878 05/2005 DC Q S-224
5678 09/2000 IN P S-224
5597 10/2001 DC P B-266
5597 01/1999 IN Q B-266
Try this. The LEAD function looks at the next row based on groups of ItemNo ordered by DateOfTest.
WITH abc AS (
SELECT
ItemNo
,DateOfTest
,NatureOfTumour
,Location
,Centre
,LEAD(NatureOfTumour) OVER (PARTITION BY ItemNo ORDER BY DateOfTest) as FutureNature
,LEAD(Location) OVER (PARTITION BY ItemNo ORDER BY DateOfTest) as FutureLocation
,LEAD(DateOfTest) OVER (PARTITION BY ItemNo ORDER BY DateOfTest) as FutureDateOfTest
FROM test_results
)
SELECT
ItemNo
,DateOfTest
,NatureOfTumour
,CASE
WHEN FutureNature = 'IN'
AND FutureLocation = Location
THEN 'Yes'
ELSE 'NO'
END AS State_Change
,FutureDateOfTest - DateOfTest as Date_Diff
,Location
,Centre
from abc
WHERE NatureOfTumour = 'DC'
You need a self join. Something along these lines:
SELECT
d.ItemNo,
i.DateOfTest - d.DateOfTest AS datediff,
d.Location,
d.Centre,
d.Gen
FROM
(
SELECT
*
FROM demo
WHERE NatureOfTumour = 'DC'
) d
INNER JOIN
(
SELECT
*
FROM demo
WHERE NatureOfTumour = 'IN'
) i ON d.ItemNo = i.ItemNo
AND d.Location = i.Location;
If I understood your question, you could try this:
Let me know .
If you want in output only the rows who changed (GEN='Y'), change LEFT JOIN to INNER JOIN.
SELECT A.ITEMNO, A.DATEOFTEST, A.NATUREOFTUMOUR, A.LOCATION
, CASE WHEN B.NATUREOFTUMOUR='IN' AND A.LOCATION = B.LOCATION THEN 'Y' ELSE 'N' END AS GEN_NEW
, CASE WHEN B.NATUREOFTUMOUR='IN' AND A.LOCATION = B.LOCATION THEN B.DATEOFTEST-A.DATEOFTEST END AS TIME_PASS
FROM TE A
LEFT JOIN TE B ON A.ITEMNO=B.ITEMNO AND B.NATUREOFTUMOUR<>'DC' AND A.DATEOFTEST < B.DATEOFTEST
WHERE A.NATUREOFTUMOUR='DC
OR (I can't understand from your question)
SELECT A.ITEMNO, A.DATEOFTEST, A.NATUREOFTUMOUR, A.LOCATION
, CASE WHEN B.NATUREOFTUMOUR='IN' THEN 'Y' ELSE 'N' END AS GEN_NEW
, CASE WHEN B.NATUREOFTUMOUR='IN' THEN B.DATEOFTEST-A.DATEOFTEST END AS TIME_PASS
FROM TE A
LEFT JOIN TE B ON A.ITEMNO=B.ITEMNO AND B.NATUREOFTUMOUR<>'DC' AND A.DATEOFTEST < B.DATEOFTEST AND A.LOCATION = B.LOCATION
WHERE A.NATUREOFTUMOUR='DC'\\
Output
ITEMNO DATEOFTEST NATUREOFTUMOUR LOCATION GEN_NEW TIME_PASS
1 2345 01.07.2006 DC P Y 153
2 2342 01.06.2006 DC Q N NULL
3 5597 01.10.2001 DC P N NULL
4 3878 01.05.2005 DC Q N NULL
5 3878 01.06.2006 DC P N NULL

SQL join in the where statement

I am currently having to use two different queries to get back the results that I'm looking for. I have tried combining the two queries together, but that ends with me getting a large amount of extra(duplicates) data that I do not need. Below I have the working query listed.
SELECT p1.note as Itemcode,
order.ID as OrderNo,
piece1.ID As Piece1,
piece2.ID As Piece2,
i1.count as Unit,
unit.count as TotalUnits,
i1.rack as RackNo,
p1.EndDate as Piece1Finish,
p2.EndDate as Piece2Finish,
unit.group as BatchNo
FROM db.dbo.unit
JOIN db.dbo.order on order.entry_ID = unit.entry_ID
JOIN db.dbo.piece piece1 on piece1.ID_piece = unit.ID_piece_1
JOIN db.dbo.piece piece2 on piece2.ID_piece = unit.ID_piece_3
JOIN db.dbo.items i1 on i1.ID_unit = unit.ID_unit
JOIN db.dbo.items i2 on i2.ID_unit = unit.ID_unit
JOIN db.dbo.items i3 on i3.ID_unit = unit.ID_unit
JOIN db.dbo.items i4 on i4.ID_unit = unit.ID_unit
JOIN db.dbo.process p1 on p1.ID_process = i1.ID_process
JOIN db.dbo.process p2 on p2.ID_process = i2.ID_process
JOIN db.dbo.process p3 on p3.ID_process = i3.ID_process
JOIN db.dbo.process p4 on p4.ID_process = i4.ID_process
WHERE p1.note like '12A%'
and p1.ID_pieceorder = '1'
and p1.ID_job = '150'
and p2.ID_pieceorder = '3'
and p2.ID_job = '150'
and i1.count = i2.count
and i1.count = i3.count
and i1.count = i4.count
and i1.rack = i2.rack
and p1.note = p2.note
and i1.status = '1'
and i2.status = '1'
and p3.ID_pieceorder = '0'
and p4.ID_pieceorder = '2'
and p3.ID_job = '153'
and p4.ID_job = '151'
and i3.status = '0'
and i4.status = '0'
and order.status <> '4'
ORDER BY OrderNo
This query works fine. The second set of data I query to find adds the following information
SELECT ...(same as above)
FROM ...(same as above plus the following)
JOIN db.dbo.items i5 on i5.ID_unit = unit.ID_unit
JOIN db.dbo.items i6 on i6.ID_unit = unit.ID_unit
JOIN db.dbo.process p5 on p5.ID_process = i5.ID_process
JOIN db.dbo.process p6 on p6.ID_process = i6.ID_process
WHERE p1.note like '12B%'
and ... (same as above plus the following)...
and p5.ID_pieceorder = '1'
and p5.ID_job = '152'
and p6.ID_pieceorder = '3'
and p6.ID_job = '152'
and i5.status = '1'
and i6.status = '1'
and i1.count = i5.count
and i1.count = i6.count
When I try to make a combined query, the table joins of i5, i6, p5, and p6 produce a massive amount of duplicated results for p1.note like '12A%' due to it not needing the fields. Is there a method where I can initiate a join in the WHERE statement so that it will only use those two tables when p1.note like '12B%'? Something along the lines of
SELECT ....
FROM ....
WHERE (p1.note like '12A%'
or
(p1.note like '12B%'
and p5.ID_pieceorder = '1'
and p5.ID_job = '152'
and p6.ID_pieceorder = '3'
and p6.ID_job = '152'
and i5.status = '1'
and i6.status = '1'
and i1.count = i5.count
and i1.count = i6.count
(JOIN db.dbo.items i5 on i5.ID_unit = unit.ID_unit
JOIN db.dbo.items i6 on i6.ID_unit = unit.ID_unit
JOIN db.dbo.process p5 on p5.ID_process = i5.ID_process
JOIN db.dbo.process p6 on p6.ID_process = i6.ID_process)))
I know that the syntax above will not work, but I am looking for a method similar to that.
**EDIT FOR TABLE STRUCTURE requested by DRapp
I'm going to fill out the results I get with ID_unit 782327
-db.dbo.items-
-ID_item- -ID_process- -count- -status- -rack- -ID_unit-
628335 782328 1 0 25 782327
628336 782330 1 1 25 782327
628337 782331 1 1 25 782327
628338 782333 1 0 25 782327
628339 782335 1 1 25 782327
628340 782336 1 1 25 782327
628341 782337 1 0 25 782327
-db.dbo.process-
-ID_process- -ID_unit- -ID_pieceorder- -ID_job- -sequence-
782328 782327 0 50 1
782329 782327 1 5305 1
782330 782327 1 150 1
782331 782327 1 152 2
782332 782327 2 5408 2
782333 782327 2 151 1
782334 782327 3 5308 3
782335 782327 3 150 1
782336 782327 3 152 2
782337 782327 0 153 4
-db.dbo.unit-
-ID_unit- -status- -ID_piece_1- -ID_piece_2--ID_piece_3--ID_product-count
782327 2 5305 5408 5308 50 1
db.dbo.items contains a unique key for each "item" that goes into a part, it contains the corresponding process code, the "count" field, the "status" of each "item", the rack for each "item", and the unit ID of each "ITEM".
db.dbo.process contains the unique key for each "process", the ID_unit, ID_pieceorder, ID_job, and the sequence. Each unit starts with ID_pieceorder 0 which is establishing the product type, then ID_piece 1 for the first piece, etc..
db.dbo.unit contains the unique key for each "unit", the codes for all its pieces, the status of the unit, and count which is the number of units.
db.dbo.piece is a table with the master list of codes on it.
-ID_Piece- -Desc-
5305 14black
5408 14blue
150 Cut
The point of needing 6 processes is as follows
p1 & i1: Making sure the Piece is Piece 1, is process 150, and that process 150 is complete
p2 & i2: Making sure the piece is piece 2, is process 150, and that process 150 is complete
p3 & i3: Making sure it the completion process, 153, is not completed.
p4 & i4: making sure the connector process, 152, isn't completed(hasn't connected the two pieces).
p5 & i5: some pieces require extra work done, this checks and makes sure that process, 152, is completed on piece 1.
P6 & i6: The same as p5 & i5 but for piece 2.
if you don't want duplicates use a Union in between them, move you order by clause to the bottom of the second one. Make sure you select columns are in the same order in both statements.
select (your columns)
From (tables and joins)
where (your where)
UNION
select (your columns)
From (tables and joins)
where (your where)
order by
Per chatroom clarification, I have come up with the the following for you to start with and minimally adjust as needed.
SELECT
u.id_unit,
u.count as TotalUnits,
u.group as BatchNo,
p1.ID_PieceOrder as Piece1Order,
p1Cut.ID_Process as Piece1CutProcessID,
i1Cut.Count as Item1CutCount,
i1Cut.Status as Item1CutStatus,
p1Tamp.ID_PieceOrder as Piece1OrderTampered,
p1Tamp.ID_Process as Piece1TamperedProcessID,
i1Tamp.Count as Item1TamperedCount,
i1Tamp.Status as Item1TamperedStatus,
p2.ID_PieceOrder as Piece2Order,
p2Cut.ID_Process as Piece2CutProcessID,
i2Cut.Count as Item2CutCount,
i2Cut.Status as Item2CutStatus,
p2Tamp.ID_PieceOrder as Piece2OrderTampered,
p2Tamp.ID_Process as Piece2TamperedProcessID,
i2Tamp.Count as Item2TamperedCount,
i2Tamp.Status as Item2TamperedStatus,
pSpacer.ID_PieceOrder as SpacerPieceOrder,
iSpacer.Count as SpacerCount,
iSpacer.Status as SpacerStatus,
pSealed.ID_PieceOrder as SealedPieceOrder,
iSealed.Count as SealedCount,
iSealed.Status as SealedStatus
FROM
db.dbo.unit u
JOIN Process p1
ON u.ID_Unit = p1.ID_Unit
AND u.ID_Piece_1 = p1.ID_Job
JOIN Process p1Cut -- ALL PIECES MUST BE CUT first
ON p1.ID_Unit = p1Cut.ID_Unit
AND p1.ID_PieceOrder = p1Cut.ID_PieceOrder
AND p1Cut.ID_Job = 150
JOIN Items i1Cut
ON p1Cut.id_process = i1Cut.id_process
LEFT JOIN Process p1Tamp -- NOT ALL PIECES MUST BE TEMPERED
ON p1Cut.ID_Unit = p1Tamp.ID_Unit
AND p1Cut.ID_PieceOrder = p1Tamp.ID_PieceOrder
AND p1Tamp.ID_Job = 152
LEFT JOIN Items i1Tamp
ON p1Tamp.id_process = i1Tamp.id_process
JOIN Process p2
ON u.ID_Unit = p2.ID_Unit
AND u.ID_Piece_3 = p2.ID_Job
JOIN Process p2Cut -- ALL PIECES MUST BE CUT first
ON p2.ID_Unit = p2Cut.ID_Unit
AND p2.ID_PieceOrder = p2Cut.ID_PieceOrder
AND p2Cut.ID_Job = 150
JOIN Items i2Cut
ON p2Cut.id_process = i2Cut.id_process
LEFT JOIN Process p2Tamp -- NOT ALL PIECES MUST BE TEMPERED
ON p2Cut.ID_Unit = p2Tamp.ID_Unit
AND p2Cut.ID_PieceOrder = p2Tamp.ID_PieceOrder
AND p2Tamp.ID_Job = 152
LEFT JOIN Items i2Tamp
ON p2Tamp.id_process = i2Tamp.id_process
LEFT JOIN Process PSpacer
ON u.ID_Unit = PSpacer.ID_Unit
AND u.ID_Job = 151
LEFT JOIN Items ISpacer
ON PSpacer.ID_Process = ISpacer.ID_Process
LEFT JOIN Process PSealed
ON u.ID_Unit = PSealed.ID_Unit
AND u.ID_Job = 153
LEFT JOIN Items ISealed
ON PSealed.ID_Process = ISealed.ID_Process
WHERE
u.ID_Unit IN (782327, 782328, 782329 )
-- just a sample of 3 unit IDs to test concept of the revised query structure
I started at the unit table. From that, I am taking each piece down its respective path... First to get the piece, then from the piece, its CUT which will always be required. From the CUT to its ITEM status record which will always exist. From the CUT, I am then looking for a TAMPERED status for the same piece order. Since not all glass needs to be tampered, I have this as a LEFT-JOIN. I then LEFT-JOIN to the tampered's item record by the process id.
I do the same for the SECOND piece.
Then, a LEFT-JOIN to see IF there is a spacing required (can change to JOIN if spacer is ALWAYS required)
Finally, a LEFT-JOIN on the job completed entry. Again, don't know if all the entries are pre-filled in up-front for ALL stages of the process vs not.
Notice my alias name reference from P1 for the process for piece 1, then the p1Cut to i1Cut, to p1Tamp to i1Tamp. So now, instead of generic p1-p6, I have context to the process or item. Additionally, I just grabbed all the columns from the respective piece, cut, tampered, spacer and sealed steps with clear column names. If you don't want something you can always remove it, but with this, you SHOULD never get any duplicates per a single UNIT.
I did not add the join to the order or piece tables respectively. I don't know if piece is really needed unless you had other columns such as the P1.Name, but your table structure dump showed the description.
Since I did not add any other qualifiers, you should get the entire status of a single UNIT in one row. I would adjust the WHERE clause to pick a range of a few units you can test and confirm the context. THEN, adjust the where clause to look for your other criteria such as an item count = another or not, or piece within your '%a%' context or not. However, I THINK this will get you well on your way.
Something like this
LEFT JOIN db.dbo.process p1
on p1.ID_process = i1.ID_process
and p1.note like '12A%'
and p1.ID_pieceorder = '1'
and p1.ID_job = '150'
and p2.ID_pieceorder = '3'
and p2.ID_job = '150'
and i1.count = i2.count
and i1.count = i3.count
and i1.count = i4.count
and i1.rack = i2.rack
and p1.note = p2.note
and i1.status = '1'
and i2.status = '1'
and p3.ID_pieceorder = '0'
and p4.ID_pieceorder = '2'
and p3.ID_job = '153'
and p4.ID_job = '151'
LEFT JOIN db.dbo.items i5
on i5.ID_unit = unit.ID_unit
and p1.note like '12B%'
and i5.status = '1'

SQL Random Select No Repeat

I have a set that looks like this:
staffID clientID eventID
s1 c1 e1c1
s1 c1 e2c1
s1 c2 e1c2
s2 c3 e1c3
s2 c4 e1c4
s2 c5 e1c5
s2 c6 e1c6
I want to select from this list two random clients for each staff. The clients should not repeat. This is not an issue for s2 but can be a problem for s1:
s1 c1 e1c1
s1 c1 e2c1
s2 c3 e1c3
s2 c5 e1c5
If a client has multiple events with one staff, the client may repeat.
The code I have works great, except for this one aspect (above).
SELECT *
FROM (
SELECT eru AS RU, rudesc AS RUName, estaff AS StaffID
, (CASE
WHEN cdprogram.prog = client.cprog THEN ('yes: ' + cdprogram.prog)
WHEN cdprogram.prog <> client.cprog THEN ('no: ' + client.cprog)
END ) AS 'IsPrimProg'
, ROWNUMBER() OVER (PARTITION BY eru, estaff ORDER BY NEWID()) 'RowsPerStaff'
FROM staff INNER JOIN events ON sid = estaff
LEFT OUTER JOIN client ON client.cid = events.ecaseno
WHERE ...
GROUP BY eru, rudesc, estaff, sfname, slname, cdprogram.prog, client.cprog
, ecaseno, edate, cdprogram.progdesc, eser
) innerquery
WHERE innerquery.RowsPerStaff <= 2
ORDER BY RU, StaffID, RowsPerStaff
This code works great, it produces two rows for each staff. But, now I need to fix it so that the same client is not picked twice.
Thank you all!

SQL Between Statement using two Sub-queries that could return multiple values

Purpose: To perform alpha splits on work-lists based on roles assigned to a user. The below logic works if the user only has ONE alpha split (ex. A-CZZZ), but I need the flexibility for some users to have multiple splits (ex. A-CZZZ AND T-ZZZZ).
High Level:
SELECT Name
FROM PatientDatabase
WHERE Name Between
(SELECT UserRole.AlphaFrom
FROM UserRole
WHERE UserRole.HasRole = 1)
AND
(SELECT UserRole.AlphaThru
FROM UserRole
WHERE UserRole.HasRole = 1)
Potential Names in PatientDatabase:
ABC,PERSON
LMN,PERSON
XYZ,PERSON
Current User has two UserRoles (alpha splits):
A-CZZZ
T-ZZZZ
I want to return:
ABC,PERSON
XYZ,PERSON
I believe the subqueries may be necessary given the security-related logic below.
INFO:
-'Roledef->ReportingCategory1->Name' is the AlphaFrom
-'Roledef->ReportingCategory2->Name' is the AlphaThru
Current Logic for alpha split that works when only one (or on the first split if more than one):
AND T.PatId->PatNm BETWEEN
(SELECT E2.Roledef->ReportingCategory1->Name
FROM SECURITYPLUS.USR S2, SecurityPlus.UsrETMRole E2,
SecurityPlus.UsrETMApplication A2
WHERE A2.USR = S2.USERNAME AND
E2.USRETMAPPLICATION = A2.ID AND S2.Username = ?
AND E2.Roledef->Name ['Alpha')
AND
(SELECT E3.Roledef->ReportingCategory2->Name
FROM SECURITYPLUS.USR S3, SecurityPlus.UsrETMRole E3,
SecurityPlus.UsrETMApplication A3
WHERE A3.USR = S3.USERNAME AND
E3.USRETMAPPLICATION = A3.ID AND S3.Username = ?
AND E3.Roledef->Name ['Alpha')
Entire SQL Logic for context:
SELECT
T.Id,
T.PatId->Mrn,
T.PatId->PatNm,
T.Invoice,
T.Invoice->Fsc,
T.Stage->Name As Stage,
T.Status->Name As Status,
T.Invoice->InvBal,
T.ReviewDate,
T.HasNoteFlag
FROM
TaskManager.Task T
WHERE
T.TaskNm->Name = 'Insurance Followup'
AND T.Status IN (SELECT ID FROM Dict.ETMTaskStatus TS
WHERE StatusType NOT IN ('DELETED','DONE'))
AND T.Invoice->Fsc->EtmRole IN
(SELECT E1.Roledef
FROM SECURITYPLUS.USR S1, SecurityPlus.UsrETMRole E1,
SecurityPlus.UsrETMApplication A1
WHERE A1.USR = S1.USERNAME AND
E1.USRETMAPPLICATION = A1.ID AND S1.Username = ?)
AND T.PatId->PatNm BETWEEN
(SELECT E2.Roledef->ReportingCategory1->Name
FROM SECURITYPLUS.USR S2, SecurityPlus.UsrETMRole E2,
SecurityPlus.UsrETMApplication A2
WHERE A2.USR = S2.USERNAME AND
E2.USRETMAPPLICATION = A2.ID AND S2.Username = ?
AND E2.Roledef->Name ['Alpha')
AND
(SELECT E3.Roledef->ReportingCategory2->Name
FROM SECURITYPLUS.USR S3, SecurityPlus.UsrETMRole E3,
SecurityPlus.UsrETMApplication A3
WHERE A3.USR = S3.USERNAME AND
E3.USRETMAPPLICATION = A3.ID AND S3.Username = ?
AND E3.Roledef->Name ['Alpha')
This next logic returns the table I need, but I cannot figure out how to get a between statement to work with this:
SELECT E2.Roledef->ReportingCategory1->Name As AlphaFrom,
E2.Roledef->ReportingCategory2->Name As AlphaThru
FROM SECURITYPLUS.USR S2, SecurityPlus.UsrETMRole E2,
SecurityPlus.UsrETMApplication A2
WHERE A2.USR = S2.USERNAME AND
E2.USRETMAPPLICATION = A2.ID AND S2.Username = ?
AND E2.Roledef->Name ['Alpha'
Returns:
AlphaFrom AlphaThru
A CZZZ
T ZZZZ
I suspect I will have to abandon the sub-queries in order to accomplish this since attempts at joining them have failed, but am unsure how to proceed.
My eventual answer was to create a custom field in our Role dictionary, then create a Role for each letter of the alphabet and use an "IN" statement as opposed to a "BETWEEN".
Using the high level example:
SELECT Name
FROM PatientDatabase
WHERE LEFT(Name,1) In
(SELECT UserRole.Alpha
FROM UserRole
WHERE UserRole.HasRole = 1)

nested dependent sql question

I'm trying to implement a front end for a reporting solution which is security dependent. The user has 12 levels of nested criteria to select from, the value of each affects all of the values below.
So the criteria selection on the page (each is a dropdown) looks something like this:
Criteria 1
Criteria 2
...
Criteria 12
There is a Security table that holds the values that are available to each user which has the following structure:
EmployeeID | Criteria_1_valid_Value | C2_valid_Value | ... | C12_valid_Value
x0001 | c1 | c2 | ... | c12
and each Employee will have one or (many) more rows in this table. Think of it as a flattened tree, with Criteria1 as the root node.
Based on keys, changing Criteria 1 will affect the values that are visible in Criteria 2 through 12. In the same way, changing the value in Criteria 2 affects the values available in Criteria 3 through Criteria 12. At each level, there is an option to select 'All Values,' which is represented by a space internally, for lookups. So I need a representation in the lookup table/view which takes into account that there may be a space at one or many levels.
Where I'm struggling is with finding a way to build the lookup view/table for each Criteria field using sql without having to resort to hardcoding.
For example, to build the lookup for criteria 2 the sql might look like this:
select EmployeeID, Criteria1, Criteria2
from Security
Union
select EmployeeID, ' ', Criteria2
from Security
Union
select EmployeeID, Criteria1, ' '
from Security
UNION
select EmployeeID, ' ', ' '
from Security
And so on. Unfortunately, of course, with 12 levels, the last works out to 2^12 unions, which frankly smells.
I've tried building a table for each level in batch, committing after each, then using the previous table joined to the Security table to build the next with a single UNION in each, but I can't seem to get the joins to work properly with the spaces.
I don't know if I'm overthinking this or completely missing something, but I feel like there has to be a simpler solution.
EDIT: This is on Oracle and I'm working with an ERP product as the underlying technology.
EDIT2: Thanks for the input everyone. I got the joins eorking correctly using joins like in the example proc from #Alex Poole below:
and (v_Criteria_1 = ' ' or Criteria_1_valid_Value = v_Criteria_1)
I was missing the v_Criteria_1 = ' ' or.
So I've got the tables loaded correctly (enough) now. This is turning into a tuning/optimization exercise. I'm going to look at the proc from #Alex Poole and the artithmetic approach of #JD_55 which I think might be very quick.
If your security table structure is something like
Name Null? Type
----------------------------------------- -------- ----------------------------
EMPLOYEEID VARCHAR2(9)
CRITERIA_1_VALID_VALUE VARCHAR2(15)
CRITERIA_2_VALID_VALUE VARCHAR2(15)
CRITERIA_3_VALID_VALUE VARCHAR2(15)
with data
EMPLOYEEI CRITERIA_1_VALI CRITERIA_2_VALI CRITERIA_3_VALI
--------- --------------- --------------- ---------------
alex crit 1a crit 1a 2a crit 1a 2a 3a
alex crit 1a crit 1a 2b crit 1a 2b 3a
alex crit 1a crit 1a 2c crit 1a 2c 3a
alex crit 1a crit 1a 2c crit 1a 2c 3b
alex crit 1b crit 1b 2a crit 1b 2a 3a
alex crit 1b crit 1b 2b crit 1b 2b 3a
alex crit 1b crit 1b 2c crit 1b 2c 3a
alex crit 1c crit 1c 2a crit 1c 2a 3a
then does this give the result you need?
create or replace type t_crit_values as table of varchar2(15)
/
show errors
create or replace function get_criteria(v_EmployeeID in varchar2,
v_Level in number,
v_Criteria_1 in varchar2 default ' ',
v_Criteria_2 in varchar2 default ' ',
v_Criteria_3 in varchar2 default ' ')
return t_crit_values as
cursor c_values is
select distinct(case v_Level
when 1 then Criteria_1_valid_Value
when 2 then Criteria_2_valid_Value
when 3 then Criteria_3_valid_Value
end) value
from security
where EmployeeID = v_EmployeeID
and (v_Criteria_1 = ' ' or Criteria_1_valid_Value = v_Criteria_1)
and (v_Criteria_2 = ' ' or Criteria_2_valid_Value = v_Criteria_2)
and (v_Criteria_3 = ' ' or Criteria_3_valid_Value = v_Criteria_3);
l_crit_values t_crit_values;
i number;
begin
l_crit_values := t_crit_values();
for r_value in c_values loop
l_crit_values.EXTEND;
l_crit_values(l_crit_values.LAST) := r_value.value;
end loop;
return l_crit_values;
end;
/
show errors
Then call the function, each time passing in the level you need and the selected values from all higher levels (which may be ' '). Something like
// first level
select * from table(get_criteria('alex', 1));
COLUMN_VALUE
---------------
crit 1a
crit 1b
crit 1c
// second level with 'crit 1b' selected
select * from table(get_criteria('alex', 2, 'crit 1b'));
COLUMN_VALUE
---------------
crit 1b 2a
crit 1b 2b
crit 1b 2c
// second level with 'crit 1c' selected
select * from table(get_criteria('alex', 2, 'crit 1c'));
COLUMN_VALUE
---------------
crit 1c 2a
// third level with 'crit 1b' and 'crit 1b 2a' selected
select * from table(get_criteria('alex', 3, 'crit 1b', 'crit 1b 2a'));
COLUMN_VALUE
---------------
crit 1b 2a 3a
// third level with 'crit 1b' and 'all values' selected
select * from table(get_criteria('alex', 3, 'crit 1b', ' '));
COLUMN_VALUE
---------------
crit 1b 2a 3a
crit 1b 2b 3a
crit 1b 2c 3a
I've only gone to three levels for brevity but it would be easy to expand. Or have I not understood what you're trying to do?
Say there were only 3 criteria you want a table like this?
id c1 c2 c3
0 a b c
1 a b space
2 a space c
3 a space space
4 space b c
5 space b space
6 space space c
7 space space space
If you create such a table using sqlloader for example including an id column from 0 to 2^12 -1 , put spaces into all the criteria columns then you could update it using arithmetic:
update temp set c1 = (select criteria1 ...) where mod(id,2) < 1;
update temp set c2 = (select criteria2 ...) where mod(id,4) < 2;
update temp set c3 = (select criteria3 ...) where mod(id,8) < 4;
Does seem like a weird requirement.
Consider a series of left outer self join with each criteria item dependent on the values of the prior criteria. You can use the NVL() function to return spaces instead of nulls when the left join produces null results:
select a.employeeId,
nvl(c1.criteria_1, ' '),
nvl(c2.criteria_2, ' '),
nvl(c3.criteria_3, ' '),
nvl(c4.criteria_4, ' '),
nvl(c5.criteria_5, ' '),
nvl(c6.criteria_6, ' '),
nvl(c7.criteria_7, ' '),
nvl(c8.criteria_8, ' '),
nvl(c9.criteria_9, ' '),
nvl(c10.criteria_10, ' '),
nvl(c11.criteria_11, ' '),
nvl(c12.criteria_12, ' ')
from security as a,
left outer join security as c1
on (c1.employeeId = a.employeeId)
left outer join security as c2
on (c2.employeeId = a.employeeId and
c2.criteria_1 = a.criteria_1)
left outer join security as c3
on (c3.employeeId = a.employeeId and
c3.criteria_1 = a.criteria_1 and
c3.criteria_2 = a.criteria_2)
left outer join security as c4
on (c4.employeeId = a.employeeId and
c4.criteria_1 = a.criteria_1 and
c4.criteria_2 = a.criteria_2 and
c4.criteria_3 = a.criteria_3)
left outer join security as c5
on (c5.employeeId = a.employeeId and
c5.criteria_1 = c1.criteria_1 and
c5.criteria_2 = a.criteria_2 and
c5.criteria_3 = a.criteria_3 and
c5.criteria_4 = a.criteria_4)
left outer join security as c6
on (c6.employeeId = a.employeeId and
c6.criteria_1 = c1.criteria_1 and
c6.criteria_2 = a.criteria_2 and
c6.criteria_3 = a.criteria_3 and
c6.criteria_4 = a.criteria_4 and
c6.criteria_5 = a.criteria_5)
left outer join security as c7
on (c7.employeeId = a.employeeId and
c7.criteria_1 = c1.criteria_1 and
c7.criteria_2 = a.criteria_2 and
c7.criteria_3 = a.criteria_3 and
c7.criteria_4 = a.criteria_4 and
c7.criteria_5 = a.criteria_5 and
c7.criteria_6 = a.criteria_6)
left outer join security as c8
on (c8.employeeId = a.employeeId and
c8.criteria_1 = c1.criteria_1 and
c8.criteria_2 = a.criteria_2 and
c8.criteria_3 = a.criteria_3 and
c8.criteria_4 = a.criteria_4 and
c8.criteria_5 = a.criteria_5 and
c8.criteria_6 = a.criteria_6 and
c8.criteria_7 = a.criteria_7)
left outer join security as c9
on (c9.employeeId = a.employeeId and
c9.criteria_1 = c1.criteria_1 and
c9.criteria_2 = a.criteria_2 and
c9.criteria_3 = a.criteria_3 and
c9.criteria_4 = a.criteria_4 and
c9.criteria_5 = a.criteria_5 and
c9.criteria_6 = a.criteria_6 and
c9.criteria_7 = a.criteria_7 and
c9.criteria_8 = a.criteria_8)
left outer join security as c10
on (c10.employeeId = a.employeeId and
c10.criteria_1 = c1.criteria_1 and
c10.criteria_2 = a.criteria_2 and
c10.criteria_3 = a.criteria_3 and
c10.criteria_4 = a.criteria_4 and
c10.criteria_5 = a.criteria_5 and
c10.criteria_6 = a.criteria_6 and
c10.criteria_7 = a.criteria_7 and
c10.criteria_8 = a.criteria_8 and
c10.criteria_9 = a.criteria_9)
left outer join security as c11
on (c11.employeeId = a.employeeId and
c11.criteria_1 = c1.criteria_1 and
c11.criteria_2 = a.criteria_2 and
c11.criteria_3 = a.criteria_3 and
c11.criteria_4 = a.criteria_4 and
c11.criteria_5 = a.criteria_5 and
c11.criteria_6 = a.criteria_6 and
c11.criteria_7 = a.criteria_7 and
c11.criteria_8 = a.criteria_8 and
c11.criteria_9 = a.criteria_9 and
c11.criteria_10 = a.criteria_10)
left outer join security as c12
on (c12.employeeId = a.employeeId and
c12.criteria_1 = c1.criteria_1 and
c12.criteria_2 = a.criteria_2 and
c12.criteria_3 = a.criteria_3 and
c12.criteria_4 = a.criteria_4 and
c12.criteria_5 = a.criteria_5 and
c12.criteria_6 = a.criteria_6 and
c12.criteria_7 = a.criteria_7 and
c12.criteria_8 = a.criteria_8 and
c12.criteria_9 = a.criteria_9 and
c12.criteria_10 = a.criteria_10 and
c12.criteria_11 = a.criteria_11);
I don't fully understand your requirements, but I would have thought that the query for criteria2 would be:
select distinct Criteria2
from Security
where EmployeeID = :the_user
and Criteria1 = :Criteria1
if the user must enter Criteria1 before Criteria2, or
select distinct Criteria2
from Security
where EmployeeID = :the_user
and (:Criteria1 is null or Criteria1 = :Criteria1)
otherwise?
So in the end it did come down to a performance issue. I created a table that held the binary representation of 2^10 integers in reverse (litte-endian, if you will).
DECBIN:
decimal binary
0 0000000000
1 1000000000
2 0100000000
...
1023 1111111111
I then cartesian join this to the security table and decode each bit to get the correct value.
So the sql looks something like this:
SELECT DISTINCT
t.employeeID,
DECODE (SUBSTR (x.binary, 1, 1), 0, ' ', t.c1) AS crit1,
DECODE (SUBSTR (x.binary, 2, 1), 0, ' ', t.c2) AS crit2,
DECODE (SUBSTR (x.binary, 3, 1), 0, ' ', t.c3) AS crit3,
DECODE (SUBSTR (x.binary, 4, 1), 0, ' ', t.c4) AS crit4,
DECODE (SUBSTR (x.binary, 5, 1), 0, ' ', t.c5) AS crit5,
DECODE (SUBSTR (x.binary, 6, 1), 0, ' ', t.c6) AS crit6,
DECODE (SUBSTR (x.binary, 7, 1), 0, ' ', t.c7) AS crit7,
DECODE (SUBSTR (x.binary, 8, 1), 0, ' ', t.c8) AS crit8,
DECODE (SUBSTR (x.binary, 9, 1), 0, ' ', t.c9) AS crit9,
DECODE (SUBSTR (x.binary, 10, 1), 0, ' ', t.c10) AS crit10,
DECODE (SUBSTR (x.binary, 10, 1), 0, 'Choose All',t.c11) AS crit10Descr
FROM Security t, DECBIN x
WHERE TO_NUMBER (x.decimal) BETWEEN 0 AND POWER (2, 10) - 1
This is faster by a factor of 10. Thanks #JD_55 for getting me tho think about the problem in a new way.