SymmetricDS: lookup Table Router route to common and specific node - data-synchronization

In SymmetricDS I know that 'lookuptable' in SYM_ROUTER can sync to specific node by referring a column that has the node id.
What I would want is for common data to sync to all node, how to configure this?
Example : I have 2 tables, Table 1 is Item, Table 2 is Store
ITEM_ID | ITEM_NAME | BRAND_ID
Item 1 | name | A
Item 2 | name | B
Item 3 | name | C
BRAND_ID | STORE_ID
A | ALL
B | 001
C | 002
I want to sync Item 1 to ALL nodes, Item 2 to node 001, Item 3 to node 002, etc.
I will use below router to sync Item table. Item 2 and Item 3 are synced OK, but Item 1 fail to sync to all node, do you have any way to sync Item 1 ?
insert into SYM_ROUTER (
router_id, source_node_group_id, target_node_group_id, router_type, router_expression, create_time, last_update_time
) values (
'corp-2-store-ok','corp', 'store', 'lookuptable', 'LOOKUP_TABLE=STORE KEY_COLUMN=BRAND_ID LOOKUP_KEY_COLUMN=BRAND_ID EXTERNAL_ID_COLUMN=STORE_ID', current_timestamp, current_timestamp
);

You'll need a router with the router_type 'subselect' and router_expression something like
c.external_id in (
select :STORE_ID from dual
union all
select external_id from sym_node where 'ALL' = :STORE_ID
)

Related

Complex Nested SUM / Sub-Select

UPDATED with sample data etc.
I am a bit over my head on this complex query. Some background: This is a rails app and I have expenditures model which has many expenditure_items which each has an amount column - these all sum up to a total for the related expenditure.
A given expenditure can be an Order which then can have multiple (or single or nil) related Invoice expenditures. I am looking for a single query that jives me all the orders which have total invoices and identify those that have invoices totalling more than a threshold (in my case 10%).
I get the idea from my searching that I need a sub-select here but I can't sort it out. I apologize as raw SQL is not my wheel house - normal Rails Active Record calls meet 99% of my needs.
Sample Data:
=> SELECT * FROM expenditures WHERE id = 17;
id | category | parent_id
-----+----------------+----------
17 | purchase_order |
=> SELECT * FROM expenditures_items WHERE expenditure_id = 17;
id | amount
-----+-------------
1 | 1000.00
2 | 2000.00
I need to obtain the SUM ( expenditures.amount ) in my result - the original order of $3,000.00.
Related Expenditures (invoices)
=> SELECT * FROM expenditures WHERE category = 'invoice', parent_id = 17;
id | category | parent_id
-----+----------------+----------
46 | invoice | 17
88 | invoice | 17
=> SELECT * FROM expenditures_items WHERE expenditure_id IN (46, 88) ;
id | amount | expenditure_id
-----+----------+---------------
23 | 500.00 | 46
24 | 1000.00 | 46
78 | 550.00 | 88
79 | 1100.00 | 88
Order 17 has two invoices (46 & 88) totalling $3,150.00 - this is the SUM of all the invoice expenditure_item amounts.
In the end I am looking for the SQL that gets me something like this:
=> SELECT * FROM expenditures WHERE category = 'purchase_order';
id | category | expenditure_total | invoice_total | percent
-----+----------------+-------------------+---------------+---------
17 | purchase_order | 3000.00 | 3150.00 | 5
45 | purchase_order | 4000.00 | 3000.00 | -25
75 | purchase_order | 7000.00 | 7000.00 | 0
99 | purchase_order | 10000.00 | 11100.00 | 11
percent is invoice_total / expenditure_total - 1.
I also need to (perhaps a HAVING clause) filter out only the results that have a percent > a threshold (say 10).
From all my searching this seems to be a sub query along with some joins but I am lost at this point.
UPDATED Further
I had another look - this is close:
SELECT DISTINCT expenditures.*, SUM( invoice_items.amount ) as invoiced_total FROM "expenditures" JOIN expenditures AS invoices ON invoices.category = 'invoice' AND expenditures.id = CAST( invoices.ancestry AS INT) JOIN expenditure_items ON expenditure_items.expenditure_id = expenditures.id JOIN expenditure_items AS invoice_items ON invoice_items.expenditure_id = invoices.id WHERE "expenditures"."category" IN ($1, $2) GROUP BY expenditures.id HAVING (( SUM( invoice_items.amount ) / SUM( expenditure_items.amount ) ) > 1.1 ) [["category", "work_order"], ["category", "purchase_order"]]
Here is the odd thing - the invoiced_total in the select works. I get the proper amounts as per my example. The issue seems to be in my HAVING where it only pulls the SUM on the first invoice.
UPDATE 3
Soooooo close:
SELECT DISTINCT
expenditures.*,
( SELECT
SUM(expenditure_items.amount)
FROM expenditure_items
WHERE expenditure_items.expenditure_id = expenditures.id ) AS order_total,
( SELECT
SUM(expenditure_items.amount)
FROM expenditure_items
JOIN expenditures invoices ON expenditure_items.expenditure_id = invoices.id
AND CAST (invoices.ancestry AS INT) = expenditures.id ) AS invoice_total
FROM "expenditures"
INNER JOIN "expenditure_items" ON "expenditure_items"."expenditure_id" = "expenditures"."id"
WHERE "expenditures"."category" IN ("work_order", "purchase_order")
The only thing I can't get is eliminate the expenditures that either have no invoices or that are over my 10% rule. The first was in my old solution with the original join - I can't seem to figure out how to sum on that join data.
step-by-step demo:db<>fiddle
I am sure, there is a better solution, but this one should work:
WITH cte AS (
SELECT
e.id,
e.category,
COALESCE(parent_id, e.id) AS parent_id,
ei.amount
FROM
expenditures e
JOIN
expenditures_items ei ON e.id = ei.expenditure_id
),
cte2 AS (
SELECT
id,
SUM(amount) FILTER (WHERE category = 'purchase_order') AS expentiture_total,
SUM(amount) FILTER (WHERE category = 'invoice') AS invoice_total
FROM (
SELECT
parent_id AS id,
category,
SUM(amount) AS amount
FROM cte
GROUP BY (parent_id, category)
) s
GROUP BY id
)
SELECT
*,
(invoice_total/expentiture_total - 1) * 100 AS percent
FROM
cte2
The first CTE joins the both tables. The COALESCE() function mirrors the id as parent_id if the record has none (if category = 'purchase_order'). This can be used to do one single GROUP on this id and the category.
This is done within the second CTE (most inner subquery). [Btw: I choose the CTE variant because I find it much more readable. In this case you could do all steps as subqueries of course.] This group sums up the different categories for each (parent_)id.
The outer subquery is doing a pivot. It shifts the different records per category into your expected result with the help of a GROUP BY and the FILTER clause (Have a look at this step in the fiddle to understand it). Don't worry about the SUM() function here. Because of the GROUP BY, one aggregation function is necessary, but it does nothing, because the grouping has been already done.
Last step is calculating the percent value out of the pivoted table.

