How to query check the constraint is valid or not - postgresql

DBFIDDLE
How to query postgresql to check the constraint is valid or not.
CREATE TABLE emp (test_check int check ( test_check >1 and test_check < 0 ));
query the constraint:
select * from pg_constraint where conname = 'emp_test_check_check';
------------------------------------------------------------------------------------
oid | 24631
conname | emp_test_check_check
connamespace | 2200
contype | c
condeferrable | f
condeferred | f
convalidated | t
conrelid | 24628
contypid | 0
conindid | 0
conparentid | 0
confrelid | 0
confupdtype |
confdeltype |
confmatchtype |
conislocal | t
coninhcount | 0
connoinherit | f
conkey | {1}
confkey | [null]
conpfeqop | [null]
conppeqop | [null]
conffeqop | [null]
confdelsetcols | [null]
conexclop | [null]
conbin | {BOOLEXPR :boolop and :args ({OPEXPR :opno 521 :opfuncid 147 :opresulttype 16 :opretset false :opcollid 0 :inputcollid 0 :args ({VAR :varno 1 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location 46} {CONST :consttype 23 :consttypmod -1 :constcollid 0 :constlen 4 :constbyval true :constisnull false :location 58 :constvalue 4 [ 1 0 0 0 0 0 0 0 ]}) :location 57} {OPEXPR :opno 97 :opfuncid 66 :opresulttype 16 :opretset false :opcollid 0 :inputcollid 0 :args ({VAR :varno 1 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 1 :varattnosyn 1 :location 64} {CONST :consttype 23 :consttypmod -1 :constcollid 0 :constlen 4 :constbyval true :constisnull false :location 77 :constvalue 4 [ 0 0 0 0 0 0 0 0 ]}) :location 75}) :location 60}
get the check definition:
select pgc.conname as constraint_name,
ccu.table_schema as table_schema,
ccu.table_name,
ccu.column_name,
pg_get_constraintdef(pgc.oid)
from pg_constraint pgc
join pg_namespace nsp on nsp.oid = pgc.connamespace
join pg_class cls on pgc.conrelid = cls.oid
left join information_schema.constraint_column_usage ccu
on pgc.conname = ccu.constraint_name
and nsp.nspname = ccu.constraint_schema
where contype ='c'
order by pgc.conname;
return:
-[ RECORD 1 ]--------+------------------------------------------------
constraint_name | emp_test_check_check
table_schema | public
table_name | emp
column_name | test_check
pg_get_constraintdef | CHECK (((test_check > 1) AND (test_check < 0)))
Similar question:
CREATE TABLE emp1 (test_check int check ( test_check >1 and test_check > 10 ));
can postgresql deduce from above check constraint to
CREATE TABLE emp1 (test_check int check ( test_check > 10 ));
if It can, how to query it.

Every boolean expression is valid for a CHECK constraint.
a Boolean (truth-value) expression.
Postgres does not attempt to simplify your expression. It's your responsibility to provide sensible expressions. The expression is parsed and checked to be valid, then stored in an internal format. (So every stored expression is valid.) But not simplified. Not even the simplest cases like CHECK (true AND true).
You already have a working query to retrieve CHECK constraints, but I would use this faster query not involving the (bloated) information_schema:
SELECT c.conname AS constraint_name
, c.conrelid::regclass AS table_name -- schema-qualified where needed
, a.column_names
, pg_get_constraintdef(c.oid) AS constraint_definition
FROM pg_constraint c
LEFT JOIN LATERAL (
SELECT string_agg(attname, ', ') AS column_names
FROM pg_attribute a
WHERE a.attrelid = c.conrelid
AND a.attnum = ANY (c.conkey)
) a ON true
WHERE c.contype = 'c' -- only CHECK constraints
AND c.conrelid > 0 -- only table constraints (incl. "column constraints")
ORDER BY c.conname;
Aside:
It should be noted that a check constraint is satisfied if the check expression evaluates to true or the null value.

Related

Database only Running total with with conditions

