Get last row from group, limit number of results in PostgreSQL - postgresql

I have a table with records representing a log, I omit rest of the columns in this example.
The id-column is autoincrement, item_id represents an item in app.
I need to get the latest item_id, for example two or three
CREATE TABLE "log" (
"id" INT,
"item_id" INT
);
-- TRUNCATE TABLE "log";
INSERT INTO "log" ("id", "item_id") VALUES
(1, 1),
(2, 2),
(3, 1),
(4, 1),
(5, 3),
(6, 3);
Basic query will list all results, latest at the top:
SELECT *
FROM "log"
ORDER BY "id" DESC
id item_id
6 3
5 3
4 1
3 1
2 2
1 1
I would like to have just two (LIMIT 2) last item_ids with their id. Last means - inserted last (ORDER BY id).
id item_id
6 3
4 1
Last three would be
id item_id
6 3
4 1
2 2
Once an item_id is returned, it is not returned again. So LIMIT 4 would return only three rows because there are only three unique item_id.
I am probably missing something. I already tried various combinations of DISTINCT OF, GROUP BY, LIMIT etc.
UPDATE #1:
After I tested query by S-man (below), I found out that it works for the data I provided howerer it does not work in general, for another set of data (sequence of item_id A, B and A again.). Here is another data set:
TRUNCATE TABLE "log";
INSERT INTO "log" ("id", "item_id") VALUES
(1, 1),
(2, 2),
(3, 3),
(4, 3),
(5, 1),
(6, 3);
Data in DB, ordered by id desc:
id item_id
6 3
5 1
4 3
3 3
2 2
1 1
Expected result for last three item_id
6 3
5 1
2 2

Well, after three changes, now we come back to the very first idea:
Just take DISTINCT ON:
demo:db<>fiddle
SELECT
*
FROM (
SELECT DISTINCT ON (item_id) -- 1
*
FROM log
ORDER BY item_id, id DESC
) s
ORDER BY id DESC -- 2
LIMIT 2
Returns exact one record of an ordered group. You group is the item_id, the order is id DESC, so you get the highest id for each item_id
Reorder by id DESC (instead of the previously ordered item_id) and limit your query output.

Related

tsql max and group by not working properly

I got the following table articles:
ID
category
price
1
category1
10
2
category1
55
3
category2
15
4
category3
20
5
category4
25
I would like to get the highest price of each category.
The result would be:
ID
category
price
2
category1
55
3
category2
15
4
category3
20
5
category4
25
select Max(price), ID, category from article
group by ID,category
returns:
ID
category
price
1
category1
10
2
category1
55
3
category2
15
4
category3
20
5
category4
25
Unfortunately I get both rows for category 1. But I only would like to have the highest price in category 1 which is 55.
Can someone help me?
see above
Try this...
I've reproduced your sample data and then added a rnk column which ranks by price descending witin each category, used this in the subquery and just returned anything where rank is 1.
DECLARE #articles TABLE (ID int, Category varchar(20), Price float)
INSERT INTO #articles VALUES
(1, 'category1', 10),
(2, 'category1', 55),
(3, 'category2', 15),
(4, 'category3', 20),
(5, 'category4', 25)
SELECT
ID, Category, Price
FROM (
SELECT
ID, Category, Price
, RANK() OVER(PARTITION BY Category ORDER BY Price DESC) as rnk
FROM #articles
) a
WHERE a.rnk = 1
Which gives these results
Bote If you have two articles for the same category with the same price, both will be returned.
--===== This is NOT a part of the solution.
-- We're just making "Readily Consumable Test Data" here.
-- This is how you should post sample data to help those
-- that would help you. You'll get more thumbs up on your
-- questions, as well
SELECT *
INTO #Articles
FROM (VALUES
(1, 'category1', 10)
,(2, 'category1', 55)
,(3, 'category2', 15)
,(4, 'category3', 20)
,(5, 'category4', 25)
)d(ID,category,price)
;
--===== One possible easy solution that will also display "ties".
WITH cteRankByCategory AS
(
SELECT *,DR = DENSE_RANK() OVER (PARTITION BY Category ORDER BY Category, Price DESC)
FROM #Articles
)
SELECT ID,Category,MaxPrice = Price
FROM cteRankByCategory
WHERE DR = 1
ORDER BY Category
;

