SQL - foreach column make slug form different column - sql

I am new with writing complex SQL queries. I am on Postgres 14 and I I am executing migration file which has following concept.
I want to add string field with name slug which will be required like:
$this->addSql('ALTER TABLE tag ADD COLUMN slug VARCHAR NOT NULL');
As the table is already populated and my new column needs to be required
I want to:
Create Null-able column, then update slug table column with valid not null values and finally ALTER column to set NOT NULL constraint!
My table (with potential slug column):
| id | name | type | slug |
|----|------------------|------|------------------|
| 1 | LARPs: The Series| | larps-the-series |
|----|------------------|------|------------------|
| 2 | #MyHaliburton. | | my-haliburton |
Catch:
Added column called “slug” is is a 'slugified' version of the name (all lower case, removed punctuation, spaces replaces with dashes).
I started with:
UPDATE `tag` SET slug = lower(name),
slug = replace(slug, ':', '-'),
slug = replace(slug, '#'', ''),
...
Is this right way to cover all the cases? And yet, how to do it for all fields? Should I use FOR EACH?
Thanks

You can do your slug generation in one command, combining a call to LOWER with two calls to REGEXP_REPLACE, one of which strips leading and trailing non-alphabetic characters, and the other which replaces non-alphabetic characters internal to the string with a - and also inserts a - when a lower-case character is followed by an upper case character:
ALTER TABLE tag ADD COLUMN slug VARCHAR;
UPDATE tag
SET slug = LOWER(
REGEXP_REPLACE(
REGEXP_REPLACE(name, '^[^A-Za-z]+|[^A-Za-z]+$', '', 'g'),
'[^A-Za-z]+|(?<=[a-z])(?=[A-Z])', '-', 'g')
)
;
ALTER TABLE tag ALTER COLUMN slug SET NOT NULL;
Output (for your sample input):
id name type slug
1 LARPs: The Series larps-the-series
2 #MyHaliburton. my-haliburton
Demo on db-fiddle

Related

PostgreSQL constraint to prevent overlapping ranges

I wonder if it's possible to write a constraint that would make ranges unique. These ranges are represented as two string-typed columns bottom and top. Say, If I have the following row in a database,
| id | bottom | top |
|----|--------|-------|
| 1 | 10000 | 10999 |
inserting the row (2, 10100, 10200) would immediately result in constraint violation error.
P.S
I can't switch to integers, unfortunately -- only strings
Never store numbers as strings, and always use a range data type like int4range to store ranges. With ranges, you can easily use an exclusion constraint:
ALTER TABLE tab ADD EXCLUDE USING gist (bottom_top WITH &&);
Here, bottom_top is a range data type.
If you have to stick with the broken data model using two string columns, you can strip # characters and still have an exclusion constraint with
ALTER TABLE tab ADD EXCLUDE USING gist (
int4range(
CAST(trim(bottom, '#') AS integer),
CAST(trim(top, '#') AS integer),
'[]'
) WITH &&
);

In Postgres, can you update CITEXT value to a different casing?

Running a posgresql database.
I have a table with CITEXT columns for case-insensitivity. When I try to update a a CITEXT value to the same word in different casing it does not work. Postgres returns 1 row updated, as it targeted 1 row, but the value is not changed.
Eg
Table Schema - users
Column | Type
___________________________
user_id | PRIMARY KEY SERIAL
user_name | CITEXT
age | INT
example row:
user_id | user_name | age
_________________________________
1 | ayanaMi | 99
SQL command:
UPDATE users SET user_name = 'Ayanami' WHERE user_id = 1
The above command turns 1 UPDATED, but the casing does not change. I assume this is because postgres sees them as the same value.
The docs state:
If you'd like to match case-sensitively, you can cast the operator's arguments to text.
https://www.postgresql.org/docs/9.1/citext.html
I can force a case sensitive search by using CAST as such:
SELECT * FROM users WHERE CAST(user_name AS TEXT) = `Ayanami`
[returns empty row]
Is there a way to force case sensitive updating?

Modifying dynamic column contents

I am trying to create a new column inside a dynamic column.
My table Templates just has two columns: ID, Structure (blob)
I run this query:
UPDATE `Templates` SET `Structure` = COLUMN_ADD(`Structure`, 'general', '') where `Templates`.`ID` = 1
Structure Result (Using COLUMN_JSON for display):
{"general":""}
Then I run this query:
UPDATE `Templates` SET `Structure` = COLUMN_ADD(COLUMN_GET(`Structure`, 'general' as CHAR), 'Inner', 'value') WHERE `Templates`.`ID` = 1
Structure Result:
{"Inner":"value"}
The result I want after both queries:
{"general": {"Inner":"value"}}
How can I get a column added to the dynamic "general" column instead of replacing the contents?
First, here is what happens with your query.
MariaDB [test]> CREATE TABLE Templates (ID INT, Structure BLOB);
Query OK, 0 rows affected (0.24 sec)
MariaDB [test]> INSERT INTO Templates VALUES (1, COLUMN_CREATE('general',''));
Query OK, 1 row affected (0.05 sec)
MariaDB [test]> SELECT COLUMN_JSON(Structure) FROM Templates;
+------------------------+
| COLUMN_JSON(Structure) |
+------------------------+
| {"general":""} |
+------------------------+
1 row in set (0.00 sec)
At this point you have in Structure one dynamic column with name general and empty string as a value.
Then you do this:
UPDATE `Templates`
SET `Structure` = COLUMN_ADD(
COLUMN_GET(`Structure`, 'general' as CHAR),
'Inner',
'value'
) ...
Your COLUMN_GET gets the value of general dynamic column, which is an empty string, and uses it as the first argument for COLUMN_ADD. It's a useless exercise, because if you want to run COLUMN_ADD on an empty string, you can just say so in the query or use COLUMN_CREATE; and if you want to actually add something to the existing value of the blob, you need to use the name of the blob.
So, COLUMN_ADD works on an empty string -- in other words, creates a clean new value for Structure, discarding everything it had -- and adds a dynamic column with name Inner and value value. That's why you are getting this:
MariaDB [test]> SELECT COLUMN_JSON(Structure) FROM Templates;
+------------------------+
| COLUMN_JSON(Structure) |
+------------------------+
| {"Inner":"value"} |
+------------------------+
1 row in set (0.00 sec)
Apparently, what you want to do instead is to set the value of general column to a new dynamic column.
You don't need to fetch general column for that, because COLUMN_ADD(x,y,z) will replace the value if a column y already exists in blob x. But you need to construct a new dynamic column for the new value of general.
So, what you should do is
UPDATE `Templates`
SET `Structure` = COLUMN_ADD(
`Structure`,
'general',
COLUMN_CREATE('Inner','value')
) ...
This accounts for a more general case when Structure also contains other columns, not only general, and you want to preserve them. If it's not the case, and you want to make sure the blob contains only general, then you can do
UPDATE `Templates`
SET `Structure` = COLUMN_CREATE(
'general',
COLUMN_CREATE('Inner','value')
)

Changing a column type from integer to string

Using PostgreSQL, what's the command to migrate an integer column type to a string column type?
Obviously I'd like to preserve the data, by converting the old integer data to strings.
You can convert from INTEGER to CHARACTER VARYING out-of-the-box, all you need is ALTER TABLE query chaning column type:
SQL Fiddle
PostgreSQL 9.3 Schema Setup:
CREATE TABLE tbl (col INT);
INSERT INTO tbl VALUES (1), (10), (100);
ALTER TABLE tbl ALTER COLUMN col TYPE CHARACTER VARYING(10);
Query 1:
SELECT col, pg_typeof(col) FROM tbl
Results:
| col | pg_typeof |
|-----|-------------------|
| 1 | character varying |
| 10 | character varying |
| 100 | character varying |
I suggest a four step process:
Create a new string column. name it temp for now. See http://www.postgresql.org/docs/9.3/static/ddl-alter.html for details
Set the string column. something like update myTable set temp=cast(intColumn as text) see http://www.postgresql.org/docs/9.3/static/functions-formatting.html for more interesting number->string conversions
Make sure everything in temp looks the way you want it.
Remove your old integer column. Once again, see http://www.postgresql.org/docs/9.3/static/ddl-alter.html for details
Rename temp to the old column name. Again: http://www.postgresql.org/docs/9.3/static/ddl-alter.html
This assumes you can perform the operation while no clients are connected; offline. If you need to make this (drastic) change in an online table, take a look at setting up a new table with triggers for live updates, then swap to the new table in an atomic operation. see ALTER TABLE without locking the table?

Check constraint for a flag column

Database is MS SQLServer
Data example:
| Name | defaultValue | value |
| one | true | valone |
| one | false | valtwo |
| one | false | valthree |
I'm after a way of constraining the table such that each 'Name' can only have one row with 'defaultValue' set to true
Create a computed column like this:
ALTER TABLE yourtable
ADD ValueCheck AS CASE defaultValue
WHEN true THEN 1
WHEN false THEN NULL
END
and then add unique constraint for (Name, ValueCheck)
I liked Michael's idea but it will only allow you one false value per name in SQL Server. To avoid this how about using
ALTER TABLE yourtable
ADD [ValueCheck] AS
(case [defaultValue] when (1) then ('~Default#?#') /*Magic string!*/
else value end) persisted
and then add unique constraint for (Name, ValueCheck).
I am assuming that name, value combinations will be unique. If the value column does not allow NULLs then using NULL rather than the magic string would be preferable otherwise choose a string that cannot appear in the data (e.g. 101 characters long if the value column only allows 100 chars)
You can use a TRIGGER to validate this constraint on update or insert events and roll back the transaction if it was invalid.