JSONB Postgres 9.4 - search

I have a jsonb storing my order product:
CREATE TABLE configuration (
documentid text PRIMARY KEY
, data jsonb NOT NULL
);
Records:
(1, [{"itemid": "PROD001", "qty": 10}, {"itemid": "PROD002", "qty": 20}]),
(2, [{"itemid": "PROD001", "qty": 5}, {"itemid": "PROD003", "qty": 6}, {"itemid": "PROD004", "qty": 7}]),
(2, [{"itemid": "PROD002", "qty": 8}])
I already index data using GIN.
How do I:
Select all sales that has PROD001
Select all sales that has itemid LIKE P%1
Select all sales that has qty > 10
get each product total qty

Postgres documentation regarding JSON functionality is really clear and worth your attention. As for the use cases you provided, answers may be the following:
-- Answer 1
SELECT sales_item
FROM configuration,jsonb_array_elements(data) AS sales_item
WHERE sales_item->>'itemid' = 'PROD001'; -- "->>" returns TEXT value
-- Answer 2
SELECT sales_item FROM configuration,jsonb_array_elements(data) AS sales_item
WHERE sales_item->>'itemid' LIKE 'P%1'; -- just use LIKE
-- Answer 3
SELECT sales_item FROM configuration,jsonb_array_elements(data) AS sales_item
WHERE (sales_item->>'qty')::INTEGER > 10; -- convert TEXT to INTEGER
For last example you just need to use Postgres Window function:
-- Answer 4
SELECT DISTINCT documentid,sum((sales_item->>'qty')::INTEGER)
OVER (PARTITION BY documentid)
FROM configuration,jsonb_array_elements(data) as sales_item;

