In a PostgreSQL table, I wan't to delete some values with conditions. This conditions are based on the begining of a field with a substr. In my example, I want to delete the values not starting with '01' or '02' or '03'. When I first run a SELECT on my values with conditions, it's ok.
Sample data
id | serial_number
---------------------
1 | 01A
2 | 01B
3 | 02A
4 | 02B
5 | 03A
6 | 03B
7 | 03C
8 | 04A
9 | 05A
10 | 06A
Example of a selection
SELECT * FROM my_table
WHERE substr(serial_number, 1, 2) LIKE '01' OR substr(serial_number, 1, 2) LIKE '02' OR substr(serial_number, 1, 2) LIKE '03'
But when I apply the same conditions in a DELETE and add a negation, it removes eveything. I need to change operators from OR to AND.
Delete with same condition (not expected result)
DELETE FROM my_table
WHERE substr(serial_number, 1, 2) NOT LIKE '01' OR substr(serial_number, 1, 2) NOT LIKE '02' AND substr(serial_number, 1, 2) NOT LIKE '03'
Delete with different operators (expected result)
DELETE FROM my_table
WHERE substr(serial_number, 1, 2) NOT LIKE '01' AND substr(serial_number, 1, 2) NOT LIKE '02' AND substr(serial_number, 1, 2) NOT LIKE '03'
How multiple conditions could be true with a AND ? It's not cumulative. I don't understand why I need to change operators.
Some basic boolean math:
True OR False = True
True AND False = False
Consider a single record in your table with value 01A. You don't want to delete this since it's in your list 01, 02, 03.
Running your logic:
substr(serial_number, 1, 2) NOT LIKE '01' = False
substr(serial_number, 1, 2) NOT LIKE '02' = True
Taking that back to the Boolean logic above you'll see that False OR True = True which means we delete the record. However, switching to an AND means False AND True = False and your record 01A is retained as expected.
This is a common issue when dealing with negation and booleans in that it doesn't match how we use OR in english.
Consider a store that cells gift cards in values 20, 50 and 100 dollars:
"Can you go get a gift card for Chili's restaurant. I can't remember
what dollar amounts they sell, but I don't need the 20 dollar one or
the 50 dollar one"
We all understand perfectly well that they don't want the 20 or the 50, but rather the 100 dollar gift card. But in boolean logic the person being asked would return with ALL the gift cards available as 20 != 50 and 50 != 20 and 100 != 50 (and 20). So the OR sees a TRUE for at least one of the conditions specified regardless of the gift card amount.
Related
Have a table where one of the columns has all of the info I need for a report.
I want to substring certain portions of this column as a column in this report, but the problem is that this column has results from 3 varying character lengths.
Example:
Row1: 20180101_ABC_12
Row2: 20180102_DEFG_23
Row3: 20180103_HIJKL_45
In this particular example I want the middle portion (eg. ABC) to be a column called 'Initials', problem is I am using CASE logic for each LEN. Not sure else how to achieve this.
My sample query below. It pulls all of the possible options, but as separate columns. What would I need to do to have these 3 options pull into one column, let's call it 'Initials'?
Thanks
SELECT
FileName
, CASE WHEN LEN(FileName) = 10 THEN SUBSTRING(FileName, 10, 3) ELSE NULL END
, CASE WHEN LEN(FileName) = 11 THEN SUBSTRING(FileName, 10, 4) ELSE NULL END
, CASE WHEN LEN(FileName) = 12 THEN SUBSTRING(FileName, 10, 5) ELSE NULL END
FROM File
In Tableau, you would accomplish this using a calculated field.
Initials:
CASE LEN(FileName)
WHEN 10 THEN SUBSTRING(FileName, 10, 3)
WHEN 11 THEN SUBSTRING(FileName, 10, 4)
WHEN 12 THEN SUBSTRING(FileName, 10, 5)
END
Or maybe
SUBSTRING(FileName
,10
,CASE LEN(FileName)
WHEN 10 THEN 3
WHEN 11 THEN 4
WHEN 12 THEN 5
END
)
But barring the more technical aspect, this can be solved with math (assuming your data is either limited to the 10, 11, and 12, or that the pattern holds):
SUBSTRING(FileName
,10
,LEN(FileName)-7
)
You need 1 CASE statement covering every possible case and not 3 separate ones, because each one creates a new column:
SELECT
FileName
, CASE LEN(FileName)
WHEN 10 THEN SUBSTRING(FileName, 10, 3)
WHEN 11 THEN SUBSTRING(FileName, 10, 4)
WHEN 12 THEN SUBSTRING(FileName, 10, 5)
ELSE NULL
END AS Initials
FROM File
Another way to get everything between the 2 _:
SELECT
FileName
, substring(
left(FileName, len(FileName) - charindex('_', reverse(FileName) + '_')),
charindex('_', FileName) + 1,
len(FileName)
) AS Initials
FROM File
but from your logic I assume that the values in column FileName have the same patern:
<9 digits>_<Initials>_<2 digits>
If this is the case then you can get what you want like this:
SELECT
FileName
, substring(FileName, 10, len(FileName) - 12) AS Initials
FROM File
I have this very weird issue with One2many field.
First let me explain you the scenario...
I have a One2many field in sale.order.line, below code will explain the structure better
class testModule(models.Model):
_name = 'test.module'
name = fields.Char()
class testModule2(models.Model):
_name = 'test.module2'
location_id = fields.Many2one('test.module')
field1 = fields.Char()
field2 = fields.Many2one('sale.order.line')
class testModule3(models.Model):
_inherit = 'sale.order.line'
test_location = fields.One2many('test.module2', 'field2')
CASE 1:
Now what is happening is that when i create a new sales order, i select the partner_id and then add a sale.order.line and inside this line i add the One2many field test_location and then i save.
CASE 2:
Create new sales order, select partner_id then add sale.order.line and inside the sale.order.line add the test_location line [close the sales order line window]. Now after the entry before hitting save i change a field say partner_id and then click save.
CASE 3:
this case is same as case 2 but with the addition that i again change the partner_id field [changes made total 2 times first of case2 and then now], then i click on save.
RESULTS
CASE 1 works fine.
CASE 2 has a issue of
odoo.sql_db: bad query: INSERT INTO "test_module2" ("id", "field2", "field1", "location_id", "create_uid", "write_uid", "create_date", "write_date") VALUES(nextval('test_module2_id_seq'), 27, 'asd', ARRAY[1, '1'], 1, 1, (now() at time zone 'UTC'), (now() at time zone 'UTC')) RETURNING id
ProgrammingError: column "location_id" is of type integer but expression is of type integer[]
LINE 1: ...VALUES(nextval('test_module2_id_seq'), 27, 'asd', ARRAY[1, '...
now for this case i put a debugger on create/write method of sale.order.line to see waht the values are getting passed..
values = {u'product_uom': 1, u'sequence': 0, u'price_unit': 885, u'product_uom_qty': 1, u'qty_invoiced': 0, u'procurement_ids': [[5]], u'qty_delivered': 0, u'qty_to_invoice': 0, u'qty_delivered_updateable': False, u'customer_lead': 0, u'analytic_tag_ids': [[5]], u'state': u'draft', u'tax_id': [[5]], u'test_location': [[5], [0, 0, {u'field1': u'asd', u'location_id': [1, u'1']}]], 'order_id': 20, u'price_subtotal': 885, u'discount': 0, u'layout_category_id': False, u'product_id': 29, u'price_total': 885, u'invoice_status': u'no', u'name': u'[CARD] Graphics Card', u'invoice_lines': [[5]]}
in the above values location_id is getting passed like u'location_id': [1, u'1']}]] which is not correct...so for this i correct the issue in code and the update the values and pass that...
CASE 3
if the user changes the field say 2 or more than 2 times then the values are
values = {u'invoice_lines': [[5]], u'procurement_ids': [[5]], u'tax_id': [[5]], u'test_location': [[5], [1, 7, {u'field1': u'asd', u'location_id': False}]], u'analytic_tag_ids': [[5]]}
here
u'location_id': False
MULTIPLE CASE
if the user does case 1 the on the same record does case 2 or case 3 then sometimes the line will be saved as field2 = Null or False in the database other values like location_id and field1 will have data but not field2
NOTE: THIS HAPPENS WITH ANY FIELD NOT ONLY PARTNER_ID FIELD ON HEADER LEVEL OF SALE ORDER
I tried debugging myself but couldn't find the reason why this is happening .
I am new to PostgreSQL and to database queries in general.
I have a list of user_id with university courses taken, date started and finished.
Some users have multiple entries and sometimes the start date or finish date (or both) are missing.
I need to retrieve the longest course taken by a user or, if start date is missing, the latest.
If multiple choices are still available, then pick random among the multiple options.
For example
on user 2 (below) I want to get only "Economics and Politics" because it has the latest date;
on user 6, only "Electrical and Electronics Engineering" because it is the longer course.
The query I did doesn't work (and I think I am off-track):
(SELECT Q.user_id, min(Q.started_at) as Started_on, max(Q.ended_at) as Completed_on,
q.field_of_study
FROM
(select distinct(user_id),started_at, Ended_at, field_of_study
from educations
) as Q
group by Q.user_id, q.field_of_study )
order by q.user_id
as the result is:
User_id Started_on Completed_on Field_of_studies
2 "2001-01-01" "" "International Economics"
2 "" "2002-01-01" "Economics and Politics"
3 "1992-01-01" "1999-01-01" "Economics, Management of ..."
5 "2012-01-01" "2016-01-01" ""
6 "2005-01-01" "2009-01-01" "Electrical and Electronics Engineering"
6 "2011-01-01" "2012-01-01" "Finance, General"
6 "" "" ""
6 "2010-01-01" "2012-01-01" "Financial Mathematics"
I think this query should do what you need, it relies on calculating the difference in days between ended_at and started_at, and uses 0001-01-01 if the started_at is null (making it a really long interval):
select
educations.user_id,
max(educations.started_at) started_at,
max(educations.ended_at) ended_at,
max(educations.field_of_study) field_of_study
from educations
join (
select
user_id,
max(
ended_at::date
-
coalesce(started_at, '0001-01-01')::date
) max_length
from educations
where (started_at is not null or ended_at is not null)
group by user_id
) x on educations.user_id = x.user_id
and ended_at::date
-
coalesce(started_at, '0001-01-01')::date
= x.max_length
group by educations.user_id
;
Sample SQL Fiddle
Unfortunately, I have a table like the following:
DROP TABLE IF EXISTS my_list;
CREATE TABLE my_list (index int PRIMARY KEY, mystring text, status text);
INSERT INTO my_list
(index, mystring, status) VALUES
(12, '', 'D'),
(14, '[id] 5', 'A'),
(15, '[id] 12[num] 03952145815', 'C'),
(16, '[id] 314[num] 03952145815[name] Sweet', 'E'),
(19, '[id] 01211[num] 03952145815[name] Home[oth] Alabama', 'B');
Is there any trick to get out number of [id] as integer from the mystring text shown above? As though I ran the following query:
SELECT index, extract_id_function(mystring), status FROM my_list;
and got results like:
12 0 D
14 5 A
15 12 C
16 314 E
19 1211 B
Preferably with only simple string functions and if not regular expression will be fine.
If I understand correctly, you have a rather unconventional markup format where [id] is followed by a space, then a series of digits that represents a numeric identifier. There is no closing tag, the next non-numeric field ends the ID.
If so, you're going to be able to do this with non-regexp string ops, but only quite badly. What you'd really need is the SQL equivalent of strtol, which consumes input up to the first non-digit and just returns that. A cast to integer will not do that, it'll report an error if it sees non-numeric garbage after the number. (As it happens I just wrote a C extension that exposes strtol for decoding hex values, but I'm guessing you don't want to use C extensions if you don't even want regex...)
It can be done with string ops if you make the simplifying assumption that an [id] nnnn tag always ends with either end of string or another tag, so it's always [ at the end of the number. We also assume that you're only interested in the first [id] if multiple appear in a string. That way you can write something like the following horrible monstrosity:
select
"index",
case
when next_tag_idx > 0 then substring(cut_id from 0 for next_tag_idx)
else cut_id
end AS "my_id",
"status"
from (
select
position('[' in cut_id) AS next_tag_idx,
*
from (
select
case
when id_offset = 0 then null
else substring(mystring from id_offset + 4)
end AS cut_id,
*
from (
select
position('[id] ' in mystring) AS id_offset,
*
from my_list
) x
) y
) z;
(If anybody ever actually uses that query for anything, kittens will fall from the sky and splat upon the pavement, wailing in horror all the way down).
Or you can be sensible and just use a regular expression for this kind of string processing, in which case your query (assuming you only want the first [id]) is:
regress=> SELECT
"index",
coalesce((SELECT (regexp_matches(mystring, '\[id\]\s?(\d+)'))[1])::integer, 0) AS my_id,
status
FROM my_list;
index | my_id | status
-------+----------------+--------
12 | 0 | D
14 | 5 | A
15 | 12 | C
16 | 314 | E
19 | 01211 | B
(5 rows)
Update: If you're having issues with unicode handling in regex, upgrade to Pg 9.2. See https://stackoverflow.com/a/14293924/398670
I have a simple query. Something like this:
SELECT l.list_name, COUNT(order_id)
FROM orders o JOIN lists l ON l.order_id=o.order_id
WHERE l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%'
GROUP BY l.list_name
The situation looks like this: overight a stored procedure is updating the lists table, but it chops lists in parts if there are more than 1000 orders.
If I have 1200 orders with criteria or list 'orders_1', then my procedure will create two lists: 'orders_1_1' 'and orders_1_2', the first having 1,000 and second 200 orders.
So when I run my query to count those orders I will get results like so:
list_name count
orders_1 100
orders_1_more_than_100_1 1000
orders_1_more_than_100_2 200
orders_2 400
orders_3_1 1000
orders_3_2 1000
orders_3_3 420
orders_3_more_than_100_1 1000
orders_3_more_than_100_2 900
orders_3_more_than_200_1 1000
orders_3_more_than_200_2 1000
orders_3_more_than_200_3 100
orders_4 200
orders_4_more_than_300 200
The result I would like to get should look like this:
list_name count
orders_1 100
orders_1_more_than_100 1200
orders_2 400
orders_3 2420
orders_3_more_than_100 1900
orders_3_more_than_200 2100
orders_4 200
orders_4_more_than_300 200
So that it will sum all lists that start the same.
Any ideas on that? :)
These are the exact values that I have in my list_names column:
WYS_AUT_PISMO_NR_6
WYS_AUT_PISMO_NR_5_POWYZEJ_240
WYS_AUT_PISMO_NR_5_DO_240
WYS_AUT_PISMO_NR_4_POWYZEJ_240_5
WYS_AUT_PISMO_NR_4_POWYZEJ_240_4
WYS_AUT_PISMO_NR_4_POWYZEJ_240_3
WYS_AUT_PISMO_NR_4_POWYZEJ_240_2
WYS_AUT_PISMO_NR_4_POWYZEJ_240_1
WYS_AUT_PISMO_NR_4_DO_240
WYS_AUT_PISMO_NR_3_POWYZEJ_240
WYS_AUT_PISMO_NR_3_DO_240
WYS_AUT_PISMO_NR_2_POWYZEJ_240
WYS_AUT_PISMO_NR_2_DO_240
WYS_AUT_PISMO_NR_1
What I want is to group them like so:
WYS_AUT_PISMO_NR_6
WYS_AUT_PISMO_NR_5_POWYZEJ_240
WYS_AUT_PISMO_NR_5_DO_240
WYS_AUT_PISMO_NR_4_POWYZEJ_240 /*here I must group those 5 lists*/
WYS_AUT_PISMO_NR_4_DO_240
WYS_AUT_PISMO_NR_3_POWYZEJ_240
WYS_AUT_PISMO_NR_3_DO_240
WYS_AUT_PISMO_NR_2_POWYZEJ_240
WYS_AUT_PISMO_NR_2_DO_240
WYS_AUT_PISMO_NR_1
This monstruous expression will isolate string starting from beginning of parameter to last _ if there are more than one underscores:
select case when len (l.list_name) - len (replace (l.list_name, '_', '')) > 1
then left(l.list_name,
len (l.list_name) - charindex('_', reverse(l.list_name)))
else l.list_name
end
Alternatively you might strip 'orders_' from string, replace underscore with dot and convert it to float, then to int to remove decimals, and then back to string using this monstrosity:
select 'orders_' + cast (cast (cast (
replace (substring (#str, 8, 100), '_', '.')
as float) as int) as varchar(100))
To avoid repeating this blobs, use derived table instead of lists:
SELECT l.TrimmedListName, COUNT(order_id)
FROM orders o
JOIN
(
select lists.*,
-- Remove optional list continuation number
case when len (list_name) - len (replace (list_name, '_', '')) > 1
then left(list_name,
len (list_name) - charindex('_', reverse(list_name)))
else list_name
end AS TrimmedListName
from lists
) l ON l.order_id=o.order_id
WHERE (l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%')
GROUP BY l.TrimmedListName
try something like
select substring(l.list_name, 0, 8), count(order_Id)
FROM orders o JOIN lists l ON l.order_id=o.order_id
WHERE l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%'
group by substring(l.list_name, 0, 8)
Updated answer for the updated question:
select newColName, COUNT(order_id)
from
(
select case when GetSubstringCount(l.list_name, '_', '') > 1 then
SUBSTRING(l.list_name, 0, len(l.list_name) - 2)
else l.list_name end as NewColName
, order_Id
FROM orders o JOIN lists l ON l.order_id=o.order_id
WHERE l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%'
) mySubTable
group by newColName
You'll need something like This to create the GetSubstringCount method