Select a row based on the value of another row - mysql

App goal
Send messages from retailers to registered customers mobile via GCM
DB architecture
I have a customers table and a related customers_realtions table with the following fields: id, customerID, retailerID, isBlocked
Required outcome
A customer can register for a specific retailer, or for a wildcard (all of them).
In case one registers for all the retailers, he has an option to block a specific retailer from sending future messages, effectively creating a blacklist.
DB values for each status
When a customer registers for a single retailer retailerID is assigned with the retailer ID.
When a customer register for all of the retailers retailerID equals 1.
When a customer blocks a retailer there are two options:
a. if he registered to this specific retailer before the isBlocked field is updated to 1 (true)
b. if he registered to all retailers before a new row is created for this retailer and isBlocked is set to 1 (true)
The challenge
When sending the message the SELECT query should include the customers that has a retailerID of 1 and does not have the sending retailerID when isBlocked equals 1.
For example, in this situation
id customerID retailID isBlocked
129 46 111 1
128 46 1 0
I don't want the customer to be selected even if the retailerID is 111
My attempt
SELECT * FROM customers_relations
WHERE
(retailID=111
OR
(retailID=1
AND
(SELECT isBlocked FROM `customers_relations` WHERE customerID=46 AND retailID=111)=0))
AND
NOT isBlocked
Question
While this is working for a single customer for whom I know the ID in advance, I am struggling to figure a way of writing a similar query for multiple customers.

I think of this as an aggregation query. You want to look through all the rows that are not blocked for a customer and determine whether retailer 111 is available or all retailers are available:
SELECT customerId
FROM customers_relations cr
WHERE isBlocked = false
GROUP BY customerId
HAVING MAX(retailId = 111) > 0 OR
MAX(retailId = 1) > 0;
I notice that your question actually says that a new row is created in customer_relations when someone is blocked. The above assumes there is one row. To handle the case when a block on any row would cause a block, then:
SELECT customerId
FROM customers_relations cr
GROUP BY customerId
HAVING (MAX(retailId = 111) > 0 OR
MAX(retailId = 1) > 0
) AND
MAX(retailId = 111 AND isblocked = true) = 0;

Related

I need help to figure out syntax for a view in an existing PostgreSQL database that extracts (unnests) content in a JSON array field

I am working on a JSON array field named session_durations in existing PostgreSQL 11.8 database view. Each field describes the sessionID and the duration (amount of time a program user visits that session). There are 12 possible sessions, an "session" refers here to online lesson in an eHealth treatment program.
This JSON field (session_durations) is populated as the user accesses the session. If user never accesses a session then no data appears in the JSON field for that session (see my examples) -- hence some sessions can be skipped over entirely.
I'd like to use SQL code to unpack this field in order to separate its components. Here are 2 example records:
Record 1: [{"sessionId":"7","duration":1886400},{"sessionId":"8","duration":1710000},{"sessionId":"9","duration":706800}]
Record 2: [{"sessionId":"1","duration":879600},{"sessionId":"2","duration":975600},{"sessionId":"3","duration":9600}]
I'd like to use my View to save duration data (e.g., "duration":879600) from each possible session into 12 new columns for each user session (e.g., "sessionId":"1") named the following:
• S1_duration
• S2_duration
• S3_duration
• S4_duration
• S5_duration
• S6_duration...
• S12_duration
All help would be greatly appreciated!!
Table:
CREATE TABLE users (
id int4 PRIMARY KEY,
session_durations json
);
----some rows of data:
13 [{"sessionId":"1","duration":12699},{"sessionId":"7","duration":1423041},{"sessionId":"8","duration":7598502},{"sessionId":"10","duration":1531229}]
14 [{"sessionId":"1","duration":55812},{"sessionId":"7","duration":2905}]
161 [{"sessionId":"7","duration":1125600},{"sessionId":"8","duration":460800}]
12 [{"sessionId":"1","duration":1520988},{"sessionId":"2","duration":94565},{"sessionId":"6","duration":35468}]
Your solutions worked perfectly! I chose to use the second solution (grouping syntax) for my project. Thanks for your patience -- and the online demo examples!
This should do:
WITH usd AS (
SELECT
us.id AS user_id,
(sd->>'sessionId')::int AS session_id,
(sd->>'duration')::int AS duration
FROM users us,
LATERAL json_array_elements(us.session_durations) AS sd
)
SELECT
users.id AS user_id,
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 1) AS "S1_duration",
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 2) AS "S2_duration",
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 3) AS "S3_duration",
…
(SELECT duration FROM usd WHERE user_id = users.id AND session_id = 12) AS "S12_duration"
FROM users;
(online demo)
Alternatively, using grouping and some filtered aggregate (dealing better with potential duplicates):
SELECT
user_id,
MAX(duration) FILTER (WHERE session_id = 1) AS "S1_duration",
MAX(duration) FILTER (WHERE session_id = 2) AS "S2_duration",
MAX(duration) FILTER (WHERE session_id = 3) AS "S3_duration",
…
MAX(duration) FILTER (WHERE session_id = 12) AS "S12_duration"
FROM (
SELECT
us.id AS user_id,
(sd->>'sessionId')::int AS session_id,
(sd->>'duration')::int AS duration
FROM users us,
LATERAL json_array_elements(us.session_durations) AS sd
) AS usd
GROUP BY user_id;
(online demo)