Query only records with max value within a group

Say you have the following users table on PostgreSQL:
id | group_id | name | age
---|----------|---------|----
1 | 1 | adam | 10
2 | 1 | ben | 11
3 | 1 | charlie | 12 <-
3 | 2 | donnie | 20
4 | 2 | ewan | 21 <-
5 | 3 | fred | 30 <-
How can I query all columns only from the oldest user per group_id (those marked with an arrow)?
I've tried with group by, but keep hitting "users.id" must appear in the GROUP BY clause.
(Note: I have to work the query into a Rails AR model scope.)
After some digging, you can do use PostgreSQL's DISTINCT ON (col):
select distinct on (users.group_id) users.*
from users
order by users.group_id, users.age desc;
-- you might want to add extra column in ordering in case 2 users have the same age for same group_id
Translated in Rails, it would be:
User
.select('DISTINCT ON (users.group_id), users.*')
.order('users.group_id, users.age DESC')
Some doc about DISTINCT ON: https://www.postgresql.org/docs/9.3/sql-select.html#SQL-DISTINCT
Working example: https://www.db-fiddle.com/f/t4jeW4Sy91oxEfjMKYJpB1/0
You could use ROW_NUMBER/RANK(if ties are possible) windowed functions:
SELECT *
FROM (SELECT *,ROW_NUMBER() OVER(PARTITION BY group_id ORDER BY age DESC) AS rn
FROM tab) s
WHERE s.rn = 1;
you can use a subquery wuth aggreagated resul in join
select m.*
from users m
inner join (
select group_id, max(age) max_age
from users
group by group_id
) AS t on (t.group_id = m.group_id and t.max_age = m.age)

Get latest record for multiple ids in psql

I have a table where I have multiple records for the same id. I would like to get the latest record for each id using some where clause.
Sample table records
vendor_id | data | created_at | id
-----------+------------+----------------------------+----
1 | some-data | 2014-01-12 16:32:54.084505 | 2
vendor_id | data | created_at | id
-----------+------------+----------------------------+----
1 | Notsome-dat| 2014-01-13 16:32:54.084505 | 3
I have multiple vendors with same data. So I want to get all the latest records for all the vendors where I can filter it with data. I have been using following query
SELECT VENDOR_ID,MAX(CREATED_AT) FROM TABLE WHERE DATA ILIKE '%Not some%'GROUP BY VENDOR_ID;
However, this query also gives me the vendor_id where they have "Not some" data in their second latest record not the latest one.
Please help.
You have to use DISTINCT
SELECT
DISTINCT ON (vendor_id) data
FROM TABLE
ORDER BY vendor_id, "created_at" DESC;

