SQLite query with GROUP BY and CASE clause - sql

In a SQLite DB I have the following data structure in the table items (simplified for sake of clarity):
id
storeId
archived
1
a-1
0
2
a-1
1
3
a-1
1
4
b-1
0
5
b-1
0
6
b-1
0
7
b-1
0
8
b-1
1
I'd like to select all items that have been archived (archived = 1), and group by storeId those that have not been archived.
To do so, I've written this query:
const queryString = `
SELECT *
FROM items
GROUP BY
CASE
WHEN items.archived = 1
THEN items.id
ELSE IFNULL(storeId, id)
END
`;
My issue is that the non archived items with a storeId for which there are archived items are never displayed. They seem to be grouped with the archived items with the same storeId value.
In other terms, with that query and that data, I only get the following:
id
storeId
archived
2
a-1
1
3
a-1
1
8
b-1
1
I guess I am missing something of how the GROUP BY works. How can I achieve my intended result?

For anyone interested, the answer was grouping by multiple columns, with concatenation:
const queryString = `
SELECT items.*, items.storeId || ' ' || items.archived AS concatenatedValues
FROM items
GROUP BY
CASE
WHEN items.archived = 1 THEN items.id
ELSE concatenatedValues
END
`;

If what you want is the row with the min id for the storeIds with archived = 0, then you can do it with MIN() window function in a CASE expression:
SELECT DISTINCT
CASE
WHEN archived THEN id
ELSE MIN(id) OVER (PARTITION BY storeId)
END AS id,
storeId,
archived
FROM items;
Or, if archived = 0 (if it exists) is not always the first in each storeId, use FIRST_VALUE() window function:
SELECT DISTINCT
CASE
WHEN archived THEN id
ELSE FIRST_VALUE(id) OVER (PARTITION BY storeId, archived ORDER BY id)
END AS id,
storeId,
archived
FROM items;
See the demo.

Related

How to write a query to find a record which is not processed

I am trying to write a query from a table A which has 2 columns :
ID , STATUS
The Status can be PROCESSING, NOTPROCESSED, FAILED, SUCCESS
When a record is successfully processed, a new record is created in the DB with STATUS as PROCESSED and the ID is the same as the previous NOTPROCESSED record.
The Sample Records in DB would like :
1 NOTPROCESSED
2 PROCESSED
1 PROCESSED
3 NOTPROCESSED
4 NOTPROCESSED
2 PROCESSED
3 NOTPROCESSED
4 NOTPROCESSED
The records can appear as duplicate for NOTPROCESSED.
I have to query the records which are NOTPROCESSED i.e
3 NOTPROCESSED
4 NOTPROCESSED
Its getting quite confusing to write the query.
Can anyone help with the logic.
you may use not exists to get this output.
select distinct a.id,a.status
from table a
where a.status='NOTPROCESSED'
and not exists (select null
from table b
where b.id=a.id
and b.status='PROCESSED')
Group by the ids and take only those groups having no record of status PROCESSED
select id
from your_table
group by id
having sum(case when status = 'PROCESSED' then 1 else 0 end) = 0
or get only the ones with only one kind of status
having count(distinct status) = 1
or use alphabetically the highest status
having max(status) = 'NOTPROCESSED'
Here are a couple of options:
select distinct id from A where id not in (
select id from A where status = 'PROCESSED'
);
select distinct id from A natural left join (
select id from A where status = 'PROCESSED'
) as B where B.id is null;
You can use analytical function as follows:
select * from
(select t.*, count(case when status = 'PROCESSED' then 1 end)
over (partition by ID) as cnt
from your_table t) t
where status = 'NOTPROCESSED' and cnt = 0

SQL - create flag in query to highlight order which contain quantity = 1

