Equivalent Functions for JSON/B Operators in Postgresql - json

I've tried to dig through the documentation and can't seem to find anything for what I'm looking for. Are there equivalent functions for the various JSON/B operators (->, ->>, #>, ?, etc) in PostgreSQL?
Edit: To clarify, I would like to know if it is possible to have the following grouped queries return the same result:
SELECT '{"foo": "bar"}'::json->>'foo'; -- 'bar'
SELECT json_get_value('{"foo": "bar"}'::json, 'foo'); -- 'bar'
SELECT '{"foo": "bar"}'::jsonb ? 'foo'; -- t
SELECT jsonb_key_exists('{"foo": "bar"}'::jsonb, 'foo'); -- t

You can use the system catalogs to discover the function equivalent to each operator.
select * from pg_catalog.pg_operator where oprname ='?';
This shows that the function is named "jsonb_exists". Some operators are overloaded and will give more than one function, you have to look at the argument types to distinguish them.
Every operator has a function 'behind' it. That function may or may not be documented in its own right.

AFAIK there are two functions which are equivalents to #> and #>> operators.
These are:
#> json_extract_path
#>> json_extract_path_text
->> json_extract_path_text -- credits: #a_horse_with_no_name
Discover more in the docs
Other than that you could extract json to a table and take values you need using regular SQL query vs a table using json_each or json_each_text.
Same thing with checking if a key exists in a JSON would be to use json_object_keys and also query the table which comes out of it.
If you need to wrap things up in a different language / using ORM then what you could do is move the data retrieval logic to a PL/SQL procedure and just execute it and obtain prepared data from it.
Obviously, you could also build your own functions that would implement the behaviour of forementioned operators, but the question is: is it really worth it? It will definitely be slower.

Related

MySQL using JSON_ARRAYAGG in a SELECT IN clause?

Our database solution is very JSON heavy, and as such, our SQL queries are all JSON based (for the most part). This includes extensive use of JSON_ARRAYAGG().
The problem I'm encountering is using a returned array of indexes in WHERE IN, which simply doesn't work. From what I can tell it's a simple formatting issue where MySQL wants an () encapsulation and a JSON array is a [] encapsulation.
For example:
SELECT COUNT(si.ID) AS item_count, JSON_ARRAYAGG(si.ID) AS item_array
FROM sourcing_item si;
Returns:
7, [1,2,3,4,5,6,7]
What I need to do is write a complex nested query that allows for selecting record IDs that are IN the JSON_ARRAYAGG result. Like:
SELECT si.item_name
FROM sourcing_item si
WHERE si.ID IN item_array
Of course the above doesn't work because MySQL doesn't recognize [] vs. ().
Is there a viable workaround for this issue? I'm surprised they haven't updated MySQL to allow the WHERE IN clause to work with a JSON array...
The MEMBER OF operator does this.
SELECT si.item_name
FROM sourcing_item si
WHERE si.ID MEMBER OF (item_array)

How to wrap existing functions (including aggregates) into a new one in Postgres?