Generate a Rails model from within code (invoke generator from a controller)

I have a particular need to be able to invoke a Rails command from within code (ie when some action occurs). I need a model to be created with specific fields (which will be determined by a form) and run the created migration in the end.
So this form I have would create all the fields, which then would result with the creation of a model with its certain fields (table and columns)
So, is there a way to invoke rails generate model NAME [field[:type][:index] field[:type] and bundle exec rake db:migrate from within a controller/ruby code?
Rather than having one table per category, here's a more relational-database-y approach:
create table category (
id serial primary key,
name text not null
);
create table attribute (
id serial primary key,
name text not null
);
create table item (
id serial primary key,
category_id integer not null references category (id),
description text
);
create table category_attribute (
attribute_id integer not null references attribute (id),
category_id integer not null references category (id)
);
create table item_attribute (
attribute_id integer not null references (attribute.id),
item_id integer not null references item (id),
value text
);
When you create a category, you store its name (and any other one-to-one attributes) in the category table. You make sure the attribute table has an entry for every attribute the category has, and then use the category_attribute table to link those attributes to that category.
When you add a new member of a category, you use the item table to store the main things about the item and the item_attribute table to store the values of each of its attributes. So with the cars and pets approach you mentioned, your database might look like
category
id | name
----+------
1 | car
2 | pet
attribute
id | name
----+------------
1 | make
2 | breed
3 | model_year
4 | name
category_attribute
attribute_id | category_id
--------------+-------------
1 | 1
2 | 2
3 | 1
4 | 2
item
id | category_id | description
----+-------------+----------------
1 | 1 | Hyundai Accent
2 | 2 | Fuzzy kitty
item_attribute
attribute_id | item_id | value
--------------+---------+---------
1 | 1 | Hyundai
3 | 1 | 2007
2 | 2 | DSH
4 | 2 | Sam
This approach can feel pretty non-obvious, because it doesn't match the "one object with many attributes" style you use with Rails models. This is how relational databases work, however. I believe there's some ActiveRecord magic you can do to make the object/relational translation a little more automatic, but I don't remember what it's called at the moment.

Left Joins that link to multiple rows only returning one

I'm trying to join two table (call them table1 and table2) but only return 1 entry for each match. In table2, there is a column called 'current' that is either 'y', 'n', or 'null'. I have left joined the two tables and put a where clause to get me the 'y' and 'null' instances, those are easy. I need help to get the rows that join to rows that only have a 'n' to return one instance of a 'none' or 'null'. Here is an example
table1
ID
1
2
3
table2
ID | table1ID | current
1 | 1 | y
2 | 2 | null
3 | 3 | n
4 | 3 | n
5 | 3 | n
My current query joins on table1.ID=table2.table1ID and then has a where clause (where table2.current = 'y' or table2.current = 'null') but that doesn't work when there is no 'y' and the value isn't 'null'.
Can someone come up with a query that would join the table like I have but get me all 3 records from table1 like this?
Query Return
ID | table2ID | current
1 | 1 | y
2 | null | null
3 | 3 | null or none
First off, I'm assuming the "null" values are actually strings and not the DB value NULL.
If so, this query below should work (notice the inclusing of the where criteria INSIDE the ON sub-clause)
select
table1.ID as ID
,table2.ID as table2ID
,table2.current
from table1 left outer join table2
on (table2.table1ID = table1.ID and
(table2.current in ('y','null'))
If this does work, I would STRONGLY recommend changing the "null" string value to something else as it is entirely misleading... you or some other developer will lose time debugging this in the future.
If "null" acutally refers to the null value, then change the above query to:
select
table1.ID as ID
,table2.ID as table2ID
,table2.current
from table1 left outer join table2
on (table2.table1ID = table1.ID and
(table2.current = 'y' or table2.current is null))
you need to decide which of the three rows from table2 with table1id = 3 you want:
3 | 3 | n
4 | 3 | n
5 | 3 | n
what's the criterion?
select t1.id
, t2.id
, case when t2.count_current > 0 then
t2.count_current
else
null
end as current
from table1 t1
left outer join
(
select id
, max(table1id)
, sum(case when current = 'y' then 1 else 0 end) as count_current
from table2
group by id
) t2
on t1.id = t2.table1id
although, as justsomebody has pointed out, this may not work as you expect once you have multiple rows with 'y' in your table 2.

Resources