Dynamically grow user selection when they have selected "all" - postgresql

I have users with multiple email preferences and I want to grow their preference selection when new preference options are added if they have selected 'all' as their preference.
I can do this through triggers for each of the preference options table. On INSERT I can also UPDATE the user preference array and append the new options. I am worried about performance when the user list begins to grow since each preference option may be updated and each user can have multiple preferences.
USER_1
PREFERENCE_1
OPTION_1 -> ALL
OPTION_2 -> [1, 2, 3]
OPTION_3 -> [1, 2]
PREFERENCE_2
OPTION_1 -> ALL
OPTION_2 -> ALL
OPTION_3 -> ALL
How should I design the schema to accommodate dynamic column arrays? Or perhaps I shouldn't be using arrays at all?
Additional question:
Is it possible to achieve the All but x selection? A user may want all of the options but not some of the options. The selection should still grow dynamically.

It looks like your question isn't getting much love around here.
You have a couple of options. The best option is to build out the relational tables to store this, but you will need to use a trigger or another indicator column to denote that a user is up for every option.
A second option that I have used a number of times is a bitstring. This is essentially a bitmask. The only regret I have about using this is having to explain how it works to developers fresh out of some shake-and-bake correspondence course who fancy themselves "full-stack."
create table preference (
bitmask bit(30) not null primary key, -- Adjust size to some overkill number
pref_name text
);
insert into preference
select ((2^n)::bigint)::bit(30) as bitmask,
'Option '||(n+1)::text as pref_name
from generate_series(0, 10, 1) as gs(n);
create table prefs_user (
id int primary key,
user_name text,
user_prefs bit(30)
);
insert into prefs_user values
(1, 'me', ~(0::bit(30))),
(2, 'you', 22::bit(30)),
(3, 'him', ~(8::bit(30))); -- all except Option 4
To join to see which options are enabled for a prefs_user:
select *
from prefs_user u
join preference p
on (u.user_prefs & p.bitmask)::bigint > 0
order by u.id, p.bitmask;
id | user_name | user_prefs | bitmask | pref_name
----+-----------+--------------------------------+--------------------------------+-----------
1 | me | 111111111111111111111111111111 | 000000000000000000000000000001 | Option 1
1 | me | 111111111111111111111111111111 | 000000000000000000000000000010 | Option 2
1 | me | 111111111111111111111111111111 | 000000000000000000000000000100 | Option 3
1 | me | 111111111111111111111111111111 | 000000000000000000000000001000 | Option 4
1 | me | 111111111111111111111111111111 | 000000000000000000000000010000 | Option 5
1 | me | 111111111111111111111111111111 | 000000000000000000000000100000 | Option 6
1 | me | 111111111111111111111111111111 | 000000000000000000000001000000 | Option 7
1 | me | 111111111111111111111111111111 | 000000000000000000000010000000 | Option 8
1 | me | 111111111111111111111111111111 | 000000000000000000000100000000 | Option 9
1 | me | 111111111111111111111111111111 | 000000000000000000001000000000 | Option 10
1 | me | 111111111111111111111111111111 | 000000000000000000010000000000 | Option 11
2 | you | 000000000000000000000000010110 | 000000000000000000000000000010 | Option 2
2 | you | 000000000000000000000000010110 | 000000000000000000000000000100 | Option 3
2 | you | 000000000000000000000000010110 | 000000000000000000000000010000 | Option 5
3 | him | 111111111111111111111111110111 | 000000000000000000000000000001 | Option 1
3 | him | 111111111111111111111111110111 | 000000000000000000000000000010 | Option 2
3 | him | 111111111111111111111111110111 | 000000000000000000000000000100 | Option 3
3 | him | 111111111111111111111111110111 | 000000000000000000000000010000 | Option 5
3 | him | 111111111111111111111111110111 | 000000000000000000000000100000 | Option 6
3 | him | 111111111111111111111111110111 | 000000000000000000000001000000 | Option 7
3 | him | 111111111111111111111111110111 | 000000000000000000000010000000 | Option 8
3 | him | 111111111111111111111111110111 | 000000000000000000000100000000 | Option 9
3 | him | 111111111111111111111111110111 | 000000000000000000001000000000 | Option 10
3 | him | 111111111111111111111111110111 | 000000000000000000010000000000 | Option 11
User me selected all options, so any options you add up to a count of 30 will always be selected.
User you selected only three options. Adding options will not affect the user's selection.
User him selected every option except option 4. New options will be selected for this user.
The bit string maps easily to an array from a checkbox group with name properties set to log-base-2 of the bitmask:
select log(2, bitmask::bigint)::int, pref_name from preference;
log | pref_name
-----+-----------
0 | Option 1
1 | Option 2
2 | Option 3
3 | Option 4
4 | Option 5
5 | Option 6
6 | Option 7
7 | Option 8
8 | Option 9
9 | Option 10
10 | Option 11
(11 rows)
When the post comes in, you can create the bitmap value through a simple loop or list comprehension on the host language side.