Several top numbers in a column T-SQL

I have a table called _Invoice in SQL Server 2016 - like this:
Company InvoiceNo
-----------------
10 1
10 2
10 3
20 1
20 2
20 3
20 4
I want to get the highest value from all companies.
Like this:
Company InvoiceNo
-----------------
10 3
20 3
I want this data to then update another table that is called InvoiceSeries
where the InvoiceNo is higher than the NextNo in InvoiceSeries table
I am stuck with getting the highest data from InvoiceNo:
UPDATE InvoiceSeries
SET NextNo = -- Highest number from each company--
FROM InvoiceSeries ise
JOIN _Invoice i ON ise.InvoiceSeries = i.InvoiceSeries
WHERE i.InvoiceNo > ise.NextNo
Some example data:
Columns in InvoiceSeries Columns in _Invoices
Company NextNo Company InvoiceNo
10 9007 10 9008
20 1001 10 9009
10 9010
10 9011
10 9012
20 1002
20 1003
20 1004
If I understand correctly, you are looking for the HIGHEST common invoice number
Example
Select A.*
From YourTable A
Join (
Select Top 1 with ties
InvoiceNo
From YourTable
Group By InvoiceNo
Having count(Distinct Company) = (Select count(Distinct Company) From YourTable)
Order By InvoiceNo Desc
) B on A.InvoiceNo=B.InvoiceNo
Returns
Company InvoiceNo
10 3
20 3
EDIT - Updated for comment
Select company
,Invoice=max(invoiceno)
From YourTable
Group By company
This answer assumes there will be a record in the Invoice Series table.
--Insert Sample Data
CREATE TABLE #_Invoice (Company INT, InvoiceNo INT)
INSERT INTO #_Invoice(Company, InvoiceNo)
VALUES
(10 , 1),
(10 , 2),
(10 , 3),
(20 , 1),
(20 , 2),
(20 , 3),
(20 , 4)
CREATE TABLE #InvoiceSeries(Company INT, NextNo INT)
INSERT INTO #InvoiceSeries(Company, NextNo)
VALUES
(10, 1),
(20 ,1)
UPDATE s
SET NextNo = MaxInvoiceNo
FROM #InvoiceSeries s
INNER JOIN (
--Get the Max invoice number per company
SELECT Company, MAX(InvoiceNo) as MaxInvoiceNo
FROM #_Invoice
GROUP BY Company
) i on i.Company = s.Company
AND s.NextNo < i.MaxInvoiceNo --Only join to records where the 'nextno' is less than the max
--Confirm results
SELECT * FROM #InvoiceSeries
DROP TABLE #InvoiceSeries
DROP TABLE #_Invoice

Postgresql: only keep unique values from integer array

Let's say I have an array of integers
1 6 6 3 3 8 4 4
It will be always of the form n*(pairs of number) + 2 (unique numbers).
Is there an efficient way of keeping only the 2 uniques values (i.e. the 2 with single occurence)?
Here, I would like to get 1 and 8.
So far is what I have:
SELECT node_id
FROM
( SELECT node_id, COUNT(*)
FROM unnest(array[1, 6, 6 , 3, 3 , 8 , 4 ,4]) AS node_id
GROUP BY node_id
) foo
ORDER BY count LIMIT 2;
You are very close, I think:
SELECT node_id
FROM (SELECT node_id, COUNT(*)
FROM unnest(array[1, 6, 6 , 3, 3 , 8 , 4 ,4]) AS node_id
GROUP BY node_id
HAVING count(*) = 1
) foo ;
You can group these back into an array, if you like, using array_agg().

TSQL passing 2 values array to stored procedure