I have tried creating a case statement but doesn't seem to give me what i want. Id like to get a split of the table (which is at a product level) and aggregate at an order level of items which contain quantity of 1.
Any ideas on how I would do this?
order id | Product | Quantity
---------+---------+--------------
11111 | sdsd4 | 1 (single item )
22222 | sasas | 1 (multiple items)
22222 | wertt | 1 (multiple items)
I'd like to get a case statement to add another column to split out orders with quantity = 1 and orders greater 1
Any idea on how I would do this?
The desired outcome would be the column in (brackets)
I could then count the orders and bring in the newly created column as the dimension
More detail here:
enter image description here
Attached is an image of table structure.
Logic, if quantity = 1 and 1 order then single item order
if order has one item but multiples of same item non single item order
if order has more than one product then non single item order
If your database supports analytic functions, then you can use a query like this one:
SELECT *,
CASE WHEN count("Product") OVER (partition by "order id") > 1
THEN 'multiple items' ELSE 'single item'
END As "How many items"
FROM Table1
Demo: https://dbfiddle.uk/?rdbms=postgres_11&fiddle=b659279fc16d2084cb1cf4a3bea361a1
Below is for BigQuery Standard SQL
#standardSQL
SELECT *,
CASE COUNT(DISTINCT Product) OVER(PARTITION BY order_id)
WHEN 1 THEN 'Single Item Order'
ELSE 'Multiple Items Order'
END Single_or_Multiple
FROM `project.dataset.table`
You can test, play with above using dummy data as below
#standardSQL
WITH `project.dataset.table` AS (
SELECT 11111 order_id, 'sdsd4' Product, 1 Quantity UNION ALL
SELECT 22222, 'sasas', 2 UNION ALL
SELECT 22222, 'wertt', 1
)
SELECT *,
CASE COUNT(DISTINCT Product) OVER(PARTITION BY order_id)
WHEN 1 THEN 'Single Item Order'
ELSE 'Multiple Items Order'
END Single_or_Multiple
FROM `project.dataset.table`
with result
Row order_id Product Quantity Single_or_Multiple
1 11111 sdsd4 1 Single Item Order
2 22222 sasas 2 Multiple Items Order
3 22222 wertt 1 Multiple Items Order
If I understand this right, you could use a subquery to get the count of records for an order and flag a record, if this count is larger then 1 and the quantity is equal to 1.
SELECT t1.order_id,
t1.product,
t1.quantity,
CASE
WHEN t1.quantity = 1
AND (SELECT count(*)
FROM elbat t2
WHERE t2.order_id = t1.order_id) > 1 THEN
'flag'
ELSE
'no flag'
END flag
FROM elbat t1;

Select if then case with first record

Can you do something like this in SQL Server?
I want to select from a table which has some records with the same product_id in one column and a Y or N in another (in stock), and take the first one which has a Y where the product_id is the same, while matching the product_id_set from another table.
... ,
SELECT
(SELECT TOP 1
(product_name),
CASE
WHEN in_stock = 'Y' THEN product_name
ELSE product_name
END
FROM
Products
WHERE
Products.product_set = Parent_Table.product_set) AS 'Product Name',
...
Sample data would be
product_set in_stock product_id product_name
---------------------------------------------------
1 N 12 Orange
1 Y 12 Pear
2 N 12 Apple
2 N 12 Lemon
Output from product_set = 1 would be 'Pear' for example.
So there's kind of two solutions depending on the answer to the following question. If there are no records for a product id with an in_stock value of 'Y', should anything return? Secondly, if there are multiple rows with in_stock 'Y', do you care which one it picks?
The first solution assumes you want the first row, whether or not there is ANY "Y" value.
select *
from (select RID = row_number() over (partition by product_set order by in_stock desc) -- i.e. sort Y before N
from Products) a
where a.RID = 1
The second will only return a value if there is at least one row with a 'Y' for in_stock. Note that the order by (select null) is essentially saying you don't care which one it picks if there are multiple in_stock items. If you DO care the order, replace it with the appropriate sort condition.
select *
from (select RID = row_number() over (partition by product_set order by (select null)) -- i.e. sort Y before N
from Products
where in_stock = 'Y') a
where a.RID = 1
I don't know what the structure of the "parent table" in your query is, so I've simplified it to assume you have what you need in Products alone.
SELECT ISNULL(
(
SELECT TOP 1 product_name
FROM Products
WHERE Products.product_set = Parent_Table.product_set
AND Products.in_stock = 'Y'
), 'Not in the stock') AS 'Product Name'

How to select or skip to the next row if meets certain criteria

