Querying a jsonb array in postgres - arrays

Table:
CREATE TABLE appointment
(
id bigserial NOT NULL,
date_of_visit timestamp without time zone NOT NULL,
symptoms text[],
diseases text[],
lab_tests text[],
prescription_id bigint NOT NULL,
medicines jsonb,
CONSTRAINT appointment_pkey PRIMARY KEY (id),
CONSTRAINT appointment_prescription_id_fkey FOREIGN KEY (prescription_id)
REFERENCES prescription (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
Insert statement:
INSERT INTO appointment values(
1,
now(),
'{"abc","def","ghi"}',
'{"abc","def","ghi"}',
'{"abc","def","ghi"}',
1,
'[{"sku_id": 1, "company": "Magnafone"}, {"sku_id": 2, "company": "Magnafone"}]')
I am trying to query against a jsonb array type column in postgres. I had some solution in hand which is as below. Somehow it is not working The error is - Cannot extract elements from a scalar.
SELECT distinct(prescription_id)
FROM appointment
WHERE to_json(array(SELECT jsonb_array_elements(medicines) ->>'sku_id'))::jsonb ?|array['1']
LIMIT 2;
Update:
The query runs just fine. There was some unwanted value in the column for some other rows because of which it was not running.

There are rows in the table containing a scalar value in column medicines instead of array.
You should inspect and properly update the data. You can find these rows with this query:
select id, medicines
from appointment
where jsonb_typeof(medicines) <> 'array';
Alternatively, you can check the type of values in this column in the query:
select prescription_id
from (
select distinct on (prescription_id)
prescription_id,
case
when jsonb_typeof(medicines) = 'array' then jsonb_array_elements(medicines) ->>'sku_id'
else null
end as sku_id
from appointment
) alias
where sku_id = '1'
limit 2;
or simply exclude non-array values in where clause:
select prescription_id
from (
select distinct on (prescription_id)
prescription_id,
jsonb_array_elements(medicines) ->>'sku_id' as sku_id
from appointment
where jsonb_typeof(medicines) = 'array'
) alias
where sku_id = '1'
limit 2;

Related

Update data when having UNIQUE Constraint

I am having a simple scores table for HTML5 game with columns: name, email and score. Email value should be a unique value, but when the same users play the game again to better their scores, the score should be updated for that user. Now it returns an error because of the unique value. How should I create a table that will update the data?
The table I have created so far:
CREATE TABLE `scores` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR( 20 ) NOT NULL,
`email` VARCHAR( 320 ) NOT NULL UNIQUE,
`score` INT NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = InnoDB;
You could use an insert statement with an on duplicate key clause:
INSERT INTO scores (name, email, score)
VALUES ('some_name', 'some_email', 123) -- Values from your application
ON DUPLICATE KEY UPDATE
score = CASE VALUES(score) > score THEN VALUES(score) ELSE score END
It is not about your create table query, it is about your insert query.
Use a try catch method to update the unique value
try
{
// inserting the line
}
catch (exception)
{
// Drop the row with that unique value
// inserting the same line which you have added in "try" section
}

MSSQL aggregate ignoring where clause

I have a strange problem that when performing an aggregate function on a type cast varchar column I receive an "Msg 8114, Level 16, State 5, Line 1. Error converting data type nvarchar to bigint." The queries where clause should filter out the non-numeric values.
Table structure is similar to this:
IF EXISTS (SELECT * FROM sys.all_objects ao WHERE ao.name = 'Identifier' AND ao.type = 'U') BEGIN DROP TABLE Identifier END
IF EXISTS (SELECT * FROM sys.all_objects ao WHERE ao.name = 'IdentifierType' AND ao.type = 'U') BEGIN DROP TABLE IdentifierType END
CREATE TABLE IdentifierType
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Style] [int] NULL,
CONSTRAINT [PK_IdentifierType_ID] PRIMARY KEY CLUSTERED ([ID] ASC)
) ON [PRIMARY]
CREATE TABLE Identifier
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[IdentifierTypeID] [int] NOT NULL,
[Value] [nvarchar](4000) NOT NULL,
CONSTRAINT [PK_Identifier_ID] PRIMARY KEY CLUSTERED ([ID] ASC)
) ON [PRIMARY]
ALTER TABLE Identifier WITH CHECK ADD CONSTRAINT [FK_Identifier_IdentifierTypeID] FOREIGN KEY([IdentifierTypeID]) REFERENCES IdentifierType ([ID])
GO
Identifier.Value is a VARCHAR column, it can and does contain non-numeric data. Filtering the query to IdentifierType.Style = 0 should mean that 'Value' only returns string representations of integers. The query below fails with "Msg 8114, Level 16, State 5, Line 1. Error converting data type nvarchar to bigint."
SELECT
MAX(CAST(Value AS BIGINT))
FROM
Identifier i,
IdentifierType it
WHERE
i.IdentifierTypeID = it.ID AND
it.Style = 0
If i extend the WHERE clause to include a 'AND ISNUMERIC(i.Value) = 1' it will return the maximum integer value. That to me implies that there is a non-numeric string in my result set. Yet i get no rows returned from this:
SELECT
*
FROM
Identifier i,
IdentifierType it
WHERE
i.IdentifierTypeID = it.ID AND
it.Style = 0 AND
ISNUMERIC(i.Value) <> 1
I've been unable to identity the row(s) that are tripping the type cast. The above query should have exposed the exceptional rows. In addition, there are no empty or extremely long strings either (the largest string is 6 character long)
Is it possible that MSSQL is attempting to do the CAST on all rows rather than filtering via the WHERE clause first?
Or has anyone else seen anything similar?
There is a second work around which is instantiating the component of the query into a temp table, and then selecting the MAX value from that.
SELECT
Value
INTO
IdentifierClone
FROM
Identifier i,
IdentifierType it
WHERE
i.IdentifierTypeID = it.ID AND
it.Style = 0
SELECT MAX(CAST(Value as BIGINT)) FROM IdentifierClone
A subquery doesn't work however.
Any help or thoughts would be appreciated.
Try using a REGEX expression to find the problem record. Here's an example where ISNUMERIC does not detect the problem but the regex expression does
CREATE TABLE tst (value nvarchar(4000))
INSERT INTO tst select '£'
-- Record found ...
SELECT * FROM tst WHERE value NOT LIKE '%[0-9]%'
-- No record found ...
SELECT * from tst where isnumeric(value) <> 1