There are a ton of questions about calculating running totals with Postgres but I am struggling to do something slightly different.
I have a table that looks like this
txn_id
amount
String
Integer
amounts can either be positive or negative.
I am trying to return a table that looks like this
txn_id
amount
running_total
overage_total
String
Integer
Integer
Integer
Where running total is running sum of the amount column as long as the amount is greater than zero and overage_total is the running sum of amounts that were lower than zero.
An example of would be
txn_id
amount
a
1
b
2
c
-4
d
2
e
-1
I have been using a window function for the running sum but it's not quite what we need.
The correct table would return
txn_id
amount
running_total
overage_total
a
1
1
0
b
2
3
0
c
-4
0
1
d
2
2
1
e
-1
1
1
Currently I have am doing this in code but it would be really incredible to do it in the database if it's possible.
The pattern here is running total with a cap. It could be achieved with recursive cte:
WITH RECURSIVE cte_r AS (
SELECT t.*, ROW_NUMBER() OVER(ORDER BY t.txn_id) AS rn FROM tab t
), cte AS (
SELECT rn,
txn_id,
amount,
CASE WHEN amount <= 0 THEN 0 ELSE amount END AS total,
CASE WHEN amount <= 0 THEN 1 ELSE 0 END AS overage_total
FROM cte_r
WHERE rn = 1
UNION ALL
SELECT cte_r.rn,
cte_r.txn_id,
cte_r.amount,
CASE WHEN cte.total + cte_r.amount <= 0 THEN 0
ELSE cte.total + cte_r.amount
END AS total,
cte.overage_total + CASE WHEN cte.total + cte_r.amount <= 0
THEN 1 ELSE 0 END AS overage_total
FROM cte
JOIN cte_r
ON cte.rn = cte_r.rn-1
)
SELECT txn_id, amount, total,overage_total
FROM cte
ORDER BY rn;
Output:
+---------+---------+--------+---------------+
| txn_id | amount | total | overage_total |
+---------+---------+--------+---------------+
| a | 1 | 1 | 0 |
| b | 2 | 3 | 0 |
| c | -4 | 0 | 1 |
| d | 2 | 2 | 1 |
| e | -1 | 1 | 1 |
| f | 2 | 3 | 1 |
| h | -4 | 0 | 2 |
+---------+---------+--------+---------------+
db<>fiddle demo
Related: Conditional SUM on Oracle and 7. Capping a running total
An option is to use a function to step through the rows and do calculations:
CREATE FUNCTION runningTotalWithCondition() RETURNS TABLE(txn_id char(1), amount int, running_total integer, overage_total integer) AS
$$
DECLARE
running_total integer := 0;
overage_total integer := 0;
c CURSOR FOR SELECT * FROM t ORDER BY txn_id ASC;
BEGIN
FOR recordvar IN c LOOP
IF (running_total + recordvar.amount) > 0 THEN
running_total = running_total + recordvar.amount;
overage_total = overage_total;
ELSE
overage_total = overage_total + abs(running_total + recordvar.amount);
running_total = 0;
END IF;
RETURN QUERY SELECT recordvar.txn_id, recordvar.amount, running_total, overage_total;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Calling the function:
SELECT * FROM runningTotalWithCondition();

Need to have subquery within subquery

I have a stock table which holds for example
Partnumber | Depot | flag_redundant
------------+-------+----------------
1 | 1 | 5
1 | 2 | 0
1 | 3 | 0
1 | 4 | 5
2 | 1 | 0
2 | 2 | 0
2 | 3 | 0
2 | 4 | 0
I need to be able to see the depots in which the parts have not been flagged as redundant, but the flag_redundant has been at least been flagged once for that part, and I need to ignore any parts where there has not been a flag flagged.
Any help appreciated!
I'm thinking of something along the lines of ....
SELECT stock.part, stock.depot,
OrderCount = (SELECT CASE WHEN Stock.flag_redundant = 5 THEN 1 end as Countcolumn FROM stock C)
FROM stock
Partnumber | MissingDepots
------------+---------------
1 | Yes
You can group by partnumber and set the conditions in the HAVING clause:
select
partnumber, 'Yes' MissingDepots
from stock
group by partnumber
having
sum(flag_redundant) > 0 and
sum(case when flag_redundant = 0 then 1 end) > 0
Or:
select
partnumber, 'Yes' MissingDepots
from stock
group by partnumber
having sum(case when flag_redundant = 0 then 1 end) between 1 and count(*) - 1
See the demo.
Results:
> partnumber | missingdepots
> ---------: | :------------
> 1 | Yes
Assuming you want to get these partnumbers that contain data sets with flag_redundant = 5 AND 0:
demo:db<>fiddle
SELECT
partnumber,
'Yes' AS missing
FROM (
SELECT
partnumber,
COUNT(flag_redundant) FILTER (WHERE flag_redundant = 5) AS cnt_redundant, -- 2
COUNT(*) AS cnt -- 3
FROM
stock
GROUP BY partnumber -- 1
) s
WHERE cnt_redundant > 0 -- 4
AND cnt_redundant < cnt -- 5
Group by partnumber
Count all records with flag_redundant = 5
Count all records
Find all partnumbers that contain any element with 5 ...
... and which have more records than 5-element records

Where does Postgres store table sequence information

