Oracle query: compare values in table to other values in same table - sql

We have an Oracle based system that manages devices and stores their details and configuration in a table called "configs". Each device is made up of several modules and the configuration of each module is saved as a comma-separated string. For example:
device_id module values
1 1 3,4,2,3
1 2 4,1,3,4
1 3 2,1,2,3
1 4 6,4,2,1
1 5 1,4,2,3
1 6 1,3,4,4
2 1 3,4,2,3
2 2 4,1,3,4
2 3 2,3,2,3
2 4 6,4,2,1
2 5 1,8,2,3
2 6 1,3,4,4
3 1 3,4,2,3
3 2 4,1,3,4
3 3 2,1,2,3
3 4 6,4,2,1
3 5 1,4,2,3
3 6 1,3,4,4
4 1 3,4,2,3
4 2 4,1,3,4
4 3 2,1,2,4
4 4 6,4,2,1
4 5 1,4,2,3
4 6 1,3,7,4
I need to audit the system and identify each module whose configuration does not match a known good config. Ideally, I want to run a query like this, assuming the config for device 1 is my good config:
select device_id,module
from configs
where values != (select values from configs where device_id = 1)
But that returns an error:
ORA-01427: single-row subquery returns more than one row
If I change the query to this, it works:
select device_id,module
from configs
where values = (select values from configs where device_id = 1 and module = 1);
But then I have to run the query for each value of "module".
What I'd like to do is write a query that will allow me to do this in one go. It's been hinted that I could use a cursor statement but I've struggled to find an "oracle cursors for dummies" type article and nothing I've looked at has helped me so far, including http://docs.oracle.com/cd/B28359_01/server.111/b28286/expressions006.htm#SQLRF52057. Can anyone suggest something?
Thanks.