Examples 1,3 with JsQuery extension
SELECT * FROM
(SELECT jsonb_array_elements(data) as elem FROM configuration
WHERE data ## '#.itemid = "PROD001"') q
WHERE q.elem ## 'itemid = "PROD001"'
SELECT * FROM
(SELECT jsonb_array_elements(data) as elem FROM configuration
WHERE data ## '#.qty > 10') q
WHERE q.elem ## 'qty > 10'
Inner query WHERE clause filters rows doesn't have any array element that matches requirements. Than jsonb_array_elements func. is applied only for needed rows of "configuration" table.

Related

Invalid Column In SQL Query

select
university_cars_video_kroenke.dbo.car_customer.cus_first,
university_cars_video_kroenke.dbo.car_customer.cus_last,
(
select COUNT(university_cars_video_kroenke.dbo.car_customer.cus_id)
from university_cars_video_kroenke.dbo.car_purchases
where university_cars_video_kroenke.dbo.car_customer.cus_id = university_cars_video_kroenke.dbo.car_purchases.cus_id
)
from university_cars_video_kroenke.dbo.car_customer
(edited for clarity)
select
customer.cus_first,
customer.cus_last,
(select
COUNT(customer.cus_id)
from purchases
where customer.cus_id = purchases.cus_id )
from customer
My error message is
Msg 8120, Level 16, State 1, Line 4 Column
'university_cars_video_kroenke.dbo.car_customer.cus_first'
is invalid in the select list because it is not contained
in either an aggregate function or the GROUP BY clause
I just want a count of records the cus_id is the same in both tables.
I just want a count of records the cus_id is the same in both tables.
Something like the following should work.
SELECT
A.cus_id,
count(A.cus_id)
FROM
university_cars_video_kroenke.dbo.car_customer AS A,
university_cars_video_kroenke.dbo.car_purchases AS B
WHERE
A.cus_id = B.cus_id

SELECT rows with primary key of multiple columns

How do I select all relevant records according to the provided list of pairs?
table:
CREATE TABLE "users_groups" (
"user_id" INTEGER NOT NULL,
"group_id" BIGINT NOT NULL,
PRIMARY KEY (user_id, group_id),
"permissions" VARCHAR(255)
);
For example, if I have the following JavaScript array of pairs that I should get from DB
[
{user_id: 1, group_id: 19},
{user_id: 1, group_id: 11},
{user_id: 5, group_id: 19}
]
Here we see that the same user_id can be in multiple groups.
I can pass with for-loop over every array element and create the following query:
SELECT * FROM users_groups
WHERE (user_id = 1 AND group_id = 19)
OR (user_id = 1 AND group_id = 11)
OR (user_id = 5 AND group_id = 19);
But is this the best solution? Let say if the array is very long. As I know query length may get ~1GB.
what is the best and quick solution to do this?
Bill Karwin's answer will work for Postgres just as well.
However, I have made the experience that joining against a VALUES clause is very often faster than a large IN list (with hundreds if not thousands of elements):
select ug.*
from user_groups ug
join (
values (1,19), (1,11), (5,19), ...
) as l(uid, guid) on l.uid = ug.user_id and l.guid = ug.group_id;
This assumes that there are no duplicates in the values provided, otherwise the JOIN would result in duplicated rows, which the IN solution would not do.
You tagged both mysql and postgresql, so I don't know which SQL database you're really using.
MySQL at least supports tuple comparisons:
SELECT * FROM users_groups WHERE (user_id, group_id) IN ((1,19), (1,11), (5,19), ...)
This kind of predicate can be optimized in MySQL 5.7 and later. See https://dev.mysql.com/doc/refman/5.7/en/range-optimization.html#row-constructor-range-optimization
I don't know whether PostgreSQL supports this type of predicate, or if it optimizes it.

Node Express and SQLite3: return related many-to-many data as one object

I have the following tables:
movies genres genres_movies
------ ------ -------------
id id id
title name genre_id
movie_id
Using SQLite3 and Node Express, how can I create a query (or queries) that will return the following, hypothetical, movie object?
{
"id": 1,
"title": "Catapult: The Movie",
"genres": [
{
"id": 1,
"name" "documentary"
},
{
"id": 2,
"name": "comedy"
}
]
}
Thanks!
Assuming your Sqlite version has the JSON1 extension enabled, something like:
PRAGMA foreign_keys = on;
CREATE TABLE movies(id INTEGER PRIMARY KEY, title TEXT);
CREATE TABLE genres(id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE genres_movies(id INTEGER PRIMARY KEY
, movie_id INTEGER REFERENCES movies(id)
, genre_id INTEGER REFERENCES genres(id)
);
CREATE INDEX gm_idx_movie_id ON genres_movies(movie_id);
INSERT INTO movies(id, title) VALUES (1, 'Catapult: The Movie')
, (2, 'Deadpool')
, (3, 'Dune');
INSERT INTO genres(id, name) VALUES (1, 'documentary'), (2, 'comedy');
INSERT INTO genres_movies(movie_id, genre_id) VALUES (1, 1), (1, 2), (2, 2);
SELECT json_object('id', m.id
, 'title', m.title
, 'genres',
CASE WHEN g.id ISNULL THEN json_array()
ELSE json_group_array(json_object('id', g.id
, 'name', g.name))
END)
AS movie
FROM movies AS m
LEFT JOIN genres_movies AS gm ON m.id = gm.movie_id
LEFT JOIN genres AS g ON g.id = gm.genre_id
GROUP BY m.id
ORDER BY m.id;
produces
movie
--------------------------------------------------------------------------------------------------------
{"id":1,"title":"Catapult: The Movie","genres":[{"id":1,"name":"documentary"},{"id":2,"name":"comedy"}]}
{"id":2,"title":"Deadpool","genres":[{"id":2,"name":"comedy"}]}
{"id":3,"title":"Dune","genres":[]}

Oracle spatial data operator - SDO_nn - Not getting any results for sdo_num_res = 1

I am using SDO_NN operator to find the nearest hydrant next to a building.
Building:
CREATE TABLE "BUILDINGS"
(
"NAME" VARCHAR2(40),
"SHAPE" "SDO_GEOMETRY")
Hydrant:
CREATE TABLE "HYDRANTS"
( "NAME" VARCHAR2(10),
"POINT" "SDO_POINT_TYPE"
);
I have setup spatial indexes properly for buildings.shape and I run the query to get the nearest hydrant to the building 'Motel'
select b1.name as name, h.point.x as x, h.point.y as y from buildings b1, hydrants h where b1.name ='Motel' and
SDO_nn( b1.shape, MDSYS.SDO_GEOMETRY(2003,NULL, NULL,SDO_ELEM_INFO_ARRAY(1,1003,1),
SDO_ORDINATE_ARRAY( h.point.x,h.point.y)), 'sdo_num_res=1')= 'TRUE';
Here's the problem:
When I set the parameter sdo_num_res=1, I get zero tuples.
And when I make sdo_num_res=2, I get one tuple.
What is the reason for the weird behavior ?
Note: I am getting zero rows only when building.name= 'Motel', for all other tuples I am getting 1 row when sdo_num_res = 1
Edit:
Insert queries
Insert into buildings (NAME,SHAPE) values ('Motel',MDSYS.SDO_GEOMETRY(2003,NULL,NULL,MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1),MDSYS.SDO_ORDINATE_ARRAY(564,425,585,436,573,458,552,447)));
Insert into hydrants (name,POINT) values ('p57',MDSYS.SDO_POINT_TYPE(589,448,0));
To perform spatial comparisons between a point to a polygon, the SDO_GEOMETRY is defined with SDO_SRID=2001 and center set to a SDO_POINT_TYPE-> which we want to compare.
MDSYS.SDO_GEOMETRY(2001, NULL, SDO_POINT_TYPE(-79, 37, NULL), NULL, NULL)
First of all, your query does not do what you say it does: it actually returns the nearest building called "Motel" from any of your hydrants. To do what you want (i.e. the opposite) you need to reverse the order of the arguments to SDO_NN: all spatial operators search the first argument, using the value of the second argument.
Then the insert into your HYDRANTS table is wrong:
Insert into hydrants (name,POINT) values ('p57',MDSYS.SDO_POINT_TYPE(589,448,0));
The SDO_POINT_TYPE object is not designed to be used that way: it is only used inside the SDO_GEOMETRY type. The proper way is this:
insert into hydrants (name,POINT) values ('p57',sdo_geometry(2001, null, SDO_POINT_TYPE(589,448,null), null, null));
And of course you need to change your table definition accordingly.
Then your building is also incorrectly created: a polygon must always close, i.e. the last point must be the same as the first point. So the proper shape should be like this:
insert into buildings (NAME,SHAPE) values ('Motel', SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1),SDO_ORDINATE_ARRAY(564,425,585,436,573,458,552,447,564,425)));
Here is the full example:
Create the tables:
create table buildings (
name varchar2(40) primary key,
shape sdo_geometry
);
create table hydrants(
name varchar2(10) primary key,
point sdo_geometry
);
Populate the tables:
insert into buildings (NAME,SHAPE) values ('Motel', SDO_GEOMETRY(2003,NULL,NULL,SDO_ELEM_INFO_ARRAY(1,1003,1),SDO_ORDINATE_ARRAY(564,425,585,436,573,458,552,447,564,425)));
insert into hydrants (name,POINT) values ('p57',sdo_geometry(2001, null, SDO_POINT_TYPE(589,448,null), null, null));
commit;
Confirm that the geometries are all correct:
select name, sdo_geom.validate_geometry_with_context (point, 0.05) from hydrants;
select name, sdo_geom.validate_geometry_with_context (shape, 0.05) from buildings;
Setup spatial metadata and create spatial indexes:
insert into user_sdo_geom_metadata (table_name, column_name, diminfo, srid)
values (
'BUILDINGS',
'SHAPE',
sdo_dim_array (
sdo_dim_element ('X', 0,1000,0.05),
sdo_dim_element ('Y', 0,1000,0.05)
),
null
);
commit;
create index buildings_sx on buildings (shape)
indextype is mdsys.spatial_index;
insert into user_sdo_geom_metadata (table_name, column_name, diminfo, srid)
values (
'HYDRANTS',
'POINT',
sdo_dim_array (
sdo_dim_element ('X', 0,1000,0.05),
sdo_dim_element ('Y', 0,1000,0.05)
),
null
);
commit;
create index hydrants_sx on hydrants (point)
indextype is mdsys.spatial_index;
Now Try the properly written query:
select h.name, h.point.sdo_point.x as x, h.point.sdo_point.y as y
from buildings b, hydrants h
where b.name ='Motel'
and sdo_nn(h.point, b.shape, 'sdo_num_res=1')= 'TRUE';
which returns:
NAME X Y
---------------- ---------- ----------
p57 589 448
1 row selected.