Related

PostgreSQL 9.3:Updating table(order column) from another table, getting same values in rows

I need help with updating table from another table in Postgres Db.
Long story short we ended up with corrupted data in db, and now I need to update one table with values from another.
I have table with this data table wfc:
| step_id | command_id | commands_order |
|---------|------------|----------------|
| 1 | 1 | 0 |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 1 | 4 | 3 |
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 2 | 3 | 1 |
| 2 | 3 | 1 |
| 2 | 4 | 3 |
and I want to update values in command_order column from another table, so I can have result like this:
| step_id | command_id | commands_order|
|---------|------------|---------------|
| 1 | 1 | 0 |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 1 | 4 | 3 |
| 1 | 1 | 4 |
| 2 | 2 | 0 |
| 2 | 3 | 1 |
| 2 | 3 | 2 |
| 2 | 4 | 3 |
It was looking like easy task, but problem is to update rows for same command_id, it is writing same value in commands_order
SQL that I tried is:
UPDATE wfc
SET commands_order = CAST(sq.input_step_id as INTEGER)
FROM (
SELECT wfp.step_id, wfp.input_command_id, wfp.input_step_id
from wfp
order by wfp.step_id, wfp.input_step_id
) AS sq
WHERE (wfc.step_id=sq.step_id AND wfc.command_id=CAST(sq.input_command_id as INTEGER));
SQL Fiddle http://sqlfiddle.com/#!15/4efff4/4
I am pretty stuck with this, please help.
Thanks in advance.
Assuming you are trying to number the rows in the order in which they were created, and as long as you understand that ctid will chnage on update and with VACCUUM FULL, you can do the following:
select step_id, command_id, rank - 1 as command_order
from (select step_id, command_id, ctid as wfc_ctid, rank() over
(partition by step_id order by ctid)
from wfc) as wfc_ordered;
This will give you the wfc table with the ordering that you want. If you do update the original table, the ctids will change, so it's probably safer to create a copy of the table with the above query.

How to set sequence number of sub-elements in TSQL unsing same element as parent?

I need to set a sequence inside T-SQL when in the first column I have sequence marker (which is repeating) and use other column for ordering.
It is hard to explain so I try with example.
This is what I need:
|------------|-------------|----------------|
| Group Col | Order Col | Desired Result |
|------------|-------------|----------------|
| D | 1 | NULL |
| A | 2 | 1 |
| C | 3 | 1 |
| E | 4 | 1 |
| A | 5 | 2 |
| B | 6 | 2 |
| C | 7 | 2 |
| A | 8 | 3 |
| F | 9 | 3 |
| T | 10 | 3 |
| A | 11 | 4 |
| Y | 12 | 4 |
|------------|-------------|----------------|
So my marker is A (each time I met A I must start new group inside my result). All rows before first A must be set to NULL.
I know that I can achieve that with loop but it would be slow solution and I need to update a lot of rows (may be sometimes several thousand).
Is there a way to achive this without loop?
You can use window version of COUNT to get the desired result:
SELECT [Group Col], [Order Col],
COUNT(CASE WHEN [Group Col] = 'A' THEN 1 END)
OVER
(ORDER BY [Order Col]) AS [Desired Result]
FROM mytable
If you need all rows before first A set to NULL then use SUM instead of COUNT.
Demo here

PostgreSQL simple count query

