Oracle Query on Comma Separated Values In a Column - sql

I have two tables. One table is a list of access points. The other is a list of who has access to what.
It use to be a person had access to either one thing or all things. But, now that has changed. Now someone might have access to several points.
The wrench in the system is that in the column that shows what they have access to may have a single value, "ALL" for all access or a comma separated list (which is new).
I originally thought I could just do WHERE Access_To Here IN(), but I am unsure how to convert the value of Access_To to a formatted list.
I need to be able to do this as a single query since I am using it for a LOV in APEX.
So, I need some help.
The Access_Points_Table has only one column, Access_Points. Here are some example values that it might have:
CSX
CZR
XR3
NBO
QHG
The Users_List table has several columns, but the most important are User_Name, Access_To. Here are some example values that it might have:
Joe | ALL
Fred | CSX
Allen | CZR, NBO
Hank | QHG
Here is query I am currently using, but it only works if there is only a single value in the Access_To column.
SELECT DISTINCT Access_Points VALUE
FROM Access_Points_Table apt
JOIN
Users_List ul
ON (ul.wwid = 'ZZ999'
AND (ul.Access_To = 'ALL' OR apt.symbol_name = ul.Access_To))
ORDER BY name ASC
What I am trying to accomplish is:
SELECT DISTINCT Access_Points VALUE
FROM Access_Points_Table apt
JOIN
Users_List ul
ON (ul.wwid = 'ZZ999'
AND (ul.Access_To = 'ALL' OR apt.symbol_name IN(Something Goes Here)))
ORDER BY name ASC
So, if run a query for the user Allen, it will return the rows:
CZR
NBO

Depending on the length of your comma-delimited data, you can use Oracle's regular expression engine (the difficulty is that Oracle limits regular expressions to 512 bytes):
SELECT DISTINCT Access_Points VALUE
FROM Access_Points_Table apt
JOIN Users_List ul
ON ( ul.wwid = 'ZZ999'
AND ( ul.Access_To = 'ALL'
OR REGEXP_LIKE(apt.symbol_name, '^(' || REPLACE(ul.Access_To, ',', '|') || ')$') ) )
ORDER BY name ASC
Alternately you can use LIKE:
SELECT DISTINCT Access_Points VALUE
FROM Access_Points_Table apt
JOIN Users_List ul
ON ( ul.wwid = 'ZZ999'
AND ( ul.Access_To = 'ALL'
OR ',' || ul.Access_To || ',' LIKE '%,' || apt.symbol_name || ',%' ) )
ORDER BY name ASC
Note that if Access_To has spaces after its commas as in your OP, it does add some complexity but that can be overcome, simply REPLACE(ul.Access_To, ' ').
By the way, I do wonder why this: ul.wwid = 'ZZ999' is in the ON clause instead of in a WHERE clause.

Another way would be to use the instr test:
SELECT DISTINCT access_points VALUE
FROM access_points_table a
JOIN users_list u
ON ( INSTR(u.access_to,a.access_points,1) > 0 )
ORDER BY 1

Related

PostgreSQL: How to pull relevant data for each row identified by a contained value

I have a database full of Chemical Data. In a table, each row of course contains a set of columns. One of these is what I want; the ID. One of these is what I am trying to identify the row by; cas_number.
I want to make a query or script that can take a list eg; (77-58-7, 77-58-7, 12578-12-0,...) and return the respective ID for each cas_number in the list.
So far I have tried to use the basic WHERE IN (_) query, only to get errors. After Googling around for a while I found nothing else probably due to the weird wording of my question. Hopefully typed out it makes more sense.
Edit to add sample data in CSV format.
id,cas_number
515,77-58-7
123,77-99-6
12,101-02-0
564,126-58-9
321,2452-01-9
624,12065-90-6
643,12202-17-4
15,12578-12-0
62,15535-79-2
77,15546-11-9
536,15571-58-1
55,15647-08-2
33,25448-25-3
22,25550-98-5
326,26544-23-0
123,27107-89-7
321,32509-66-3
234,35674-68-1
543,57583-34-3
456,57583-35-4
765,62229-08-7
53,68109-88-6
31,77745-66-5
86,91031-62-8
First make a table from your list using unnest and then join.
select id, cas_number
from _table
inner join unnest(string_to_array('4141-41-53, 535-35-135, 5136-662-32', ', ')) v
on cas_number = v;
or as a parameterized query -
select id, cas_number
from _table
inner join unnest(string_to_array(:cas_list, ', ')) v on cas_number = v;
Ordering the result :
select id, cas_number
from _table
inner join unnest(string_to_array(:cas_list, ', ')) with ordinality t(v, o)
on cas_number = t.v
order by t.o;

Collating SQL query results

