Bringing all the rows from left join in Postgresql - sql

Here are two tables, reseller and domain_name, I want all the resellers against the domain_name.extensions they have(even if extension count is zero).
Problem is that this query is returning only those resellers whose extension count is not null
SELECT row_number() over (order by 1) as id, *, 123 as total FROM crosstab(
$$
select domain_name.invoicing_party , domain_name.extension_id, count(*) as total
from reseller LEFT JOIN domain_name
ON domain_name.invoicing_party_id = reseller.id
where domain_name.registration_date::date = '2015-05-25'
group by extension_id,invoicing_party
order by invoicing_party, extension_id
$$,
$$ SELECT m FROM generate_series(1,9) m $$
) AS (
invoicing_party varchar, "com" varchar, "my" varchar, "idn" varchar, "net" varchar,
"org" varchar, "edu" varchar, "mil" varchar, "gov" varchar, "name" varchar
) ;
Any help would of great value.
Edit 1
Even if I remove the where clause, even then I am getting only those resellers who have some domain extensions.

Related

Nested query that requires the first result to be returned

I have 2 tables as such
Table ErrorCodes:
type_code desc
01 Error101
02 Error99
03 Error120
Table ErrorXML:
row_index typeCode
1 87
2 02
3 01
The output should be the description(column desc) of the first matched type_code between the 2 tables
Expected output : Error99
I have gotten so far.
select isnull(descript, 'unknown') as DESCRIPTION
from (select top 1 a.stmt_cd as descript
from ErrorCodes a, ErrorXML b
where a.type_cd = b.typecode
order by b.row_index)
But this query doesn't return the string UNKNOWN when there is no common typecode (join condition) between the 2 tables. In this case, im getting null.
How can I resolve this?
This is an interesting question. I believe the following can be an intuitive and beautiful solution (I used desc_ as column name rather than desc which is a reserved word):
select (select desc_ from ErrorCodes x where x.type_code = a.typeCode) desc_
from ErrorXML a
where (select desc_ from ErrorCodes x where x.type_code = a.typeCode) is not null
order by row_index
limit 1;
If you also need to handle the case if query returns no row then for MySQL, following syntax should suffice. For other databases you can use similar encapsulation with isnull, nvl, etc:
select ifnull((select (select desc_ from ErrorCodes x where x.type_code = a.typeCode) desc_ from ErrorXML a where (select desc_ from ErrorCodes x where x.type_code = a.typeCode) is not null order by row_index limit 1), 'UNKNOWN');
To test I used following scripts and seems to work properly:
create database if not exists stackoverflow;
use stackoverflow;
drop table if exists ErrorCodes;
create table ErrorCodes
(
type_code varchar(2),
desc_ varchar(10)
);
insert into ErrorCodes(type_code, desc_) values
('01', 'Error101'),
('02', 'Error99'),
('03', 'Error120');
drop table if exists ErrorXML;
create table ErrorXML
(
row_index integer,
typeCode varchar(2)
);
insert into ErrorXML(row_index, typeCode) values
('1', '87'),
('2', '02'),
('3', '01');
Final-1 quote: While generating your tables try to use same column names as much as possible. I.e. I'd suggest ErrorXML to use type_code rather than typeCode.
Final quote: I choose to use lower letters in SQL since capital letters should be used while emphasizing an important point. I also suggest that style.
What about this: Do a subquery to bring back the first row_index for each type_code.
Do a LEFT OUTER Join on the ErrorCodes table so that you get NULLs as well.
SELECT
ISNULL(ErrorCodes.desc,'unknown') AS description
ErrorXML.row_index
FROM ErrorCodes
LEFT OUTER JOIN (
SELECT type_code, MIN(row_index) AS row_index
FROM ErrorXML
GROUP BY type_code
) AS ErrorXML ON ErrorCodes.type_code = ErrorXML .type_code

order by Item must appear in the select list if distinct is used