I am having trouble with SQL Server Express's identity feature

I'm working with SQL Server Express, I created this table
CREATE TABLE inventory
(
id INT NOT NULL IDENTITY(1,1),
description nvarchar(50),
quantity int,
price money
)
when I insert this statement:
INSERT INTO inventory VALUES('water', 20, 1.50)
I get this error:
The number of columns in the query and the table must match. [ Number
of columns in query = 3, Number of columns in table = 4 ]
and when I put this statement:
INSERT INTO inventory VALUES(1, 'water', 20, 1.50)
I get this error:
The column cannot be modified. [ Column name = id ]
I thought identity would auto increment the value, so can't I do either, and how can I fix it? Thanks in advance
You must explicitly specify columns in your insert
insert Inventory(Description, Quantity, Price) values ( ...)

SQL - How to INSERT a foreign key as a value for a column

I know this is rather basic, and i've searched for answers for quite some time, but I'm troubled.
I don't know how to make my coding readable on here but here it is.
Here's the query for making the table in question:
CREATE TABLE customer
( customer_id INT NOT NULL CONSTRAINT customer_pk PRIMARY KEY IDENTITY,
first_name VARCHAR(20) NOT NULL,
surname VARCHAR(20) NOT NULL,
dob DATETIME NOT NULL,
home_address VARCHAR(50) NOT NULL,
contact_number VARCHAR(10) NOT NULL,
referrer_id INT NULL FOREIGN KEY REFERENCES customer(customer_id),
);
And here's the problem code:
--fill customer table
INSERT INTO customer
VALUES ( 'Harold', 'Kumar', '2010-07-07 14:03:54', '3 Blue Ln, Perth', 0812391245, NULL )
INSERT INTO customer
VALUES ( 'Bingo', 'Washisnameoh', '2010-09-21 12:30:07', '3 Red St, Perth', 0858239471, NULL )
INSERT INTO customer
VALUES ( 'John', 'Green', '2010-11-07 14:13:34', '4 Blue St, Perth', 0423904823, NULL )
INSERT INTO customer
VALUES ( 'Amir', 'Blumenfeld', '2010-11-01 11:03:04', '166 Yellow Rd, Perth', 0432058323, NULL)
INSERT INTO customer
VALUES ( 'Hank', 'Green', '2010-07-07 16:04:24', '444 Orange Crs, Perth', 0898412429, 8)
(Specifically the line with the 8 value at the end.)
When executing the second query it responds with this:
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted
with the FOREIGN KEY SAME TABLE constraint
"FK_customer_referr__5772F790". The conflict occurred in database
"master", table "dbo.customer", column 'customer_id'. The statement
has been terminated.
Appreciate your help with this.
1)
You have a primary key on customer_id - and your insert statements do not have value for customer id
2)
You have a self referencing foreign key in the form of referrer_id referring to customer_id.
When you are inserting a record with referrer_id which is not null, in your case which is '8', make sure you already inserted a record with customer_id '8'
How do you know that the referrer_id is supposed to be 8 ??
What you need to do is catch the value of the customer_id inserted, and then used that in your second query:
DECLARE #referToID INT
INSERT INTO dbo.Customer(first_name, surname, dob, home_address, contact_number, referrer_id)
VALUES ('Harold', 'Kumar', '2010-07-07 14:03:54', '3 Blue Ln, Perth', 0812391245, NULL)
SELECT #ReferToID = SCOPE_IDENTITY() ; -- catch the newly given IDENTITY ID
INSERT INTO dbo.Customer(first_name, surname, dob, home_address, contact_number, referrer_id)
VALUES ('Hank', 'Green', '2010-07-07 16:04:24', '444 Orange Crs, Perth', 0898412429, #ReferToID)
I don't know which row you want to refer to (you didn't specify) - but I hope you understand the mechanism:
insert the new row into your table
get the newly inserted ID by using SCOPE_IDENTITY
insert the next row which refers to that first row and use that value returned by SCOPE_IDENTITY
Update: if you really want to have a given row reference itself (strange concept.....), then you'd need to do it in two steps:
insert the new row into your table
get the newly inserted ID by using SCOPE_IDENTITY
update that row to set the referrer_id
Something like this:
DECLARE #NewCustomerID INT
INSERT INTO dbo.Customer(first_name, surname, dob, home_address, contact_number)
VALUES ('Hank', 'Green', '2010-07-07 16:04:24', '444 Orange Crs, Perth', 0898412429)
SELECT #NewCustomerID = SCOPE_IDENTITY() ; -- catch the newly given IDENTITY ID
UPDATE dbo.Customer
SET referrer_id = #NewCustomerID
WHERE customer_id = #NewCustomerID
The only problem you have here is the identity must have a seed value which can be like Identity(1,1) where the first 1 is the starting point and the send 1 is the auto seed number...the re run your insert statement