How to add a tag based on a column value

I'm trying to join two tables and select certain columns to display in the output including a 'flag' if a certain transaction amount is greater than or equal to 100. The flag would return a 1 if it is, else null.
I thought I could achieve this using a CASE in my SELECT but it only returns one record every time since it returns the first record that meets this condition. How do I just create this 'FLAG' column during my join easily?
SELECT payment_id, amount, type,
CASE
WHEN amount >= 100 THEN 1
ELSE NULL
END AS flag
FROM trans JOIN customers ON (user_id = cust_id)
JOIN bank ON (trans.bank = bank.id)
WHERE (error is false)
I expect an output such as:
payment_id amount type flag
1 81 3 NULL
2 104 2 1
3 150 2 1
4 234 1 1
However, I'm only getting the first record such as:
payment_id amount type flag
2 104 2 1
I tried your table structure in my local and it is working perfectly.
I need one thing from you is in which table you are having error column.
If I comment where condition then it is working fine.
If you're getting fewer rows than you expect, it's either due to:
Join condition
You're doing a INNER joins to the customers and bank tables. If you have 4 source rows in your trans table, but only one row that matches in your customers table (condition user_id = cust_id), then you will only have one row returned.
The same goes for the subsequent join to your bank table. If there you somehow have a transaction that references a bank which is not defined in the bank table, then you won't see a record for this row.
WHERE clause
Obviously you won't see any rows that don't meet the conditions specified here.
It's probably #1 -- check to see if the rows with payment_id IN (1,3,4) have corresponding user id values in the user table and corresponding bank id values in the banks table.

Find the count of elements from another table

First the schema - i have 4 tables:
office - officeid, officename
member - memberid, officeid, membername (multiple members per office)
transaction - transactionid, memberid, transactiontype (multiple transactions per member)
activity - activityid, officeid (multiple activities per office)
How can i get the list of member names, along with offices and the count of each transaction type and total count of activities for that office with a single query?
I have tried myself and i can get the office names, member names and activity count but not able to get the transaction count (per transaction type) with a single query. Wondering if this is possible at all.
Any suggestions/help is appreciated. Please let me know if i can provide any details which i missed
Edit - I added a sqlfiddle with some sample data at http://sqlfiddle.com/#!9/9cf7b/1
Also realized i had missed the officeid foreign key in the member table.
Edit - Adding expected output
officename, membername, transaction_count_1, trasaction_count_2, activitycount
abc , aa , 1 , 1, , 3
abc , bb , 1 , 0, , 1
abc , cc , 0 , 1, , 0
I think this can solve your purpose.
select b.membername,a.officename,count(transactiontype)
from office a,
member b,
transaction c
where a.officeid = b.officeid
and b.memberid = c.memberid
group by b.membername,a.officename
If you need any further data manipulation just give me an idea (perferably a screenshot or an image) of how your output shall look and i shall change the query accordingly.
Thanks.

SSRS lookup with source not in current tablix

I have 3 tables that I want to show in a tablix SSRS report in 3.0.
Table 1 - policy ID, amt paid by company
Query - Select * from table1 where amt paid by company <> 0
Table 2 - policy ID, policy number, previous policy number
Query - Select * from table2 where previous policy number <> ' '
Table 3 - previous policy number, paid under prior company
Query - Select * from table3 where paid under prior company <> 0
I want to display the following columns on one tablix row per entry in table 1:
Policy number
amt paid by company
previous policy number
paid under prior company.
I created a tablix. I can display everything from table 1 and use lookup for table 2 items but when I do a lookup for item from table 3 it gives me an error.
From my research about this error I understand it to mean I cannot use the source in lookup from any table but table 1 in my case. And I can only do one level in lookup.
I have looked and can find no example for this anywhere and I have tried other methods and cannot figure out how to get to that piece of data in table 3.
Is my only choice to combine tables 2 and 3 together then use in this report with the lookup?
You can use INNER JOIN to get a dataset that contains all fields you require to show in the tablix.
SELECT
table2.PolicyNumber,
table1.AmntPaidByCompany,
table2.PrevPolicyNumber,
table3.PaidUnderPriorCompany
FROM table2
INNER JOIN table3
ON table2.PrevPolicyNumber = table3.PrevPolicyNumber
INNER JOIN table1
ON table1.PolicyID = table2.PolicyID
WHERE table1.AmntPaidByCompany <> 0
AND table2.PrevPolicyNumber <> ''
AND table3.PaidUnderPriorCompany <> 0
Live Demo
Let me know if this helps.