Trying to scale this down so the answer is simple. I can probably extrapolate the answers here to apply to a bigger data set.
Given the following table:
+------+-----+
| name | age |
+------+-----+
| a | 5 |
| b | 7 |
| c | 8 |
| d | 8 |
| e | 10 |
+------+-----+
I want to make a table that shows the count of people where their age is equal to or greater than x. For instance, the table about would produce:
+--------------+-------+
| at least age | count |
+--------------+-------+
| 5 | 5 |
| 6 | 4 |
| 7 | 4 |
| 8 | 3 |
| 9 | 1 |
| 10 | 1 |
+--------------+-------+
Is there a single query that can accomplish this task? Obviously, it is easy to write a simple function for it, but I'm hoping to be able to do this quickly with one query.
Thanks!
Yes, what you're looking for is a window function.
with cte_age_count as (
select age,
count(*) c_star
from people
group by age)
select age,
sum(c_star) over (order by age
range between unbounded preceding
and current row)
from cte_age_count
Not syntax checked ... let me know if it works!

Tableau: DATEDIFF( 'days', MIN([Start Date]), [End Date])

Cheers!
I'm trying to get a chart working that shows me the count of work orders that are completed each day after work on a unit (serial number) starts. I'd like to be able to "shadow" multiple serial numbers on top of each other, normalized to a start date of '0'.
Currently I have columns in my data set:
Work order number (0..999), repeats for each serial number
Serial number (0..999)
Work order start date (Datetime)
Work order end date (Datetime)
Say for instance that a new serial number starts each day, contains 5 work orders, and requires 5 days to complete (there are 5 units in WIP at any given time).
The data might look like (dates shown as ints):
| Work order number | Serial number | Work order start date | Work order end date |
| ----------------- | ------------- | --------------------- | ------------------- |
| 1 | 1 | 1 | 2 |
| 2 | 1 | 1 | 3 |
| 3 | 1 | 2 | 4 |
| 4 | 1 | 3 | 5 |
| 5 | 1 | 4 | 5 |
| 1 | 2 | 2 | 3 |
| 2 | 2 | 2 | 4 |
| 3 | 2 | 3 | 5 |
| 4 | 2 | 4 | 6 |
| 5 | 2 | 5 | 6 |
I'm assuming I'll need a calculated column that would perhaps go something like:
[Work order end days since start] =
[Work order end date] - MIN(
IF(*serial number matches current*, [Work order start date], NULL)
)
I (clearly) have no idea how to actually create such a calculated field in Tableau.
The values in the column (same order as the data above) should be:
| Work order end days since start |
| ------------------------------- |
| 1 |
| 2 |
| 3 |
| 4 |
| 4 |
| 1 |
| 2 |
| 3 |
| 4 |
| 4 |
Any guidance or help? Happy to clarify anything as well. Many thanks! Cheers!
You will have better results with this kind of data if you reshape it to have a single date column and add a type column indicating whether the current row describes the start or completion of a workorder.
| Work order number | Serial number | date | type |
Think of each row representing a state change, not a work order.
Open work orders on a particular date would be those that have a start record prior to that date, but don't have a completion record prior to that date. If you define a calculated field as +1 if type = New and -1 if type = Completion, then you can use a running total of that field to view the number of open work orders over time.

Typo3 TCA custom table

I have this situation, I have one offer, and that offer have n number of dates, and n number of options. So I have two additional tables for offer. And third one, which is a price, but price depends of date, and offer. And it is like this:
| | date 1 | date 2 | date 3 |
| offer 1 | price 11 | price 12 | price 13 |
| offer 2 | price 21 | price 22 | price 23 |
| offer 3 | price 31 | price 32 | price 33 |
Is there any way to create TCA custom field to insert all of this Price values at once?
So, basically I need one table with input fields and to store also uid of date and offer in it as reference.
Make more than one table... Tables with dynamic col count are horrible bad to maintain.
Table Offer:
uid | Name | Desc
1 | offer1 | This is some cool shit
2 | offer2 | dsadsad
3 | offer3 | sdadsdsadsada
Table Date:
uid | date
1 | 12.02.2014
2 | 12.03.2014
3 | 20.03.2014
Table Prices:
uid | date | offer | price
1 | 1 | 1 | price11
2 | 1 | 2 | price21
3 | 1 | 3 | price31
4 | 2 | 1 | price12
5 | 2 | 2 | price22
6 | 2 | 3 | price32
7 | 3 | 1 | price13
8 | 3 | 2 | price23
9 | 3 | 3 | price33
And then its straight forward...