I have a model in Postgres – say Student – which has primary key id serial, which means there is another table student_id_seq which keeps track of the sequence information for student.
If I change the table name of student to, say, StudentYear, will there be any problem? Would I have to do anything to make sure renaming the table is not going to crash anything?
You can rename the table or the sequence however you want, and everything will keep working.
The relationship between a table column and its sequence is stored in two places:
In pg_attrdef, where the default values of attributes are stored.
In pg_depend where dependencies between objects are tracked.
All objects and columns are internally referenced by their object IDs or attribute numbers, so renaming something is not a problem.
An example:
CREATE TABLE serialdemo (id serial);
SELECT oid FROM pg_class WHERE relname = 'serialdemo';
oid
-------
69427
(1 row)
SELECT attnum FROM pg_attribute WHERE attrelid = 69427 AND attname = 'id';
attnum
--------
1
(1 row)
The dependent objects:
SELECT classid::regclass, objid, deptype
FROM pg_depend
WHERE refobjid = 69427 AND refobjsubid = 1;
classid | objid | deptype
------------+-------+---------
pg_attrdef | 69430 | a
pg_class | 69425 | a
(2 rows)
One of these dependent objects is the column default definition:
SELECT adbin, pg_get_expr(adbin, adrelid) AS adsrc
FROM pg_attrdef WHERE oid = 69430;
-[ RECORD 1 ]---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
adbin | {FUNCEXPR :funcid 480 :funcresulttype 23 :funcretset false :funcvariadic false :funcformat 2 :funccollid 0 :inputcollid 0 :args ({FUNCEXPR :funcid 1574 :funcresulttype 20 :funcretset false :funcvariadic false :funcformat 0 :funccollid 0 :inputcollid 0 :args ({CONST :consttype 2205 :consttypmod -1 :constcollid 0 :constlen 4 :constbyval true :constisnull false :location -1 :constvalue 4 [ 49 15 1 0 0 0 0 0 ]}) :location -1}) :location -1}
adsrc | nextval('serialdemo_id_seq'::regclass)
adsrc is just for the human eye. adbin will not change when the sequence is renamed, although everything keeps working.
The relevant reference to the sequence is :constvalue 4 [ 49 15 1 0 0 0 0 0 ]. This is a 4-byte unsigned integer, the object id (1 × 256² + 15 × 256 + 49 = 69425).
The other dependent object is the sequence:
SELECT relname, relkind FROM pg_class WHERE oid = 69425;
relname | relkind
-------------------+---------
serialdemo_id_seq | S
(1 row)
if change the table name of student to say StudentYear will there be any problem?
Nope. Column of table related with this sequence. If i am not mistake like following:
nextval('student_id_seq'::regclass)
So you don't need to care about renaming tables or columns.

Group rows into two types depending on a value in column

I have a table:
------------------------------------------
Uid | mount | category
-----------------------------------------
1 | 10 | a
1 | 3 | b
3 | 7 | a
4 | 1 | b
4 | 12 | a
4 | 5 | b
1 | 2 | c
2 | 5 | d
I want to have one result like this:
------------------------------------------
Uid | suma | sumnota
-----------------------------------------
1 | 10 | 5
2 | 0 | 5
3 | 7 | 0
4 | 12 | 6
Group by uid;
Suma is sum(mount) where catagory = 'a';
Sumnota is sum(mount) where catagory <> 'a';
Any ideas how to do it?
Use conditional aggregation with CASE statements in SUM() function:
SELECT
uid
, SUM(CASE WHEN category = 'a' THEN mount ELSE 0 END) AS suma
, SUM(CASE WHEN category IS DISTINCT FROM 'a' THEN mount ELSE 0 END) AS sumnota
FROM
yourtable
GROUP BY uid
ORDER BY uid
I'm using IS DISTINCT FROM clause to properly handle NULL values in category column. If that's not your case you could simply use <> operator.
From documentation (bold emphasis mine):
Ordinary comparison operators yield null (signifying "unknown"), not
true or false, when either input is null.
For non-null inputs, IS DISTINCT FROM is the same as the <> operator. However, if both inputs are null it returns false, and if only one input is null it returns true.
Here's a solution more "verbosed" than accepted answer.
WITH
t_suma AS ( SELECT uid, SUM(mount) AS suma
FROM your_table
WHERE category = 'a'
GROUP BY uid ),
t_sumnota AS ( SELECT uid, SUM(mount) AS sumnota
FROM your_table
WHERE category <> 'a' or category is NULL
GROUP BY uid )
SELECT distinct y.uid, COALESCE( suma, 0) AS suma, COALESCE( sumnota, 0 ) AS sumnota
FROM your_table y LEFT OUTER JOIN t_suma ON ( y.uid = t_suma.uid )
LEFT OUTER JOIN t_sumnota ON ( y.uid = t_sumnota.uid )
ORDER BY uid;

TSQL A recursive update?

I'm wondering if exists a recursive update in tsql (CTE)
ID parentID value
-- -------- -----
1 NULL 0
2 1 0
3 2 0
4 3 0
5 4 0
6 5 0
I it possible to update the column value recursively using e.g CTE from ID = 6 to the top most row ?
Yes, it should be. MSDN gives an example:
USE AdventureWorks;
GO
WITH DirectReports(EmployeeID, NewVacationHours, EmployeeLevel)
AS
(SELECT e.EmployeeID, e.VacationHours, 1
FROM HumanResources.Employee AS e
WHERE e.ManagerID = 12
UNION ALL
SELECT e.EmployeeID, e.VacationHours, EmployeeLevel + 1
FROM HumanResources.Employee as e
JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
)
UPDATE HumanResources.Employee
SET VacationHours = VacationHours * 1.25
FROM HumanResources.Employee AS e
JOIN DirectReports AS d ON e.EmployeeID = d.EmployeeID;