I'm using Postgres 9.2 to generate some JSON data. For each nested table I'm doing this nested set of functions:
SELECT array_to_json(
coalesce(
array_agg(
row_to_json(foo)),
ARRAY[]::json[])
)
FROM foo
The effect is to create a json array with each row being the json collection for the row. The coalesce ensures that I get an empty array rather than nil if the table is empty. In most cases foo is actually a subquery but I don't think that is relevent to the question.
I want to create a function table_to_json_array(expression) such that this has the same effect as above:
SELECT table_to_json_array(foo) FROM foo
I need to use this lots so I was planning to create a Postgres function to have the effect of the combination of these calls to clean up my queries. Looking at the documentation it seems as if I need to create an aggregate rather than a function to take a table argument but those look like I would need to reimplement array_agg myself.
Have I missed something (possibly just the type a function would need to take)? Any suggestions?
In most cases foo is actually a subquery but I don't think that is
relevent to the question.
Unfortunately, it is. You can create a function with regclass argument:
create or replace function table_to_json(source regclass)
returns json language plpgsql
as $$
declare
t json;
begin
execute format ('
SELECT
array_to_json(
coalesce(array_agg(row_to_json(%s)),
ARRAY[]::json[]))
FROM %s', source, source)
into t;
return t;
end $$;
select table_to_json('my_table');
select table_to_json('my_schema.my_view');
But in context:
select table_to_json_rec(arg)
from (select * from my_table) arg
the argument arg is of type record. PL/pgSQL functions cannot accept type record. The only way to get this is a C function, what I guess is not an option. The same goes for aggregates (you must have a function to define an aggregate).
Postgres 9.3 adds a json_agg function which simplifies the specific query I need although this isn't a general solution to the aggregate functions issue. It still needs a coalesce function to ensure the empty set is properly returned.
SELECT coalesce( json_agg(foo), json'[]')
FROM foo
And it works even when foo is a subquery.

MySQL SET Type in PostgreSQL? [duplicate]

This question already has an answer here:
convert MySQL SET data type to Postgres
(1 answer)
Closed 9 years ago.
I'm trying to use MySQL SET type in PostgreSQL, but I found only Arrays, that has quite similar functionality but doesn't met requirements.
Does PostgreSQL has similar datatype?
You can use following workarounds:
1. BIT strings
You can define your set of maximum N elements as simply BIT(N).
It is little bit awkward to populate and retrieve - you will have to use bit masks as set members. But bit strings really shine for set operations: intersection is simply &, union is |.
This type is stored very efficiently - bit per bit with small overhead for length.
Also, it is nice that length is not really limited (but you have to decide it upfront).
2. HSTORE
HSTORE type is an extension, but very easy to install. Simply executing
CREATE EXTENSION hstore
for most installations (9.1+) will make it available. Rumor has it that PostgreSQL 9.3 will have HSTORE as standard type.
It is not really a set type, but more like Perl hash or Python dictionary: it keeps arbitrary set of key=>value pairs.
With that, it is not very efficient (certainly not BIT string efficient), but it does provide functions essential for sets: || for union, but intersection is little bit awkward: use
slice(a,akeys(b)) || slice(b,akeys(a))
You can read more about HSTORE here.
What about an array with a check constraint:
create table foobar
(
myset text[] not null,
constraint check_set
check ( array_length(myset,1) <= 2
and (myset = array[''] or 'one'= ANY(myset) or 'two' = ANY(myset))
)
);
This would match a the definition of SET('one', 'two') as explained in the MySQL manual.
The only thing that this would not do, is to "normalize" the array. So
insert into foobar values (array['one', 'two']);
and
insert into foobar values (array['two', 'one']);
would be displayed differently than in MySQL (where both would wind up as 'one','two')
The check constraint will however get messy with more than 3 or 4 elements.
Building on a_horse_with_no_name's answer above, I would suggest something just a little more complex:
CREATE FUNCTION set_check(in_value anyarray, in_check anyarray)
RETURNS BOOL LANGUAGE SQL IMMUTABLE AS
$$
WITH basic_check AS (
select bool_and(v = any($2)) as condition, count(*) as ct
FROM unnest($1) v
GROUP BY v
), length_check AS (
SELECT count(*) = 0 as test FROM unnest($1)
)
SELECT bool_and(condition AND ct = 1)
FROM basic_check
UNION
SELECT test from length_check where test;
$$;
Then you should be able to do something like:
CREATE TABLE set_test (
my_set text[] CHECK (set_check(my_set, array['one'::text,'two']))
);
This works:
postgres=# insert into set_test values ('{}');
INSERT 0 1
postgres=# insert into set_test values ('{one}');
INSERT 0 1
postgres=# insert into set_test values ('{one,two}');
INSERT 0 1
postgres=# insert into set_test values ('{one,three}');
ERROR: new row for relation "set_test" violates check constraint "set_test_my_set_check"
postgres=# insert into set_test values ('{one,one}');
ERROR: new row for relation "set_test" violates check constraint "set_test_my_set_check"
Note this assumes that for your set, every value must be unique (we are talking sets here). The function should perform very well and should meet your needs. However this has the advantage of handling any size sets.
Storage-wise it is completely different from MySQL's implementation. It will take up more space on disk but should handle sets with as many members as you like, provided that you aren't running up against storage limits.... So this should have a superset of functionality in comparison to MySQL's implementation. One significant difference though is that this does not collapse the array into distinct values. It just prohibits them. If you need that too, look at a trigger.
This solution also leaves the ordinality of input data intact so '{one,two}' is distinct from '{two,one}' so if you need to ensure that behavior has changed, you may want to look into exclusion constraints on PostgreSQL 9.2.
Are you looking for enumerated data types?
PostgreSQL 9.1 Enumerated Types
From reading the page referenced in the question, it seems like a SET is a way of storing up to 64 named boolean values in one column. PostgreSQL does not provide a way to do this. You could use independent boolean columns, or some size of integer and twiddle the bits directly. Adding two new tables (one for the valid names, and the other to join names to detail rows) could make sense, especially if there is the possibility of needing to associate any other data to individual values.
some time ago I wrote one similar extension
https://github.com/okbob/Enumset
but it is not complete
some more complete and close to mysql is functionality from pltoolkit
http://okbob.blogspot.cz/2010/12/bitmapset-for-plpgsql.html
http://pgfoundry.org/frs/download.php/3203/pltoolbox-1.0.2.tar.gz
http://postgres.cz/wiki/PL_toolbox_%28en%29
function find_in_set can be emulated via arrays
http://okbob.blogspot.cz/2009/08/mysql-functions-for-postgresql.html

MySQL proxy better way to detect select query

I am using lua script
https://github.com/clofresh/mysql-proxy-cache to cache the select query.
But there is a problem with the way it is detecting select statement.
It is using following code
return query:sub(1,6):lower() == 'select'
This will not work if select query is nested in (). Example:
(SELECT * from tbl_name);
Is there a way to remove extra () in mysql proxy ?
or Is there a better way to detect select query?
I would try to write a normalizing script using the String Library that detect common patterns and replaces them with equivalent normalized sql.
One example is your parenteses but also queries where the where parts have been moved around could benefit from this.
The queries are actually inside of the the parentheses, not inside of a string? That shouldn't parse correctly, even with a plug in. If it is in a string then simply use :sub(2, 7), however, if it is not, then put it inside of a string. Create a function that basically reproduces the function, except puts it in a string, e.g.:
function mysqlQuery(mysqlString)
loadstring(mysqlString)();
return mysqlString;
end
mysqlQuery("SELECT * from tbl");

Combine 'like' and 'in' in a SqlServer Reporting Services query?

The following doesn't work, but something like this is what I'm looking for.
select *
from Products
where Description like (#SearchedDescription + %)
SSRS uses the # operator in-front of a parameter to simulate an 'in', and I'm not finding a way to match up a string to a list of strings.
There are a few options on how to use a LIKE operator with a parameter.
OPTION 1
If you add the % to the parameter value, then you can customize how the LIKE filter will be processed. For instance, your query could be:
SELECT name
FROM master.dbo.sysobjects
WHERE name LIKE #ReportParameter1
For the data set to use the LIKE statement properly, then you could use a parameter value like sysa%. When I tested a sample report in SSRS 2008 using this code, I returned the following four tables:
sysallocunits
sysaudacts
sysasymkeys
sysaltfiles
OPTION 2
Another way to do this that doesn't require the user to add any '%' symbol is to generate a variable that has the code and exceute the variable.
DECLARE #DynamicSQL NVARCHAR(MAX)
SET #DynamicSQL =
'SELECT name, id, xtype
FROM dbo.sysobjects
WHERE name LIKE ''' + #ReportParameter1 + '%''
'
EXEC (#DynamicSQL)
This will give you finer controller over how the LIKE statement will be used. If you don't want users to inject any additional operators, then you can always add code to strip out non alpha-numeric characters before merging it into the final query.
OPTION 3
You can create a stored procedure that controls this functionality. I generally prefer to use stored procedures as data sources for SSRS and never allow dynamically generated SQL, but that's just a preference of mine. This helps with discoverability when performing dependency analysis checks and also allows you to ensure optimal query performance.
OPTION 4
Create a .NET code assembly that helps dynamically generate the SQL code. I think this is overkill and a poor choice at best, but it could work conceivably.
Have you tried to do:
select * from Products where Description like (#SearchedDescription + '%')
(Putting single quotes around the % sign?)
Dano, which version of SSRS are you using? If it's RS2000, the multi-parameter list is
not officially supported, but there is a workaround....
put like this:
select *
from tsStudent
where studentName like #SName+'%'
I know this is super old, but this came up in my search to solve the same problem, and I wound up using a solution not described here. I'm adding a new potential solution to help whomever else might follow.
As written, this solution only works in SQL Server 2016 and later, but can be adapted for older versions by writing a custom string_split UDF, and by using a subquery instead of a CTE.
First, map your #SearchedDescription into your Dataset as a single string using JOIN:
=JOIN(#SearchedDedscription, ",")
Then use STRING_SPLIT to map your "A,B,C,D" kind of string into a tabular structure.
;with
SearchTerms as (
select distinct
Value
from
string_split(#SearchedDescription, ',')
)
select distinct
*
from
Products
inner join SearchTerms on
Products.Description like SearchTerms.Value + '%'
If someone adds the same search term multiple times, this would duplicate rows in the result set. Similarly, a single product could match multiple search terms. I've added distinct to both the SearchTerms CTE and the main query to try to suppress this inappropriate row duplication.
If your query is more complex (including results from other joins) then this could become an increasingly big problem. Just be aware of it, it's the main drawback of this method.