I'm using SQL Server 2012 and C#.
Imagine have something similar to a shopping cart and now need to create an order with the following items:
productA - 4 (qty)
productB - 1 (qty)
productC - 9 (qty)
In my C# code I have a list that looks like this:
id : "productA" , qty : "4"
id : "productB" , qty : "1"
id : "productV" , qty : "9"
Questions:
How can I pass the list of 2 values to the stored procedure?
How can I have the stored procedure run 3 while loops each one running 4 times, then once then 9 times in order to physically create one record x request?
Note: In my case I don't have a QTY column in the table, I need to specifically create one record x item on the order.
You can done this by Table Value Parameter in SQL.
Sql Authority
MSDN
You can done this by passing TVP as #table format
declare #table table(product varchar(10), qty int)
insert into #table
select 'product1', 4 union
select 'product2', 2
;WITH cte AS (
SELECT product, qty FROM #table
UNION ALL
SELECT product, qty-1 FROM cte WHERE qty > 1
)
SELECT t.product, t.qty
FROM cte c
JOIN #table t ON c.product = t.product
ORDER BY 1
Reference for the CTE : Creating duplicate records for a given table row
To pass a table into the stored procedure use table-valued parameter.
At first create a type:
CREATE TYPE [dbo].[ProductsTableType] AS TABLE(
[ID] [varchar](50) NOT NULL,
[qty] [int] NOT NULL
)
Then use this type in the stored procedure. The #ParamProducts is a table and can be used in all queries where a table can be used.
CREATE PROCEDURE [dbo].[AddProducts]
#ParamProducts ProductsTableType READONLY
AS
BEGIN
...
END
To actually insert required number of rows I would use a table of numbers , http://web.archive.org/web/20150411042510/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-numbers-table.html
In my database I have a table called Numbers with a column Number that contains numbers from 1 to 100,000. Once you have such table it is trivial to get the set that you need.
DECLARE #T TABLE (ID varchar(50), qty int);
INSERT INTO #T (ID, qty) VALUES ('productA', 4);
INSERT INTO #T (ID, qty) VALUES ('productB', 1);
INSERT INTO #T (ID, qty) VALUES ('productV', 9);
SELECT *
FROM
#T AS Products
INNER JOIN dbo.Numbers ON Products.qty >= dbo.Numbers.Number
;
Result set
ID qty Number
productA 4 1
productA 4 2
productA 4 3
productA 4 4
productB 1 1
productV 9 1
productV 9 2
productV 9 3
productV 9 4
productV 9 5
productV 9 6
productV 9 7
productV 9 8
productV 9 9
This is an example. In your case you would have this SELECT inside INSERT INTO YourFinalTable.

T-SQL table variable data order

I have a UDF which returns table variable like
--
--
RETURNS #ElementTable TABLE
(
ElementID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
ElementValue VARCHAR(MAX)
)
AS
--
--
Is the order of data in this table variable guaranteed to be same as the order data is inserted into it. e.g. if I issue
INSERT INTO #ElementTable(ElementValue) VALUES ('1')
INSERT INTO #ElementTable(ElementValue) VALUES ('2')
INSERT INTO #ElementTable(ElementValue) VALUES ('3')
I expect data will always be returned in that order when I say
select ElementValue from #ElementTable --Here I don't use order by
EDIT:
If order by is not guaranteed then the following query
SELECT T1.ElementValue,T2.ElementValue FROM dbo.MyFunc() T1
Cross Apply dbo.MyFunc T2
order by t1.elementid
will not produce 9x9 matrix as
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
consistently.
Is there any possibility that it could be like
1 2
1 1
1 3
2 3
2 2
2 1
3 1
3 2
3 3
How to do it using my above function?
No, the order is not guaranteed to be the same.
Unless, of course you are using ORDER BY. Then it is guaranteed to be the same.
Given your update, you obtain it in the obvious way - you ask the system to give you the results in the order you want:
SELECT T1.ElementValue,T2.ElementValue FROM dbo.MyFunc() T1
Cross join dbo.MyFunc() T2
order by t1.elementid, t2.elementid
You are guaranteed that if you're using inefficient single row inserts within your UDF, that the IDENTITY values will match the order in which the individual INSERT statements were specified.
Order is not guaranteed.
But if all you want is just simply to get your records back in the same order you inserted them, then just order by your primary key. Since you already have that field setup as an auto-increment, it should suffice.
...or use a deterministic function
SELECT TOP 9
M1 = (ROW_NUMBER() OVER(ORDER BY id) + 2) / 3,
M2 = (ROW_NUMBER() OVER(ORDER BY id) + 2) % 3 + 1
FROM
sysobjects
M1 M2
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3