Cassandra -How to create composite column name (not key) using cqlsh

I'm trying to model a column family in Cassandra 1.1 which logically looks like this:
Entp: //CF
//rowkey-> entp_name_xyz:
{entp_full_name: "full_name_xyz",
some_value: 1,
policy: {policy_name: "default policy",
type: "type 1",
prop_1: "prop 1",
...
},
rules: {rule_1:, rule_2:,rule_3:}
}
The queries I'm trying to model are:
Get all policies given an entp name, Get all rules given an entp, Get all columns given an entp_name
I'm planning to model this column family as having "wide rows" where one row would look like this:
RowKey:- entp_name_xyz,
column_name:- policy:p1
Value:-{JSON object - {policy_name: "default policy", type: "type 1", prop_1: "prop 1", ...}}
column_name:- policy:p2
Value:-{JSON object - {policy_name: "default policy2", type: "type 1", prop_1: "prop 1", ...}}
column_name: rule:r1 where r1 is a rowkey of a Rules column family
Value: Null
Now my question is in cqlsh or cassandra-cli,
how do I insert a composite column name such as policy:p1?
With this scheme, is it possible to have a query like:
select * from entp where column_name like "policy:*" and entp_name= xyz in order to just read all the policy columns ?
How do I set a null value for a column. I read in some forums that you shouldn't need to set a null because its equivalent to not having a value. But consider the case where
you have a static schema with col1, col2 and col3 and I want to insert a row with col3 =null, but with col1 and col2 having some values. What is the cqlsh syntax to insert such data (I could not find it in the documentation) because the following gives an error:
insert into entp (col1,col2,col3) values ("abc","xyz", null)
Thanks!
Composites are far, far easier to work with in CQL3, which is available to you in cassandra 1.1, so I'll use that in my answer. Tables with multiple-component primary keys in CQL3 are equivalent to wide rows in the storage engine (Cassandra) layer.
If I've interpreted what your policy and rules data looks like, then this is a possible answer:
CREATE TABLE entp_policies (
entp_name text,
policy_name text,
policy_value text,
PRIMARY KEY (entp_name, policy_name)
);
CREATE TABLE entp_rules (
entp_name text,
rule_name text,
rule_value text,
PRIMARY KEY (entp_name, rule_name)
);
You'd use it like this:
INSERT INTO entp_policies (entp_name, policy_name, policy_value)
VALUES ('entp_name_xyz', 'p1',
'{policy_name: "default policy", type: "type 1", ...}');
INSERT INTO entp_policies (entp_name, policy_name, policy_value)
VALUES ('entp_name_xyz', 'p2',
'{policy_name: "default policy2", type: "type 1", ...}');
INSERT INTO entp_rules (entp_name, rule_name) VALUES ('entp_name_xyz', 'r1');
-- Get all policies given an entp name
SELECT * FROM entp_policies WHERE entp_name = 'entp_name_xyz';
-- Get all rules given an entp
SELECT * FROM entp_rules WHERE entp_name = 'entp_name_xyz';
-- Get all columns given an entp_name (both of the above)
With your scheme, yes, it would be possible to have a query like that, but it would be a bit more finicky than with my version, plus CQL2 is deprecated.
That's right, you just avoid inserting the value. There isn't any explicit NULL in cql (yet), but you could just do:
insert into entp (col1,col2) values ('abc','xyz');
Hope that helps!
You can use both rules and policies in one table if you define the another column in the composite
create table entp_details(
entp_name text,
type text,
name text,
value text,
primary key (entp_name, type, name));
In here type is either (Policy or Rule).
INSERT INTO entp_details (entp_name, type, name, value)
VALUES ('entp_name_xyz', 'Policy', 'p1',
'{policy_name: "default policy", type: "type 1", ...}');
INSERT INTO entp_details (entp_name, type, name, value)
VALUES ('entp_name_xyz', 'Policy', 'p2',
'{policy_name: "default policy2", type: "type 1", ...}');
INSERT INTO entp_details (entp_name, type, name, value) VALUES ('entp_name_xyz', 'Rule', 'r1', null);
And the queries are like
select * from entp_details WHERE entp_name = 'entp_name_xyz' and type = 'Policy';
select * from entp_details WHERE entp_name = 'entp_name_xyz' and type = 'Rule';

Resources