I really am not even sure which direction to go with this...
I'm trying to select a list of customers based on the following rules:
Select all rows from Customer where Ranking = 1,
OR if Ranking = 1 AND Type = Store then Rank 1 and return the row with Rank 2.
OR if the customer only has 1 row, return it even if the type = Store.
The Ranking is not assigned with a Rank statement in the query. Rather it is an actual column in the Customer table (populated by a stored proc that does the ranking).
Using the example below I'd want rows 1, 4, 6, and 10 returned.
Customer Table
RowID CustID Type Ranking
----- ------ ---- -------
1 9 Web 1
2 9 Catalog 2
3 9 Store 3
4 10 Store 1
5 11 Store 1
6 11 Web 2
7 12 Store 1
8 12 Web 2
9 12 Catalog 3
10 13 Web 1
I feel like this task is more difficult BECAUSE the Ranking is already done when the table is created! Any suggestions are most welcome!
You could try something like this (I haven't tested it!):
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE (c.Ranking = 1 AND c.Type != 'Store')
OR (c.Type = 'Store' AND Ranking = 2)
OR (c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2))
If the customer table is large, you might find that the query is a bit slow and something like this would be faster:
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Ranking = 1 AND c.Type != 'Store'
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 2
UNION ALL
SELECT
RowId,
CustId,
Type,
Ranking
FROM Customer c
WHERE c.Type = 'Store' AND Ranking = 1 AND NOT EXISTS (SELECT 1 FROM Customer WHERE CustId = c.CustId AND Ranking = 2)
As with the other answer, I haven't done a lot of thorough testing, but here's what I'd look at. The idea here is to build a row_number over the data set prioritizing type:store to the top, and then using rank as the secondary sort condition.
select *
from (
select
rid = row_number() over (partition by CustID, order by case when type = 'Store' then 0 else 1 end, Rank desc),
rowid,
CustID,
Type,
Ranking
from customer)
where RID = 1
Try:
SELECT *
FROM Customer c
WHERE
-- There's only one row for this customer
(
SELECT COUNT(*)
FROM Customer
WHERE CustID = c.CustID
) = 1
-- There's a row with Ranking = 1 and Type = 'Store', so select Ranking = 2
OR (Ranking = 2 AND EXISTS (SELECT 1 FROM Customer WHERE CustID = c.CustID AND Ranking = 1 AND Type = 'Store'))
-- There's a row with Ranking = 1 that's not Type = 'Store'
OR (Ranking = 1 AND Type <> 'Store')

Query to select last order (entry) of every product belonging to user that's not returned

I am stuck with rather confusing query.
Assume I have a ProductLending table that tracks what product each user has borrowed, when it was renewed, was it returned or not etc.. Given a user, I want to be able to select, all unique products that are still with the user.
table example:
userid DateRenewed ProductId isReturned
````````````````````````````````````````````````
1 2011-07-21 15 0
1 2011-08-20 16 0
1 2011-09-21 15 1
2 2011-09-21 17 0
1 2011-09-21 15 0
This is a mock up so sorry if it's not accurate.
Now, given userId = 1, I want to select just unique productId that are NOT returned, but are with the user. So this should give me 15, 16 as result, as even though 15 was returned, it was re-borrowed. If we delete the last row, then the result would just be 16, since user has only 16 with him.
I tried ordering by dateRenewed and selecting top 1 but it did totally something else.. how do I construct a query for this please?
If product is not returned by user, then the sum of bought products must be larger than sum of returned products
SELECT userid,ProductId FROM <table>
GROUP BY userid,ProductId HAVING SUM(CASE CAST(isReturned AS INT) WHEN 0 THEN 1 ELSE 0 END)-SUM(CAST(isReturned AS INT))>0
Try this:
;WITH qry AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY userID, ProductID ORDER BY DateRenewed DESC) rn
FROM YourTable
)
SELECT *
FROM qry
WHERE rn = 1 AND isReturned = 0;
select distinct ProductId
from TABLE_NAME t1
where UserId= #UserId
and IsReturned = 0
and not exists
(
select *
from TABLE_NAME t2
where t2.UserId = t1.UserId
and t2.ProductId = t1.ProductId
and t2.IsReturned = 1
and t2.DateRenewed > t1.DateRenewed
)