I am having to write a query for an AS400 report. We are looking to group data by date. In other words we want to sum all data for each individual year and month. This is the query I have currently:
Select SCDATA.SCCLNT.CCLNT,
(Select SCDATA.SCCLNT.CNAME From SCDATA.SCCLNT
Where SCDATA.SCCLNT.CLTGRP Like 916500 Fetch First 1 Rows Only) As ClientName,
CONCAT(TRIM(SCDATA.SCCLNT.CADD1), SCDATA.SCCLNT.CADD2) As Address1,
CONCAT(TRIM(SCDATA.SCCLNT.CCITY), CONCAT(', ',
CONCAT(TRIM(SCDATA.SCCLNT.CSTATE), CONCAT(' ', TRIM(SCDATA.SCCLNT.CZIP)))))
As Address2,
SCDATA.SCCLNT.CLTGRP As Group,
SCDATA.SCPLHS.HMONTH || '-' || SCDATA.SCPLHS.HYEAR AS EndDate,
sum(HPL#) as Placed#,
sum (hpl$) as Placed$,
sum(HPMT$M) as PymtMth,
sum(HPMT$) as PymtTTL,
sum(HCOM$) as CommTTL,
sum(HPIF#) as PIF,
sum(HCLI#) as WithDrawn#,
sum(HCLI$) as WithDrawn$,
sum(HCLA#) as Closed#,
sum(HCLA$) as Closed$,
sum(HPMT$)/sum(HPL$) as Recovered,
sum(HAC#) as Active#,
sum(HAC$) as Active$
From SCDATA.SCCLNT
Inner Join SCDATA.SCPLHS On SCDATA.SCPLHS.HCLNT = SCDATA.SCCLNT.CCLNT And
(SCDATA.SCPLHS.HYEAR Between 17 And 17) and
(SCDATA.SCPLHS.HMONTH Between 01 And 10 )
Where SCDATA.SCCLNT.CLTGRP Like 916500
Group By SCDATA.SCPLHS.HYEAR ,
SCDATA.SCPLHS.HMONTH,
SCDATA.SCCLNT.CCLNT,
SCDATA.SCCLNT.CADD1,
SCDATA.SCCLNT.CADD2,
SCDATA.SCCLNT.CZIP,
SCDATA.SCCLNT.CLTGRP,
SCDATA.SCCLNT.CCITY,
SCDATA.SCCLNT.CSTATE
How can I collate this date so that my results show each date only once, and the sum of all data for that date?
Than you.
--EDIT--
Here are the results I am getting from the current query, tab delimited:
https://drive.google.com/open?id=0BwJ_JKr6NhYJVnNIVDcyNW9WMms CSV File
The results I need are:
https://drive.google.com/open?id=0BwJ_JKr6NhYJUTBDUTlDV00yanc
When I was grouping the query results I was including the SCCLNT column in the group. Each client group has multiple client numbers, which was causing the query to return multiple results, one for every client ID.
You statement has an aggregation problem - your GROUP BY includes two separate tables, the columns of which are not going to share an index (and quite possibly one of the tables may not have an index over the used columns at all). The statement is going to be slower than it needs to be.
You may find it more faster to do the aggregation just on the actual sale data:
SELECT Client.group, Client.name, Client.address1, Client.address2,
Historical.month || '-' || Historical.year as endDate
Historical.placed#, Historical.placed$,
Historical.pymtMth,
Historical.pymtTTL, Historical.commTTL,
Historical.PIF,
Historical.withdrawn#, Historical.withdrawn$,
Historical.closed#, Historical.closed$,
Historical.recovered,
Historical.active#, Historical.active$
FROM (SELECT SCPlHs.hYear as year,
SCPlHs.hMonth as month,
SUM(SCPlHs.hPl#) as placed#,
SUM(SCPlHs.hPl$) as placed$,
SUM(SCPlHs.hpmt$m) as pymtMth,
SUM(SCPlHs.hPmt$) as pymtTTL,
SUM(SCPlHs.hCom$) as commTTL,
SUM(SCPlHs.hPif#) as PIF,
SUM(SCPlHs.hCli#) as withdrawn#,
SUM(SCPlHs.hCli$) as withdrawn$,
SUM(SCPlHs.hCla#) as closed#,
SUM(SCPlHs.hCla$) as closed$,
SUM(SCPlHs.hPmt$) / SUM(SCPlHs.hpl$) as recovered,
SUM(SCPlHs.hAc#) as active#,
SUM(SCPlHs.hAc$) as active$
FROM SCData.SCPlHs
JOIN (SELECT DISTINCT cClnt as client
FROM SCData.SCClnt
WHERE SCClnt.cltGrp = 916500) Client
ON Client.client = SCPlHs.hClnt
-- dates, like all positive, contiguous-range types,
-- should be queries with an exclusive upper bound.
-- You should stop using BETWEEN, if possible.
WHERE SCPlHs.hYear >= 17 and SCPlHs.hYear < 18
AND SCPlHs.hMonth >= 1 and SCPlHs.hMonth < 11
GROUP BY SCPlHs.hYear, SCPlHs.hMonth) Historical
-- Cross joins multiply the total number of rows, but that's okay here because
-- the joined table is going to only have one row
CROSS JOIN (SELECT SCClnt.cltGrp as group
SCClnt.cName as name,
TRIM(SCClnt.cAdd1) || TRIM(SCClnt.cAdd2) as address1,
TRIM(SCClnt.cCity) || ', ' || TRIM(SCClnt.cState) || ' ' || TRIM(SCClnt.cZip) as address2
FROM SCData.SCClnt
WHERE SCClnt.cltGrp = 916500
FETCH FIRST 1 ROW ONLY) Client
ORDER BY Historical.year, Historical.month

Pivot without number gets null

I've tried writing a simple pivot function with SQL. My main problem is that all the tables involved have no numbers in them. I think that this is the main reason I get only one result and the rest is NULL even though some of the cells have data in them.
This is my function:
SELECT Inspector, ShiftManager, ForkliftOperator, CalibrationOperator, sorted
FROM (SELECT LHAPP_OWNER.dbo.Personnel.FirstName + ' ' + LHAPP_OWNER.dbo.Personnel.LastName
AS name, LHAPP_OWNER.dbo.PersonnelGroup.PersonnelGroup
FROM MachineGroup
INNER JOIN _MG_CurrentEmployee ON MachineGroup.MachineGroupID = _MG_CurrentEmployee.MachineGroupID
INNER JOIN LHAPP_OWNER.dbo.PersonnelGroup ON _MG_CurrentEmployee.PerssonelGroupID = LHAPP_OWNER.dbo.PersonnelGroup.PersonnelGroupID
LEFT OUTER JOIN LHAPP_OWNER.dbo.Personnel ON _MG_CurrentEmployee.PerssonelID = LHAPP_OWNER.dbo.Personnel.PersonnelID
WHERE (MachineGroup.MachineGroup = N'Line 1')) tbl PIVOT
( MIN(name) FOR PersonnelGroup IN (Inspector, ShiftManager, ForkliftOperator, CalibrationOperator, sorted) ) tbl2
I didn't know what to do with the name so I just gave it MIN. I know it's probably wrong.
Could someone give me a hand here?
My main Table of the inner select looks like this:
name || PersonnelGroup
---------------------------------------
ron roni || Inspector
Lighthouse Systems || Shift Manager
gali logi || Forklift Operator
NULL || Calibration Operator
NULL || sorted
After the function I get this:
Inspector|ShiftManager|ForkliftOperator|CalibrationOperator|Sorted
------------------------------------------------------------------------
ron roni | NULL | NULL | NULL | NULL
It's OK now. I found out the problem. Some of the PersonnelGroup names had two words in them and I looked for one word combining the tow words and so it didn't find any answers. For example, I changed 'ShiftManager' into '[Shift Manager]'

Filter 2-dimensional array

I have this array:
1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0,11:0,12:0,13:0,14:0,15:0,16:0
17:0,18:0,19:0,20:0,21:0,22:0,23:0,24:0,25:0,26:0,27:0,28:0,29:0,30:0,31:0,32:0,
49:0,33:0,34:0,35:0,36:0,37:0,38:0,39:0,40:0,41:0,42:0,43:0,44:0,45:0,46:0,47:0,
48:0,50:0,51:0,52:0,53:0,54:0,55:0,56:0,57:0,58:0,59:0,60:0,61:0,62:0,63:9,64:0,
65:0,66:0,67:0,68:0,69:0,70:0,71:0,72:0,73:0,74:0,75:0,76:0,77:0,78:0,79:0,80:0,
81:0,82:0,83:0,84:0,85:0,86:0,87:0,88:0,89:0,90:0,91:0,92:0,93:0,94:0,95:0,96:0,
97:0,98:0,99:0,100:0
I want to filter all entry like *:0 so that I only get this result:
63:9
I think I have to describe it better:
I have a table users with a field user_skill.
In this field is a such a string: 1:0, 2:0, 3:0, 4:3, 5:8, 6:9, 7:0, 8:0, 9:0 with this syntax: skill_id:prio,skill_id:prio,skill_id:prio,skill_id:prio,...
Now I want to to join the users table with the skills table like this:
SELECT skill_name
FROM users
inner join skills on skills.skill_id = ANY (string_to_array(regexp_replace(user_skill,':[0-9]*','','g'),',')::int[])
where user_id = 16
order by skill_name
That works well but I only want to see skill_name where the user has prio <> 0.
Proper solution
You might want to familiarize yourself with normalization and implement this as a proper n:m relation between the tables users and skills with an additional attribute prio in the user_skill table. Here is a complete recipe:
How to implement a many-to-many relationship in PostgreSQL?
Then your query can be very simple:
SELECT s.skill_name
FROM user_skill uk
JOIN skills s USING (skill_id)
WHERE uk.user_id = 16
AND uk.prio <> 0
ORDER BY s.skill_name;
It can (and should) be backed up with indices and will be faster by several orders of magnitude than what you have right now.
It will need some more space on disk.
Solution for the dark side
While being locked in this unfortunate situation you can help yourself with this query. However, this assumes at least Postgres version
SELECT s.skill_name
FROM (
SELECT split_part(us_item, ':', 1) AS skill_id
FROM (
SELECT trim(unnest(string_to_array(user_skill, ','))) AS us_item
FROM users
WHERE user_id = 16 -- enter user_id here
) x
WHERE split_part(us_item, ':', 2) <> '0'
) u
JOIN skills s USING (skill_id)
ORDER BY 1;
Demo with example:
SELECT split_part(us_item, ':', 1) AS skill_id
FROM (
SELECT trim(unnest(string_to_array(
'1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0,11:0,12:0,13:0,14:0,15:0,16:0,'
'17:0,18:0,19:0,20:0,21:0,22:0,23:0,24:0,25:0,26:0,27:0,28:0,29:0,30:0,31:0,32:0,'
'49:0,33:0,34:0,35:0,36:0,37:0,38:0,39:0,40:0,41:0,42:0,43:0,44:0,45:0,46:0,47:0,'
'48:0,50:0,51:0,52:0,53:0,54:0,55:0,56:0,57:0,58:0,59:0,60:0,61:0,62:0,63:9,64:0,'
'65:0,66:0,67:0,68:0,69:0,70:0,71:0,72:0,73:0,74:0,75:0,76:0,77:0,78:0,79:0,80:0,'
'81:0,82:0,83:0,84:0,85:0,86:0,87:0,88:0,89:0,90:0,91:0,92:0,93:0,94:0,95:0,96:0,'
'97:0,98:0,99:0,100:0', ','))) AS item
) x
WHERE split_part(us_item, ':', 2) <> '0';
trim() deals with leading and trailing spaces, like you have in your example. But those may just be artifacts in the sloppy question.
I fixed a missing ,.
BTW, the SQL standard allows to enter string literal like I demonstrate. Weird, but sometimes useful.

SQL Sorting using Order by

Can you all please help me with this?
Presently, I have this SELECT which returns data ordered by this way
SELECT DISTINCT gl.group_id,
gl.group_name,
gl.group_description,
gl.status_code,
gl.member_count,
(
SELECT grpp.group_name
FROM test_group_relationship grel
JOIN test_group grpp
ON grel.parent_group_id = grpp.group_id
WHERE grel.child_group_id = gl.group_id
) AS parent_group_name,
gl.group_name_key,
gl.group_description_key
FROM test_group gl
WHERE gl.group_org_id = '3909'
AND gl.group_name_key like '%' || 'GROUP' || '%'
ORDER BY
gl.group_name_key, CONVERT(gl.group_name, 'WE8EBCDIC500')
The output is below.I have tried indenting the columns to paste the data.
GROUP_NAME GROUP_NAME_KEY
Add Group Basic Flow ADD GROUP BASIC FLOW
Administrative Group ADMINISTRATIVE GROUP
Amy Group 33 AMY GROUP 33
Amy Test Group 1 AMY TEST GROUP 1
another add group test from matt ANOTHER ADD GROUP TEST FROM MATT
**My Question is in the FIELD GROUP_NAME--> how can i SORT DATA using ORDER BY
so that lowercase letters will be sorted before uppercase letters.
Expected output is :-
the value "another add group test from matt" has to come at the first place.This way
lowercase letters are sorted first and then UPPER CASE.
See also:
SQL ORDER BY Issue Continued
PLSQL ORDER BY Issue
Convert the type on the field to a collation that is case sensitive and order by it asc
In your order by add
Group_Name COLLATE Latin1_General_CS_AS Asc
assuming that your characters are in english; otherwise substitute french etc.
try:
ORDER BY UPPER (SUBSTR (GROUP_NAME, 1, 1)), SUBSTR (GROUP_NAME, 1, 1) DESC, UPPER(GROUP_NAME), GROUP_NAME DESC;
Just add BINARY to your ORDER BY.
ORDER BY BINARY gl.group_name_key
You may have to use DESC otherwise upper case will come first. But then that would also sort z-a.
I'm not an Oracle guy, but depending on your version of Oracle, I believe that there are some session variables that will determine this for you. You can try the following:
ALTER SESSION SET nls_comp=binary;
ALTER SESSION SET nls_sort=GENERIC_M_CI;