Does the module numbers have to match ? i.e. is the configuration of the module 2 (of the device 1) considered as a good configuration for the module 3 (of some other device)
If so, there are several good configuration (that's why the db says single-row subquery returns more than one row). You have to account for that :
select device_id, module
from configs
where values not in (select values from configs where device_id = 1)
If not, you have to choose the one matching the considered row, for example :
select device_id, module
from configs c1
where values = (
select values
from configs c2
where device_id = 1
and c1.module = c2.module
)

Your query that is producing errors is attempting to compare a list of items in a single value context. You need to change your "!=" to "not in" and it should work. Here is the adjusted query:
select device_id, module
from configs
where values not in (select values from configs where device_id = 1)

If I understood you right, I suppose you can use something like this:
DECLARE
device_string VARCHAR2 (100);
BEGIN
FOR i IN (SELECT * FROM configs WHERE device_id = 1)
LOOP
SELECT listagg(device_id, ';')
WITHIN GROUP (ORDER BY device_id)
INTO device_string
FROM configs
WHERE values != i.values AND module=i.module;
DBMS_OUTPUT.PUT_LINE(
' Bad config for module ' || i.module ||
' in devices: ' || device_string);
END LOOP;
END;

Related

SQL Rows to Columns if column values are unknown

I have a table that has demographic information about a set of users which looks like this:
User_id Category IsMember
1 College 1
1 Married 0
1 Employed 1
1 Has_Kids 1
2 College 0
2 Married 1
2 Employed 1
3 College 0
3 Employed 0
The result set I want is a table that looks like this:
User_Id|College|Married|Employed|Has_Kids
1 1 0 1 1
2 0 1 1 0
3 0 0 0 0
In other words, the table indicates the presence or absence of a category for each user. Sometimes the user will have a category where the value if false, sometimes the user will have no row for a category, in which case IsMember is assumed to be false.
Also, from time to time additional categories will be added to the data set, and I'm wondering if its possible to do this query without knowing up front all the possible category names, in other words, I won't be able to specify all the column names I want to count in the result. (Note only user 1 has category "has_kids" and user 3 is missing a row for category "married"
(using Postgres)
Thanks.
You can use jsonb funcions.
with titles as (
select jsonb_object_agg(Category, Category) as titles,
jsonb_object_agg(Category, -1) as defaults
from demog
),
the_rows as (
select null::bigint as id, titles as data
from titles
union
select User_id, defaults || jsonb_object_agg(Category, IsMember)
from demog, titles
group by User_id, defaults
)
select id, string_agg(value, '|' order by key)
from (
select id, key, value
from the_rows, jsonb_each_text(data)
) x
group by id
order by id nulls first
You can see a running example in http://rextester.com/QEGT70842
You can replace -1 with 0 for the default value and '|' with ',' for the separator.
You can install tablefunc module and use the crosstab function.
https://www.postgresql.org/docs/9.1/static/tablefunc.html
I found a Postgres function script called colpivot here which does the trick. Ran the script to create the function, then created the table in one statement:
select colpivot ('_pivoted', 'select * from user_categories', array['user_id'],
array ['category'], '#.is_member', null);

MonetDB: Enumerate groups of rows based on a given "boundary" condition

Consider the following table:
id gap groupID
0 0 1
2 3 1
3 7 2
4 1 2
5 5 2
6 7 3
7 3 3
8 8 4
9 2 4
Where groupID is the desired, computed column, such as its value is incremented whenever the gap column is greater than a threshold (in this case 6). The id column defines the sequential order of appearance of the rows (and it's already given).
Can you please help me figure out how to dynamically fill out the appropriate values for groupID?
I have looked in several other entries here in StackOverflow, and I've seen the usage of sum as an aggregate for a window function. I can't use sum because it's not supported in MonetDB window functions (only rank, dense_rank, and row_num). I can't use triggers (to modify the record insertion before it takes place) either because I need to keep the data mentioned above within a stored function in a local temporary table -- and trigger declarations are not supported in MonetDB function definitions.
I have also tried filling out the groupID column value by reading the previous table (id and gap) into another temporary table (id, gap, groupID), with the hope that this would force a row-by-row operation. But this has failed as well because it gives the groupID 0 to all records:
declare threshold int;
set threshold = 6;
insert into newTable( id, gap, groupID )
select A.id, A.gap,
case when A.gap > threshold then
(select case when max(groupID) is null then 0 else max(groupID)+1 end from newTable)
else
(select case when max(groupID) is null then 0 else max(groupID) end from newTable)
end
from A
order by A.id asc;
Any help, tip, or reference is greatly appreciated. It's been a long time already trying to figure this out.
BTW: Cursors are not supported in MonetDB either --
You can assign the group using a correlated subquery. Simply count the number of previous values that exceed 6:
select id, gap,
(select 1 + count(*)
from t as t2
where t2.id <= t.id and t2.gap > 6
) as Groupid
from t;

SQL : Check if result = number for each id

I have this sort of structure
ID STATUS
1 FIRSTSTAT
2 FIRSTSTAT
3 FIRSTSTAT
1 SECSTAT
3 SECSTAT
3 THIRDSTAT
3 FOURTHSTAT
3 FIFTHSTAT
I want to get the 3 back because he has all following status (FIRSTSTAT, SECSTAT, THIRDSTAT). Do you have an idea how I could make that?
It should be done by explicitely giving the statuses because other statuses exist so SELECT FROM WHERE = 'THIRDSTAT' is not ok since it should have all three statuses, not only one of them.
So I guess it should be done calculating the SUM or something like that.
I tried the following but of course, it does not work :
SELECT
FROM
WHERE
AND
AND
If the number of different status values is known to always be 3:
select id
from tablename
where status in ('FIRSTSTAT', 'SECSTAT', 'THIRDSTAT')
group by id
having count(distinct status) = 3

Searching for a number in a database column where column contains series of numbers seperated by a delimeter '"&" in SQLite

My table structure is as follows :
id category
1 1&2&3
2 18&2&1
3 11
4 1&11
5 3&1
6 1
My Question: I need a sql query which generates the result set as follows when the user searched category is 1
id category
1 1&2&3
2 18&2&1
4 1&11
5 3&1
6 1
but i am getting all the results not the expected one
I have tried regexp and like operators but no success.
select * from mytable where category like '%1%'
select * from mytable where category regexp '([.]*)(1)(.*)'
I really dont know about regexp I just found it.
so please help me out.
For matching a list item separated by &, use:
SELECT * FROM mytable WHERE '&'||category||'&' LIKE '%&1&%';
this will match entire item (ie, only 1, not 11, ...), whether it is at list beginning, middle or end.

Getting a comma-delimited list of PK's for duplicates of a record in SQL Server 2005?

This is an off-shoot of a previous question I had: A little fuzzy on getting DISTINCT on one column?
This query makes a little more sense, given the data:
SELECT Receipts.ReceiptID, FolderLink.ReceiptFolderID
FROM dbo.tbl_ReceiptFolderLnk AS FolderLink INNER JOIN
dbo.tbl_Receipt AS Receipts ON FolderLink.ReceiptID = Receipts.ReceiptID
With results:
ReceiptID ReceiptFolderID NewColumn (duplicate folder ID list)
-------------------- --------------- ----------
1 3
2 3
3 7
4 <---> 4 8,9
5 4
6 1
3 8
4 <---> 8 4,9
4 <---> 9 4,8
That answer provided me to view distinct(ReceiptID)'s... great. Now, for those ID's, 3 and 4, they exist in multiple ReceiptFolderID's.
Given this NON-unique list of ReceiptID's, I'd like an additional column, of comma-delimited ReceiptFolderLinkID's where the ReceiptID also exists.
So for ReceiptID=4, the new column, say, DuplicateFoldersList, should read, "8,9", etc, and similar with ID=3, or any other duplicates.
So basically, I'd like another column to indicate the ReceiptFolderID's additional occurrences of ReceiptID in other folders.
Thanks!
You can create a function that, given a ReceiptID and the "current" ReceiptFolderID for that row, returns the other ReceiptFolderIDs as a concatenated, comma-delimited list. Example:
CREATE FUNCTION [dbo].[GetOtherReceiptFolderIDs](#receiptID int, #receiptFolderID int)
RETURNS varchar(MAX) AS
BEGIN
DECLARE #returnValue varchar(MAX)
SELECT #returnValue = COALESCE(#returnValue + ', ', '') + COALESCE(CONVERT(varchar(MAX), ReceiptFolderID), '')
FROM tbl_ReceiptFolderLink AS FolderLink
WHERE FolderLink.ReceiptID = #receiptID
AND FolderLink.ReceiptFolderID <> #receiptFolderID
RETURN #returnValue
END
Then, you can run a query that uses this function to obtain your new column:
SELECT Receipts.ReceiptID, ReceiptFolderID, dbo.GetOtherReceiptFolderIDs(Receipts.ReceiptID, ReceiptFolderID) AS NewColumn
FROM tbl_Receipt AS Receipts
INNER JOIN tbl_ReceiptFolderLink AS FolderLinks
ON Receipts.ReceiptID = FolderLinks.ReceiptID
I tested this and it produces the following results (if I got your schema correctly):
ReceiptID ReceiptFolderID NewColumn
6 1 NULL
1 3 NULL
2 3 NULL
4 4 8, 9
5 4 NULL
3 7 8
3 8 7
4 8 4, 9
4 9 4, 8
In Mysql there is group_concat aggregate function, but in T-SQL and oracle you need to use another approach... This site lists multiple approaches for T-SQL, but none are very simple and easy (as mysql is)