Need to return a temp table in SQL joining another temp table using DISTINCT and ORDER BY clause.
I have a declared a table which returns a few things.
Declare #GrpItems TABLE (ID INT,
Name NVARCHAR(32),
Date DATETIME,
City NVARCHAR(32),
CityCode NVARCHAR(8),
CurrencySort NVARCHAR(16)
)
INSERT INTO #GrpItems
SELECT
ID, Name, Date ,
CityCodeorCaption --this can be two type based on User input CityCode or CityCaption
FROM
RepeatItemTable
Now I have a different table where I want to insert and the procedure returns that table as the final result.
DECLARE #CurrencyTable TABLE (RowNumber INT Identity (1,1),
FK_Currency INT,
Value INT,
CityCode NVARCHAR(16),
CityCaption NVARCHAR(16)
)
INSERT INTO #Currency
SELECT DISTINCT
gb.FK_Currency, cv.Value,
c.CityCode, c.CityCaption
FROM
Balance b
JOIN
Currency c ON c.PK_Currency = b.FK_Currency
JOIN
#GrpItems gi ON c.FK_Grpitem = gi.PK_Grpitem
ORDER BY
gi.CityCodeorName
I know somewhere I need group by but I am not sure or a select clause in where filter
I think
ORDER BY
gi.CityCodeOrNAME
WHEN 'City' THEN City
ELSE CityCode ASC
END
Which does not seem to work? I need the Distinct because it might break some other logic.
Select * from #CurrencyTable
You can always use group by instead of select distinct. That will solve your problem:
SELECT gb.FK_Currency, cv.Value, c.CityCode, c.CityCaption
FROM Balance b JOIN
Currency c
ON c.PK_Currency = b.FK_Currency JOIN
#GrpItems gi
ON c.FK_Grpitem = gi.PK_Grpitem
GROUP BY gb.FK_Currency, cv.Value, c.CityCode, c.CityCaption
ORDER BY MAX(gi.CityCodeorName) ;
Note the use of the aggregation function in the ORDER BY.
ORDER BY CASE WHEN CityCodeOrNAME = 'City'
THEN City
ELSE CityCode
END
In case you need differnt orders you can also separate them
ORDER BY CASE WHEN CityCodeOrNAME = 'City' THEN City END DESC,
CASE WHEN CityCodeOrNAME <> 'City' THEN CityCode END ASC

Get rows for the last 10 dates