Is it possible to construct a single query to match two tables enforcing a 1:1 relationship between them?

For those interested in the reasoning behind this question: I have an e-commerce site that works fine, but has no gift certificate capabilities. Adding monetary GCs should be pretty simple, but I'd also like to allow the gifting of specific products (sounds odd but is relevant to my industry). So I plan to create a new table to house gift certificates that are linked to a specific user and product, and I need an efficient way to evaluate that table on the cart and checkout pages.
Imagine tables exist that look similar to the following:
CartContents
CartID Integer (Unique sequential row identifier)
UserID Integer
ProductID Integer
Quantity Integer
Gifts
GiftID Integer (Unique sequential row identifier)
ProductID Integer
UserID Integer
Quantity Integer
This is an overly simplified layout, but demonstrates the idea. The first table lists items in the user's cart; one record per product (though real products will have additional details that may vary). The product table has further attributes on products but I don't list it here for simplicity. The second table is a set of gift certificates, each for a specific product, that have been presented to this user ID.
Table data may look like the following:
CartContents
CartID UserID ProductID Quantity
1 1 1 1
2 1 2 2
3 1 1 2
4 2 3 1
Gifts
ProductID UserID Quantity
1 1 1
2 1 1
3 3 1
Is it possible to construct a single query that provides one row per cart item and links the above two tables taking into account that each gift may only link to each cart item once? Or does this need to be handled in a script?
In other words, because user 1 has product 1 in their cart twice, and they have only been promised one free product 1, the query should return a matching Gifts record for cartID 1, but not cartID 3. The query, pulling for user ID 1, would return:
CartID ProductID Quantity unpaidQuantity
1 1 1 0
2 2 2 1
3 1 2 2
Or
CartID ProductID Quantity unpaidQuantity
1 1 1 1
2 2 2 1
3 1 2 1
I realize that the fact that there is more than one 'right' answer to this question raises a red flag. In reality it doesn't matter which cart record each GC is applied to, as the end result (the price) will work out the same. I'm perfectly happy to say the 'first' (lowest cartID) is the one that should be linked.
My assumption is that the database will be far more efficient at this than any script I could write; I'd even be willing to bet there's some crazy type of join I've never heard of specifically designed for it. I am also assuming that any such ColdFusion script may be somewhat complicated and thus take a fair amount of development and testing time while a single query may be relatively simple (though apparently beyond my limited SQL capabilities). If I'm incorrect in this I'd appreciate any thoughts on that as well.
My setup, if it matters:
MySQL 5.0
ColdFusion 9
Windows 2000 AS
Edit:
It sounds like the quantity column is really going to cause issues, so let's continue assuming that quantity does not exist on the Gifts table. It still must exist on cartContents, however.
I thought of another way of doing this that just requires and additional group by and join. However, it requires a unique id on CartContents. I'm not sure i this is what CartId is supposed to be. However, it seems that a user could have more than one cart, so I assume not.
The idea is to identify the first record for a given product in each cart. Then, use this information when joining in the gifts.
select CartID, UserID, ProductID, Quantity, FirstCCId
from CartContents cc join
(select CartID, UserID, ProductID, min(CartContentsId) as FirstCCId
from CartContents cc
group by CartID, UserID, ProductID
) ccmin
on cc.CartId = ccmin.CartId and cc.UserId = ccmin.UserId and
cc.ProductId = ccmin.ProductId left outer join
Gifts g
on cc.ProductID= g.ProductId and cc.UserID = g.userId and
cc.CartContentsId = ccmin.FirstCCId
This works when the gift is applied to only one product line row. If the quantity for the gift is actually larger than the quantity on any given line, this query still only puts it on one line.
Does this work?
select c.cartid, c.productid, c.quantity, c.quantity -
case
when (select sum(c2.quantity) from CartContents c2
where c.userid = c2.userid
and c.productid = c2.productid
and c.cartid < c2.cartid) <
(select g.quantity from gifts g
where c.userid = g.userid
and c.productid = g.productid) then
(select g.quantity from gifts g
where c.userid = g.userid
and c.productid = g.productid) -
(select sum(c2.quantity) from CartContents c2
where c.userid = c2.userid
and c.productid = c2.productid
and c.cartid < c2.cartid)
else 0
end UnpaidQuantity
from CartContents c
where userid = 1