Is it possible to a db constraint in for this rule?

I wish to make sure that my data has a constraint the following check (constraint?) in place
This table can only have one BorderColour per hub/category. (eg. #FFAABB)
But it can have multiple nulls. (all the other rows are nulls, for this field)
Table Schema
ArticleId INT PRIMARY KEY NOT NULL IDENTITY
HubId TINYINT NOT NULL
CategoryId INT NOT NULL
Title NVARCHAR(100) NOT NULL
Content NVARCHAR(MAX) NOT NULL
BorderColour VARCHAR(7) -- Can be nullable.
I'm gussing I would have to make a check constraint? But i'm not sure how, etc.
sample data.
1, 1, 1, 'test', 'blah...', '#FFAACC'
1, 1, 1, 'test2', 'sfsd', NULL
1, 1, 2, 'Test3', 'sdfsd dsf s', NULL
1, 1, 2, 'Test4', 'sfsdsss', '#AABBCC'
now .. if i add the following line, i should get some sql error....
INSERT INTO tblArticle VALUES (1, 2, 'aaa', 'bbb', '#ABABAB')
any ideas?
CHECK constraints are ordinarily applied to a single row, however, you can cheat using a UDF:
CREATE FUNCTION dbo.CheckSingleBorderColorPerHubCategory
(
#HubID tinyint,
#CategoryID int
)
RETURNS BIT
AS BEGIN
RETURN CASE
WHEN EXISTS
(
SELECT HubID, CategoryID, COUNT(*) AS BorderColorCount
FROM Articles
WHERE HubID = #HubID
AND CategoryID = #CategoryID
AND BorderColor IS NOT NULL
GROUP BY HubID, CategoryID
HAVING COUNT(*) > 1
) THEN 1
ELSE 0
END
END
Then create the constraint and reference the UDF:
ALTER TABLE Articles
ADD CONSTRAINT CK_Articles_SingleBorderColorPerHubCategory
CHECK (dbo.CheckSingleBorderColorPerHubCategory(HubID, CategoryID) = 1)
Another option that is available is available if you are running SQL2008. This version of SQL has a feature called filtered indexes.
Using this feature you can create a unique index that includes all rows except those where BorderColour is null.
CREATE TABLE [dbo].[UniqueExceptNulls](
[HubId] [tinyint] NOT NULL,
[CategoryId] [int] NOT NULL,
[BorderColour] [varchar](7) NULL,
)
GO
CREATE UNIQUE NONCLUSTERED INDEX UI_UniqueExceptNulls
ON [UniqueExceptNulls] (HubID,CategoryID)
WHERE BorderColour IS NOT NULL
This approach is cleaner than the approach in my other answer because it doesn't require creating extra computed columns. It also doesn't require you to have a unique column in the table, although you should have that anyway.
Finally, it will also be much faster than the UDF/Check Constraint solutions.
You can also do a trigger with something like this (this is actually overkill - you can make it cleaner by assuming the database is already in a valid state - i.e. UNION instead of UNION all etc):
IF EXISTS (
SELECT COUNT(BorderColour)
FROM (
SELECT INSERTED.HubId, INSERTED.CategoryId, INSERTED.BorderColour
UNION ALL
SELECT HubId, CategoryId, BorderColour
FROM tblArticle
WHERE EXISTS (
SELECT *
FROM INSERTED
WHERE tblArticle.HubId = INSERTED.HubId
AND tblArticle.CategoryId = INSERTED.CategoryId
)
) AS X
GROUP BY HubId, CategoryId
HAVING COUNT(BorderColour) > 1
)
RAISEERROR
If you have a unique column in your table, then you can accomplish this by creating a unique constraint on a computer column.
The following sample created a table that behaved as you described in your requirements and should perform better than a UDF based check constraint. You might also be able to improve the performance further by making the computed column persisted.
CREATE TABLE [dbo].[UQTest](
[Id] INT IDENTITY(1,1) NOT NULL,
[HubId] TINYINT NOT NULL,
[CategoryId] INT NOT NULL,
[BorderColour] varchar(7) NULL,
[BorderColourUNQ] AS (CASE WHEN [BorderColour] IS NULL
THEN cast([ID] as varchar(50))
ELSE cast([HuBID] as varchar(3)) + '_' +
cast([CategoryID] as varchar(20)) END
),
CONSTRAINT [UQTest_Unique]
UNIQUE ([BorderColourUNQ])
)
The one possibly undesirable facet of the above implementation is that it allows a category/hub to have both a Null AND a color defined. If this is a problem, let me know and I'll tweak my answer to address that.
PS: Sorry about my previous (incorrect) answer. I didn't read the question closely enough.

Resources