I have a scenario in a Postgres 9.3 database where I have to get the last 10 dates when books were sold. Consider below example:
Store Book
---------- ----------------------
Id Name Id Name Sid Count Date
1 ABC 1 XYZ 1 20 11/11/2015
2 DEF 2 JHG 1 10 11/11/2015
3 UYH 1 10 15/11/2015
4 TRE 1 50 17/11/2015
There is currently no UNIQUE constraint on (name, sid, date) in table book, but we have a service in place that inserts only one count per day.
I have to get results based on store.id. When I pass the ID, the report should be generated with bookname, sold date, and the count of sold copies.
Desired output:
BookName 11/11/2015 15/11/2015 17/11/2015
XYZ 20 -- --
JHG 10 -- --
UYH -- 10 --
TRE -- -- 50
This looks unsuspicious, but it's a hell of a question.
Assumptions
Your counts are integer.
All columns in table book are defined NOT NULL.
The composite (name, sid, date) is unique in table book. You should have a UNIQUE constraint, preferably (for performance) with columns in this order:
UNIQUE(sid, date, name)
This provides the index needed for performance automatically. (Else create one.) See:
Multicolumn index and performance
Is a composite index also good for queries on the first field?
crosstab() queries
To get top performance and short query strings (especially if you run this query often) I suggest the additional module tablefunc providing various crosstab() functions. Basic instructions:
PostgreSQL Crosstab Query
Basic queries
You need to get these right first.
The last 10 days:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
Numbers for last 10 days using the window function dense_rank():
SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC;
(Not including actual dates in this query.)
Column names for output columns (for full solution):
SELECT 'bookname, "' || string_agg(to_char(date, 'DD/MM/YYYY'), '", "' ORDER BY date) || '"'
FROM (
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10
) sub;
Simple result with static column names
This may be good enough for you - but we don't see actual dates in the result:
SELECT * FROM crosstab(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
) AS (bookname text
, date1 int, date2 int, date3 int, date4 int, date5 int
, date6 int, date7 int, date8 int, date9 int, date10 int);
For repeated use I suggest you create this (very fast) generic C function for 10 integer columns once, to simplify things a bit:
CREATE OR REPLACE FUNCTION crosstab_int10(text, text)
RETURNS TABLE (bookname text
, date1 int, date2 int, date3 int, date4 int, date5 int
, date6 int, date7 int, date8 int, date9 int, date10 int)
LANGUAGE C STABLE STRICT AS
'$libdir/tablefunc','crosstab_hash';
Details in this related answer:
Dynamically generate columns for crosstab in PostgreSQL
Then your call becomes:
SELECT * FROM crosstab(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
); -- no column definition list required!
Full solution with dynamic column names
Your actual question is more complicated, you also want dynamic column names.
For a given table, the resulting query could look like this then:
SELECT * FROM crosstab_int10(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
) AS t(bookname
, "04/11/2015", "05/11/2015", "06/11/2015", "07/11/2015", "08/11/2015"
, "09/11/2015", "10/11/2015", "11/11/2015", "15/11/2015", "17/11/2015");
The difficulty is to distill dynamic column names. Either assemble the query string by hand, or (much rather) let this function do it for you:
CREATE OR REPLACE FUNCTION f_generate_date10_sql(_sid int = 1)
RETURNS text
LANGUAGE sql AS
$func$
SELECT format(
$$SELECT * FROM crosstab_int10(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = %1$s
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
) AS ct(bookname, "$$
|| string_agg(to_char(date, 'DD/MM/YYYY'), '", "' ORDER BY date) || '")'
, _sid)
FROM (
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10
) sub
$func$;
Call:
SELECT f_generate_date10_sql(1);
This generates the desired query, which you execute in turn.
db<>fiddle here

Combine "person" table, "logged on" table and "data" table to get person with latest logged on date that has data

I have three tables:
Table 1: PERSON
personName varchar2 unique
personGender varchar2
personAge int
Table 2: LOGGED_ON_LOG
id int unique
personName varchar2
loggedOn timestamp
Table 3: DATA
id int unique
personName varchar2
dataContent varchar2
I have been trying to write a join statement to retrieve a result of all persons that has data in DATA table with the last time they logged in.
I know I can do:
SELECT loggedOn FROM LOGGED_ON_LOG WHERE loggedOn IN (SELECT max(loggedOn) as loggedOn FROM LOGGED_ON_LOG WHERE personName='John Doe');
to get the last time a user was logged on.
And I can do:
SELECT personName, personGender, personAge FROM PERSON WHERE personName IN (SELECT DISTINCT personName FROM DATA);
to get all persons that have data.
How can I join these two selects so that I get a result of:
personName, personGender, personAge, loggedOn
You are probably not getting a response because not that many folks have used apache derby but this should work for most dbmses if not necessarily optimized:
SELECT p.personName, p.personGender, p.personAge, Max( loggedOn)
FROM PERSON p, Logged_on_log l
WHERE p.personName = l.personName
AND exists (SELECT 1 FROM DATA d where d.person_name = p.person_name)
GROUP BY p.personName, p.personGender, p.personAge
If for some reason derby doesn't support group bys you could just add your subquery:
AND loggedOn = (
SELECT max(loggedOn) FROM LOGGED_ON_LOG l2
WHERE p.personName = l2.person_name)

Finding Duplicate occurrences in column

I have 2 tables Customer and meter.
A customer might have multiple meternbr in meter table. Customer has a customernbr column.
I want to return customers who have more than one meternbr only. Look at the table below. I want to return only customers a and c with the meternbr also.
Customer Meter
-------- -----
a a-100
b a-101
c b-103
d c-104
c-105
If that is a single string (which i don't think is a good idea to begin with), and if your DBMS supports LEFT/SUBSTRING and INSTR you can do a LEFT or a SUBSTRING combined with a INSTR that finds where is the first '-' index and get the customers that have more than one occurrence by using GROUP BY and HAVING COUNT(*) > 1.
SELECT LEFT(meterColumn,INSTR(meterColumn,'-')-1)
FROM meter
WHERE LEFT(meterColumn,INSTR(meterColumn,'-')-1) IN (
SELECT LEFT(meterColumn,INSTR(meterColumn,'-')-1)
FROM meter
GROUP BY LEFT(meterColumn,INSTR(meterColumn,'-')-1)
HAVING COUNT(*) > 1
)
GROUP BY 1;
If those are two columns in the meter table (customerNbr) and (meterNbr), you could simply do:
SELECT customerNbr
FROM meter
GROUP BY 1
HAVING COUNT(*) > 1;
Use GROUP BY and HAVING Clause with COUNT(*) > 1.
Here's a working Sample: http://sqlfiddle.com/#!3/d1b91/17
Pasting code and results below also:
CREATE TABLES (Note: Have not placed FK Constaint for demo purposes)
CREATE TABLE Customer
(
customernbr NVARCHAR(20) NOT NULL
)
CREATE TABLE Meter
(
meternbr NVARCHAR(20) NOT NULL,
customernbr NVARCHAR(20) NOT NULL
)
INSERT DATA. (Uncomment last 2 SELECT statements if you wanna see the data)
INSERT INTO Customer VALUES
('a'),
('b'),
('c'),
('d');
INSERT INTO Meter VALUES
('a-100','a'),
('a-101','a'),
('b-103','b'),
('c-104','c'),
('c-105','c'),
('d-106','d');
--SELECT * FROM Customer;
--SELECT * FROM Meter;
RUN SELECT STATEMENT
SELECT
customernbr AS 'Customer',
meternbr AS 'Meter'
FROM Meter WHERE customernbr IN
(
SELECT customernbr
FROM Meter
GROUP BY customernbr
HAVING COUNT(*) > 1
)
SEE RESULTS :)
CUSTOMER METER
a a-100
a a-101
c c-104
c c-105